engine.py 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473
  1. # ext/asyncio/engine.py
  2. # Copyright (C) 2020-2024 the SQLAlchemy authors and contributors
  3. # <see AUTHORS file>
  4. #
  5. # This module is part of SQLAlchemy and is released under
  6. # the MIT License: https://www.opensource.org/licenses/mit-license.php
  7. from __future__ import annotations
  8. import asyncio
  9. import contextlib
  10. from typing import Any
  11. from typing import AsyncIterator
  12. from typing import Callable
  13. from typing import Dict
  14. from typing import Generator
  15. from typing import NoReturn
  16. from typing import Optional
  17. from typing import overload
  18. from typing import Tuple
  19. from typing import Type
  20. from typing import TYPE_CHECKING
  21. from typing import TypeVar
  22. from typing import Union
  23. from . import exc as async_exc
  24. from .base import asyncstartablecontext
  25. from .base import GeneratorStartableContext
  26. from .base import ProxyComparable
  27. from .base import StartableContext
  28. from .result import _ensure_sync_result
  29. from .result import AsyncResult
  30. from .result import AsyncScalarResult
  31. from ... import exc
  32. from ... import inspection
  33. from ... import util
  34. from ...engine import Connection
  35. from ...engine import create_engine as _create_engine
  36. from ...engine import create_pool_from_url as _create_pool_from_url
  37. from ...engine import Engine
  38. from ...engine.base import NestedTransaction
  39. from ...engine.base import Transaction
  40. from ...exc import ArgumentError
  41. from ...util.concurrency import greenlet_spawn
  42. if TYPE_CHECKING:
  43. from ...engine.cursor import CursorResult
  44. from ...engine.interfaces import _CoreAnyExecuteParams
  45. from ...engine.interfaces import _CoreSingleExecuteParams
  46. from ...engine.interfaces import _DBAPIAnyExecuteParams
  47. from ...engine.interfaces import _ExecuteOptions
  48. from ...engine.interfaces import CompiledCacheType
  49. from ...engine.interfaces import CoreExecuteOptionsParameter
  50. from ...engine.interfaces import Dialect
  51. from ...engine.interfaces import IsolationLevel
  52. from ...engine.interfaces import SchemaTranslateMapType
  53. from ...engine.result import ScalarResult
  54. from ...engine.url import URL
  55. from ...pool import Pool
  56. from ...pool import PoolProxiedConnection
  57. from ...sql._typing import _InfoType
  58. from ...sql.base import Executable
  59. from ...sql.selectable import TypedReturnsRows
  60. _T = TypeVar("_T", bound=Any)
  61. def create_async_engine(url: Union[str, URL], **kw: Any) -> AsyncEngine:
  62. """Create a new async engine instance.
  63. Arguments passed to :func:`_asyncio.create_async_engine` are mostly
  64. identical to those passed to the :func:`_sa.create_engine` function.
  65. The specified dialect must be an asyncio-compatible dialect
  66. such as :ref:`dialect-postgresql-asyncpg`.
  67. .. versionadded:: 1.4
  68. :param async_creator: an async callable which returns a driver-level
  69. asyncio connection. If given, the function should take no arguments,
  70. and return a new asyncio connection from the underlying asyncio
  71. database driver; the connection will be wrapped in the appropriate
  72. structures to be used with the :class:`.AsyncEngine`. Note that the
  73. parameters specified in the URL are not applied here, and the creator
  74. function should use its own connection parameters.
  75. This parameter is the asyncio equivalent of the
  76. :paramref:`_sa.create_engine.creator` parameter of the
  77. :func:`_sa.create_engine` function.
  78. .. versionadded:: 2.0.16
  79. """
  80. if kw.get("server_side_cursors", False):
  81. raise async_exc.AsyncMethodRequired(
  82. "Can't set server_side_cursors for async engine globally; "
  83. "use the connection.stream() method for an async "
  84. "streaming result set"
  85. )
  86. kw["_is_async"] = True
  87. async_creator = kw.pop("async_creator", None)
  88. if async_creator:
  89. if kw.get("creator", None):
  90. raise ArgumentError(
  91. "Can only specify one of 'async_creator' or 'creator', "
  92. "not both."
  93. )
  94. def creator() -> Any:
  95. # note that to send adapted arguments like
  96. # prepared_statement_cache_size, user would use
  97. # "creator" and emulate this form here
  98. return sync_engine.dialect.dbapi.connect( # type: ignore
  99. async_creator_fn=async_creator
  100. )
  101. kw["creator"] = creator
  102. sync_engine = _create_engine(url, **kw)
  103. return AsyncEngine(sync_engine)
  104. def async_engine_from_config(
  105. configuration: Dict[str, Any], prefix: str = "sqlalchemy.", **kwargs: Any
  106. ) -> AsyncEngine:
  107. """Create a new AsyncEngine instance using a configuration dictionary.
  108. This function is analogous to the :func:`_sa.engine_from_config` function
  109. in SQLAlchemy Core, except that the requested dialect must be an
  110. asyncio-compatible dialect such as :ref:`dialect-postgresql-asyncpg`.
  111. The argument signature of the function is identical to that
  112. of :func:`_sa.engine_from_config`.
  113. .. versionadded:: 1.4.29
  114. """
  115. options = {
  116. key[len(prefix) :]: value
  117. for key, value in configuration.items()
  118. if key.startswith(prefix)
  119. }
  120. options["_coerce_config"] = True
  121. options.update(kwargs)
  122. url = options.pop("url")
  123. return create_async_engine(url, **options)
  124. def create_async_pool_from_url(url: Union[str, URL], **kwargs: Any) -> Pool:
  125. """Create a new async engine instance.
  126. Arguments passed to :func:`_asyncio.create_async_pool_from_url` are mostly
  127. identical to those passed to the :func:`_sa.create_pool_from_url` function.
  128. The specified dialect must be an asyncio-compatible dialect
  129. such as :ref:`dialect-postgresql-asyncpg`.
  130. .. versionadded:: 2.0.10
  131. """
  132. kwargs["_is_async"] = True
  133. return _create_pool_from_url(url, **kwargs)
  134. class AsyncConnectable:
  135. __slots__ = "_slots_dispatch", "__weakref__"
  136. @classmethod
  137. def _no_async_engine_events(cls) -> NoReturn:
  138. raise NotImplementedError(
  139. "asynchronous events are not implemented at this time. Apply "
  140. "synchronous listeners to the AsyncEngine.sync_engine or "
  141. "AsyncConnection.sync_connection attributes."
  142. )
  143. @util.create_proxy_methods(
  144. Connection,
  145. ":class:`_engine.Connection`",
  146. ":class:`_asyncio.AsyncConnection`",
  147. classmethods=[],
  148. methods=[],
  149. attributes=[
  150. "closed",
  151. "invalidated",
  152. "dialect",
  153. "default_isolation_level",
  154. ],
  155. )
  156. class AsyncConnection(
  157. ProxyComparable[Connection],
  158. StartableContext["AsyncConnection"],
  159. AsyncConnectable,
  160. ):
  161. """An asyncio proxy for a :class:`_engine.Connection`.
  162. :class:`_asyncio.AsyncConnection` is acquired using the
  163. :meth:`_asyncio.AsyncEngine.connect`
  164. method of :class:`_asyncio.AsyncEngine`::
  165. from sqlalchemy.ext.asyncio import create_async_engine
  166. engine = create_async_engine("postgresql+asyncpg://user:pass@host/dbname")
  167. async with engine.connect() as conn:
  168. result = await conn.execute(select(table))
  169. .. versionadded:: 1.4
  170. """ # noqa
  171. # AsyncConnection is a thin proxy; no state should be added here
  172. # that is not retrievable from the "sync" engine / connection, e.g.
  173. # current transaction, info, etc. It should be possible to
  174. # create a new AsyncConnection that matches this one given only the
  175. # "sync" elements.
  176. __slots__ = (
  177. "engine",
  178. "sync_engine",
  179. "sync_connection",
  180. )
  181. def __init__(
  182. self,
  183. async_engine: AsyncEngine,
  184. sync_connection: Optional[Connection] = None,
  185. ):
  186. self.engine = async_engine
  187. self.sync_engine = async_engine.sync_engine
  188. self.sync_connection = self._assign_proxied(sync_connection)
  189. sync_connection: Optional[Connection]
  190. """Reference to the sync-style :class:`_engine.Connection` this
  191. :class:`_asyncio.AsyncConnection` proxies requests towards.
  192. This instance can be used as an event target.
  193. .. seealso::
  194. :ref:`asyncio_events`
  195. """
  196. sync_engine: Engine
  197. """Reference to the sync-style :class:`_engine.Engine` this
  198. :class:`_asyncio.AsyncConnection` is associated with via its underlying
  199. :class:`_engine.Connection`.
  200. This instance can be used as an event target.
  201. .. seealso::
  202. :ref:`asyncio_events`
  203. """
  204. @classmethod
  205. def _regenerate_proxy_for_target(
  206. cls, target: Connection
  207. ) -> AsyncConnection:
  208. return AsyncConnection(
  209. AsyncEngine._retrieve_proxy_for_target(target.engine), target
  210. )
  211. async def start(
  212. self, is_ctxmanager: bool = False # noqa: U100
  213. ) -> AsyncConnection:
  214. """Start this :class:`_asyncio.AsyncConnection` object's context
  215. outside of using a Python ``with:`` block.
  216. """
  217. if self.sync_connection:
  218. raise exc.InvalidRequestError("connection is already started")
  219. self.sync_connection = self._assign_proxied(
  220. await greenlet_spawn(self.sync_engine.connect)
  221. )
  222. return self
  223. @property
  224. def connection(self) -> NoReturn:
  225. """Not implemented for async; call
  226. :meth:`_asyncio.AsyncConnection.get_raw_connection`.
  227. """
  228. raise exc.InvalidRequestError(
  229. "AsyncConnection.connection accessor is not implemented as the "
  230. "attribute may need to reconnect on an invalidated connection. "
  231. "Use the get_raw_connection() method."
  232. )
  233. async def get_raw_connection(self) -> PoolProxiedConnection:
  234. """Return the pooled DBAPI-level connection in use by this
  235. :class:`_asyncio.AsyncConnection`.
  236. This is a SQLAlchemy connection-pool proxied connection
  237. which then has the attribute
  238. :attr:`_pool._ConnectionFairy.driver_connection` that refers to the
  239. actual driver connection. Its
  240. :attr:`_pool._ConnectionFairy.dbapi_connection` refers instead
  241. to an :class:`_engine.AdaptedConnection` instance that
  242. adapts the driver connection to the DBAPI protocol.
  243. """
  244. return await greenlet_spawn(getattr, self._proxied, "connection")
  245. @util.ro_non_memoized_property
  246. def info(self) -> _InfoType:
  247. """Return the :attr:`_engine.Connection.info` dictionary of the
  248. underlying :class:`_engine.Connection`.
  249. This dictionary is freely writable for user-defined state to be
  250. associated with the database connection.
  251. This attribute is only available if the :class:`.AsyncConnection` is
  252. currently connected. If the :attr:`.AsyncConnection.closed` attribute
  253. is ``True``, then accessing this attribute will raise
  254. :class:`.ResourceClosedError`.
  255. .. versionadded:: 1.4.0b2
  256. """
  257. return self._proxied.info
  258. @util.ro_non_memoized_property
  259. def _proxied(self) -> Connection:
  260. if not self.sync_connection:
  261. self._raise_for_not_started()
  262. return self.sync_connection
  263. def begin(self) -> AsyncTransaction:
  264. """Begin a transaction prior to autobegin occurring."""
  265. assert self._proxied
  266. return AsyncTransaction(self)
  267. def begin_nested(self) -> AsyncTransaction:
  268. """Begin a nested transaction and return a transaction handle."""
  269. assert self._proxied
  270. return AsyncTransaction(self, nested=True)
  271. async def invalidate(
  272. self, exception: Optional[BaseException] = None
  273. ) -> None:
  274. """Invalidate the underlying DBAPI connection associated with
  275. this :class:`_engine.Connection`.
  276. See the method :meth:`_engine.Connection.invalidate` for full
  277. detail on this method.
  278. """
  279. return await greenlet_spawn(
  280. self._proxied.invalidate, exception=exception
  281. )
  282. async def get_isolation_level(self) -> IsolationLevel:
  283. return await greenlet_spawn(self._proxied.get_isolation_level)
  284. def in_transaction(self) -> bool:
  285. """Return True if a transaction is in progress."""
  286. return self._proxied.in_transaction()
  287. def in_nested_transaction(self) -> bool:
  288. """Return True if a transaction is in progress.
  289. .. versionadded:: 1.4.0b2
  290. """
  291. return self._proxied.in_nested_transaction()
  292. def get_transaction(self) -> Optional[AsyncTransaction]:
  293. """Return an :class:`.AsyncTransaction` representing the current
  294. transaction, if any.
  295. This makes use of the underlying synchronous connection's
  296. :meth:`_engine.Connection.get_transaction` method to get the current
  297. :class:`_engine.Transaction`, which is then proxied in a new
  298. :class:`.AsyncTransaction` object.
  299. .. versionadded:: 1.4.0b2
  300. """
  301. trans = self._proxied.get_transaction()
  302. if trans is not None:
  303. return AsyncTransaction._retrieve_proxy_for_target(trans)
  304. else:
  305. return None
  306. def get_nested_transaction(self) -> Optional[AsyncTransaction]:
  307. """Return an :class:`.AsyncTransaction` representing the current
  308. nested (savepoint) transaction, if any.
  309. This makes use of the underlying synchronous connection's
  310. :meth:`_engine.Connection.get_nested_transaction` method to get the
  311. current :class:`_engine.Transaction`, which is then proxied in a new
  312. :class:`.AsyncTransaction` object.
  313. .. versionadded:: 1.4.0b2
  314. """
  315. trans = self._proxied.get_nested_transaction()
  316. if trans is not None:
  317. return AsyncTransaction._retrieve_proxy_for_target(trans)
  318. else:
  319. return None
  320. @overload
  321. async def execution_options(
  322. self,
  323. *,
  324. compiled_cache: Optional[CompiledCacheType] = ...,
  325. logging_token: str = ...,
  326. isolation_level: IsolationLevel = ...,
  327. no_parameters: bool = False,
  328. stream_results: bool = False,
  329. max_row_buffer: int = ...,
  330. yield_per: int = ...,
  331. insertmanyvalues_page_size: int = ...,
  332. schema_translate_map: Optional[SchemaTranslateMapType] = ...,
  333. **opt: Any,
  334. ) -> AsyncConnection:
  335. ...
  336. @overload
  337. async def execution_options(self, **opt: Any) -> AsyncConnection:
  338. ...
  339. async def execution_options(self, **opt: Any) -> AsyncConnection:
  340. r"""Set non-SQL options for the connection which take effect
  341. during execution.
  342. This returns this :class:`_asyncio.AsyncConnection` object with
  343. the new options added.
  344. See :meth:`_engine.Connection.execution_options` for full details
  345. on this method.
  346. """
  347. conn = self._proxied
  348. c2 = await greenlet_spawn(conn.execution_options, **opt)
  349. assert c2 is conn
  350. return self
  351. async def commit(self) -> None:
  352. """Commit the transaction that is currently in progress.
  353. This method commits the current transaction if one has been started.
  354. If no transaction was started, the method has no effect, assuming
  355. the connection is in a non-invalidated state.
  356. A transaction is begun on a :class:`_engine.Connection` automatically
  357. whenever a statement is first executed, or when the
  358. :meth:`_engine.Connection.begin` method is called.
  359. """
  360. await greenlet_spawn(self._proxied.commit)
  361. async def rollback(self) -> None:
  362. """Roll back the transaction that is currently in progress.
  363. This method rolls back the current transaction if one has been started.
  364. If no transaction was started, the method has no effect. If a
  365. transaction was started and the connection is in an invalidated state,
  366. the transaction is cleared using this method.
  367. A transaction is begun on a :class:`_engine.Connection` automatically
  368. whenever a statement is first executed, or when the
  369. :meth:`_engine.Connection.begin` method is called.
  370. """
  371. await greenlet_spawn(self._proxied.rollback)
  372. async def close(self) -> None:
  373. """Close this :class:`_asyncio.AsyncConnection`.
  374. This has the effect of also rolling back the transaction if one
  375. is in place.
  376. """
  377. await greenlet_spawn(self._proxied.close)
  378. async def aclose(self) -> None:
  379. """A synonym for :meth:`_asyncio.AsyncConnection.close`.
  380. The :meth:`_asyncio.AsyncConnection.aclose` name is specifically
  381. to support the Python standard library ``@contextlib.aclosing``
  382. context manager function.
  383. .. versionadded:: 2.0.20
  384. """
  385. await self.close()
  386. async def exec_driver_sql(
  387. self,
  388. statement: str,
  389. parameters: Optional[_DBAPIAnyExecuteParams] = None,
  390. execution_options: Optional[CoreExecuteOptionsParameter] = None,
  391. ) -> CursorResult[Any]:
  392. r"""Executes a driver-level SQL string and return buffered
  393. :class:`_engine.Result`.
  394. """
  395. result = await greenlet_spawn(
  396. self._proxied.exec_driver_sql,
  397. statement,
  398. parameters,
  399. execution_options,
  400. _require_await=True,
  401. )
  402. return await _ensure_sync_result(result, self.exec_driver_sql)
  403. @overload
  404. def stream(
  405. self,
  406. statement: TypedReturnsRows[_T],
  407. parameters: Optional[_CoreAnyExecuteParams] = None,
  408. *,
  409. execution_options: Optional[CoreExecuteOptionsParameter] = None,
  410. ) -> GeneratorStartableContext[AsyncResult[_T]]:
  411. ...
  412. @overload
  413. def stream(
  414. self,
  415. statement: Executable,
  416. parameters: Optional[_CoreAnyExecuteParams] = None,
  417. *,
  418. execution_options: Optional[CoreExecuteOptionsParameter] = None,
  419. ) -> GeneratorStartableContext[AsyncResult[Any]]:
  420. ...
  421. @asyncstartablecontext
  422. async def stream(
  423. self,
  424. statement: Executable,
  425. parameters: Optional[_CoreAnyExecuteParams] = None,
  426. *,
  427. execution_options: Optional[CoreExecuteOptionsParameter] = None,
  428. ) -> AsyncIterator[AsyncResult[Any]]:
  429. """Execute a statement and return an awaitable yielding a
  430. :class:`_asyncio.AsyncResult` object.
  431. E.g.::
  432. result = await conn.stream(stmt):
  433. async for row in result:
  434. print(f"{row}")
  435. The :meth:`.AsyncConnection.stream`
  436. method supports optional context manager use against the
  437. :class:`.AsyncResult` object, as in::
  438. async with conn.stream(stmt) as result:
  439. async for row in result:
  440. print(f"{row}")
  441. In the above pattern, the :meth:`.AsyncResult.close` method is
  442. invoked unconditionally, even if the iterator is interrupted by an
  443. exception throw. Context manager use remains optional, however,
  444. and the function may be called in either an ``async with fn():`` or
  445. ``await fn()`` style.
  446. .. versionadded:: 2.0.0b3 added context manager support
  447. :return: an awaitable object that will yield an
  448. :class:`_asyncio.AsyncResult` object.
  449. .. seealso::
  450. :meth:`.AsyncConnection.stream_scalars`
  451. """
  452. if not self.dialect.supports_server_side_cursors:
  453. raise exc.InvalidRequestError(
  454. "Cant use `stream` or `stream_scalars` with the current "
  455. "dialect since it does not support server side cursors."
  456. )
  457. result = await greenlet_spawn(
  458. self._proxied.execute,
  459. statement,
  460. parameters,
  461. execution_options=util.EMPTY_DICT.merge_with(
  462. execution_options, {"stream_results": True}
  463. ),
  464. _require_await=True,
  465. )
  466. assert result.context._is_server_side
  467. ar = AsyncResult(result)
  468. try:
  469. yield ar
  470. except GeneratorExit:
  471. pass
  472. else:
  473. task = asyncio.create_task(ar.close())
  474. await asyncio.shield(task)
  475. @overload
  476. async def execute(
  477. self,
  478. statement: TypedReturnsRows[_T],
  479. parameters: Optional[_CoreAnyExecuteParams] = None,
  480. *,
  481. execution_options: Optional[CoreExecuteOptionsParameter] = None,
  482. ) -> CursorResult[_T]:
  483. ...
  484. @overload
  485. async def execute(
  486. self,
  487. statement: Executable,
  488. parameters: Optional[_CoreAnyExecuteParams] = None,
  489. *,
  490. execution_options: Optional[CoreExecuteOptionsParameter] = None,
  491. ) -> CursorResult[Any]:
  492. ...
  493. async def execute(
  494. self,
  495. statement: Executable,
  496. parameters: Optional[_CoreAnyExecuteParams] = None,
  497. *,
  498. execution_options: Optional[CoreExecuteOptionsParameter] = None,
  499. ) -> CursorResult[Any]:
  500. r"""Executes a SQL statement construct and return a buffered
  501. :class:`_engine.Result`.
  502. :param object: The statement to be executed. This is always
  503. an object that is in both the :class:`_expression.ClauseElement` and
  504. :class:`_expression.Executable` hierarchies, including:
  505. * :class:`_expression.Select`
  506. * :class:`_expression.Insert`, :class:`_expression.Update`,
  507. :class:`_expression.Delete`
  508. * :class:`_expression.TextClause` and
  509. :class:`_expression.TextualSelect`
  510. * :class:`_schema.DDL` and objects which inherit from
  511. :class:`_schema.ExecutableDDLElement`
  512. :param parameters: parameters which will be bound into the statement.
  513. This may be either a dictionary of parameter names to values,
  514. or a mutable sequence (e.g. a list) of dictionaries. When a
  515. list of dictionaries is passed, the underlying statement execution
  516. will make use of the DBAPI ``cursor.executemany()`` method.
  517. When a single dictionary is passed, the DBAPI ``cursor.execute()``
  518. method will be used.
  519. :param execution_options: optional dictionary of execution options,
  520. which will be associated with the statement execution. This
  521. dictionary can provide a subset of the options that are accepted
  522. by :meth:`_engine.Connection.execution_options`.
  523. :return: a :class:`_engine.Result` object.
  524. """
  525. result = await greenlet_spawn(
  526. self._proxied.execute,
  527. statement,
  528. parameters,
  529. execution_options=execution_options,
  530. _require_await=True,
  531. )
  532. return await _ensure_sync_result(result, self.execute)
  533. @overload
  534. async def scalar(
  535. self,
  536. statement: TypedReturnsRows[Tuple[_T]],
  537. parameters: Optional[_CoreSingleExecuteParams] = None,
  538. *,
  539. execution_options: Optional[CoreExecuteOptionsParameter] = None,
  540. ) -> Optional[_T]:
  541. ...
  542. @overload
  543. async def scalar(
  544. self,
  545. statement: Executable,
  546. parameters: Optional[_CoreSingleExecuteParams] = None,
  547. *,
  548. execution_options: Optional[CoreExecuteOptionsParameter] = None,
  549. ) -> Any:
  550. ...
  551. async def scalar(
  552. self,
  553. statement: Executable,
  554. parameters: Optional[_CoreSingleExecuteParams] = None,
  555. *,
  556. execution_options: Optional[CoreExecuteOptionsParameter] = None,
  557. ) -> Any:
  558. r"""Executes a SQL statement construct and returns a scalar object.
  559. This method is shorthand for invoking the
  560. :meth:`_engine.Result.scalar` method after invoking the
  561. :meth:`_engine.Connection.execute` method. Parameters are equivalent.
  562. :return: a scalar Python value representing the first column of the
  563. first row returned.
  564. """
  565. result = await self.execute(
  566. statement, parameters, execution_options=execution_options
  567. )
  568. return result.scalar()
  569. @overload
  570. async def scalars(
  571. self,
  572. statement: TypedReturnsRows[Tuple[_T]],
  573. parameters: Optional[_CoreAnyExecuteParams] = None,
  574. *,
  575. execution_options: Optional[CoreExecuteOptionsParameter] = None,
  576. ) -> ScalarResult[_T]:
  577. ...
  578. @overload
  579. async def scalars(
  580. self,
  581. statement: Executable,
  582. parameters: Optional[_CoreAnyExecuteParams] = None,
  583. *,
  584. execution_options: Optional[CoreExecuteOptionsParameter] = None,
  585. ) -> ScalarResult[Any]:
  586. ...
  587. async def scalars(
  588. self,
  589. statement: Executable,
  590. parameters: Optional[_CoreAnyExecuteParams] = None,
  591. *,
  592. execution_options: Optional[CoreExecuteOptionsParameter] = None,
  593. ) -> ScalarResult[Any]:
  594. r"""Executes a SQL statement construct and returns a scalar objects.
  595. This method is shorthand for invoking the
  596. :meth:`_engine.Result.scalars` method after invoking the
  597. :meth:`_engine.Connection.execute` method. Parameters are equivalent.
  598. :return: a :class:`_engine.ScalarResult` object.
  599. .. versionadded:: 1.4.24
  600. """
  601. result = await self.execute(
  602. statement, parameters, execution_options=execution_options
  603. )
  604. return result.scalars()
  605. @overload
  606. def stream_scalars(
  607. self,
  608. statement: TypedReturnsRows[Tuple[_T]],
  609. parameters: Optional[_CoreSingleExecuteParams] = None,
  610. *,
  611. execution_options: Optional[CoreExecuteOptionsParameter] = None,
  612. ) -> GeneratorStartableContext[AsyncScalarResult[_T]]:
  613. ...
  614. @overload
  615. def stream_scalars(
  616. self,
  617. statement: Executable,
  618. parameters: Optional[_CoreSingleExecuteParams] = None,
  619. *,
  620. execution_options: Optional[CoreExecuteOptionsParameter] = None,
  621. ) -> GeneratorStartableContext[AsyncScalarResult[Any]]:
  622. ...
  623. @asyncstartablecontext
  624. async def stream_scalars(
  625. self,
  626. statement: Executable,
  627. parameters: Optional[_CoreSingleExecuteParams] = None,
  628. *,
  629. execution_options: Optional[CoreExecuteOptionsParameter] = None,
  630. ) -> AsyncIterator[AsyncScalarResult[Any]]:
  631. r"""Execute a statement and return an awaitable yielding a
  632. :class:`_asyncio.AsyncScalarResult` object.
  633. E.g.::
  634. result = await conn.stream_scalars(stmt)
  635. async for scalar in result:
  636. print(f"{scalar}")
  637. This method is shorthand for invoking the
  638. :meth:`_engine.AsyncResult.scalars` method after invoking the
  639. :meth:`_engine.Connection.stream` method. Parameters are equivalent.
  640. The :meth:`.AsyncConnection.stream_scalars`
  641. method supports optional context manager use against the
  642. :class:`.AsyncScalarResult` object, as in::
  643. async with conn.stream_scalars(stmt) as result:
  644. async for scalar in result:
  645. print(f"{scalar}")
  646. In the above pattern, the :meth:`.AsyncScalarResult.close` method is
  647. invoked unconditionally, even if the iterator is interrupted by an
  648. exception throw. Context manager use remains optional, however,
  649. and the function may be called in either an ``async with fn():`` or
  650. ``await fn()`` style.
  651. .. versionadded:: 2.0.0b3 added context manager support
  652. :return: an awaitable object that will yield an
  653. :class:`_asyncio.AsyncScalarResult` object.
  654. .. versionadded:: 1.4.24
  655. .. seealso::
  656. :meth:`.AsyncConnection.stream`
  657. """
  658. async with self.stream(
  659. statement, parameters, execution_options=execution_options
  660. ) as result:
  661. yield result.scalars()
  662. async def run_sync(
  663. self, fn: Callable[..., _T], *arg: Any, **kw: Any
  664. ) -> _T:
  665. """Invoke the given synchronous (i.e. not async) callable,
  666. passing a synchronous-style :class:`_engine.Connection` as the first
  667. argument.
  668. This method allows traditional synchronous SQLAlchemy functions to
  669. run within the context of an asyncio application.
  670. E.g.::
  671. def do_something_with_core(conn: Connection, arg1: int, arg2: str) -> str:
  672. '''A synchronous function that does not require awaiting
  673. :param conn: a Core SQLAlchemy Connection, used synchronously
  674. :return: an optional return value is supported
  675. '''
  676. conn.execute(
  677. some_table.insert().values(int_col=arg1, str_col=arg2)
  678. )
  679. return "success"
  680. async def do_something_async(async_engine: AsyncEngine) -> None:
  681. '''an async function that uses awaiting'''
  682. async with async_engine.begin() as async_conn:
  683. # run do_something_with_core() with a sync-style
  684. # Connection, proxied into an awaitable
  685. return_code = await async_conn.run_sync(do_something_with_core, 5, "strval")
  686. print(return_code)
  687. This method maintains the asyncio event loop all the way through
  688. to the database connection by running the given callable in a
  689. specially instrumented greenlet.
  690. The most rudimentary use of :meth:`.AsyncConnection.run_sync` is to
  691. invoke methods such as :meth:`_schema.MetaData.create_all`, given
  692. an :class:`.AsyncConnection` that needs to be provided to
  693. :meth:`_schema.MetaData.create_all` as a :class:`_engine.Connection`
  694. object::
  695. # run metadata.create_all(conn) with a sync-style Connection,
  696. # proxied into an awaitable
  697. with async_engine.begin() as conn:
  698. await conn.run_sync(metadata.create_all)
  699. .. note::
  700. The provided callable is invoked inline within the asyncio event
  701. loop, and will block on traditional IO calls. IO within this
  702. callable should only call into SQLAlchemy's asyncio database
  703. APIs which will be properly adapted to the greenlet context.
  704. .. seealso::
  705. :meth:`.AsyncSession.run_sync`
  706. :ref:`session_run_sync`
  707. """ # noqa: E501
  708. return await greenlet_spawn(fn, self._proxied, *arg, **kw)
  709. def __await__(self) -> Generator[Any, None, AsyncConnection]:
  710. return self.start().__await__()
  711. async def __aexit__(self, type_: Any, value: Any, traceback: Any) -> None:
  712. task = asyncio.create_task(self.close())
  713. await asyncio.shield(task)
  714. # START PROXY METHODS AsyncConnection
  715. # code within this block is **programmatically,
  716. # statically generated** by tools/generate_proxy_methods.py
  717. @property
  718. def closed(self) -> Any:
  719. r"""Return True if this connection is closed.
  720. .. container:: class_bases
  721. Proxied for the :class:`_engine.Connection` class
  722. on behalf of the :class:`_asyncio.AsyncConnection` class.
  723. """ # noqa: E501
  724. return self._proxied.closed
  725. @property
  726. def invalidated(self) -> Any:
  727. r"""Return True if this connection was invalidated.
  728. .. container:: class_bases
  729. Proxied for the :class:`_engine.Connection` class
  730. on behalf of the :class:`_asyncio.AsyncConnection` class.
  731. This does not indicate whether or not the connection was
  732. invalidated at the pool level, however
  733. """ # noqa: E501
  734. return self._proxied.invalidated
  735. @property
  736. def dialect(self) -> Any:
  737. r"""Proxy for the :attr:`_engine.Connection.dialect` attribute
  738. on behalf of the :class:`_asyncio.AsyncConnection` class.
  739. """ # noqa: E501
  740. return self._proxied.dialect
  741. @dialect.setter
  742. def dialect(self, attr: Any) -> None:
  743. self._proxied.dialect = attr
  744. @property
  745. def default_isolation_level(self) -> Any:
  746. r"""The initial-connection time isolation level associated with the
  747. :class:`_engine.Dialect` in use.
  748. .. container:: class_bases
  749. Proxied for the :class:`_engine.Connection` class
  750. on behalf of the :class:`_asyncio.AsyncConnection` class.
  751. This value is independent of the
  752. :paramref:`.Connection.execution_options.isolation_level` and
  753. :paramref:`.Engine.execution_options.isolation_level` execution
  754. options, and is determined by the :class:`_engine.Dialect` when the
  755. first connection is created, by performing a SQL query against the
  756. database for the current isolation level before any additional commands
  757. have been emitted.
  758. Calling this accessor does not invoke any new SQL queries.
  759. .. seealso::
  760. :meth:`_engine.Connection.get_isolation_level`
  761. - view current actual isolation level
  762. :paramref:`_sa.create_engine.isolation_level`
  763. - set per :class:`_engine.Engine` isolation level
  764. :paramref:`.Connection.execution_options.isolation_level`
  765. - set per :class:`_engine.Connection` isolation level
  766. """ # noqa: E501
  767. return self._proxied.default_isolation_level
  768. # END PROXY METHODS AsyncConnection
  769. @util.create_proxy_methods(
  770. Engine,
  771. ":class:`_engine.Engine`",
  772. ":class:`_asyncio.AsyncEngine`",
  773. classmethods=[],
  774. methods=[
  775. "clear_compiled_cache",
  776. "update_execution_options",
  777. "get_execution_options",
  778. ],
  779. attributes=["url", "pool", "dialect", "engine", "name", "driver", "echo"],
  780. )
  781. class AsyncEngine(ProxyComparable[Engine], AsyncConnectable):
  782. """An asyncio proxy for a :class:`_engine.Engine`.
  783. :class:`_asyncio.AsyncEngine` is acquired using the
  784. :func:`_asyncio.create_async_engine` function::
  785. from sqlalchemy.ext.asyncio import create_async_engine
  786. engine = create_async_engine("postgresql+asyncpg://user:pass@host/dbname")
  787. .. versionadded:: 1.4
  788. """ # noqa
  789. # AsyncEngine is a thin proxy; no state should be added here
  790. # that is not retrievable from the "sync" engine / connection, e.g.
  791. # current transaction, info, etc. It should be possible to
  792. # create a new AsyncEngine that matches this one given only the
  793. # "sync" elements.
  794. __slots__ = "sync_engine"
  795. _connection_cls: Type[AsyncConnection] = AsyncConnection
  796. sync_engine: Engine
  797. """Reference to the sync-style :class:`_engine.Engine` this
  798. :class:`_asyncio.AsyncEngine` proxies requests towards.
  799. This instance can be used as an event target.
  800. .. seealso::
  801. :ref:`asyncio_events`
  802. """
  803. def __init__(self, sync_engine: Engine):
  804. if not sync_engine.dialect.is_async:
  805. raise exc.InvalidRequestError(
  806. "The asyncio extension requires an async driver to be used. "
  807. f"The loaded {sync_engine.dialect.driver!r} is not async."
  808. )
  809. self.sync_engine = self._assign_proxied(sync_engine)
  810. @util.ro_non_memoized_property
  811. def _proxied(self) -> Engine:
  812. return self.sync_engine
  813. @classmethod
  814. def _regenerate_proxy_for_target(cls, target: Engine) -> AsyncEngine:
  815. return AsyncEngine(target)
  816. @contextlib.asynccontextmanager
  817. async def begin(self) -> AsyncIterator[AsyncConnection]:
  818. """Return a context manager which when entered will deliver an
  819. :class:`_asyncio.AsyncConnection` with an
  820. :class:`_asyncio.AsyncTransaction` established.
  821. E.g.::
  822. async with async_engine.begin() as conn:
  823. await conn.execute(
  824. text("insert into table (x, y, z) values (1, 2, 3)")
  825. )
  826. await conn.execute(text("my_special_procedure(5)"))
  827. """
  828. conn = self.connect()
  829. async with conn:
  830. async with conn.begin():
  831. yield conn
  832. def connect(self) -> AsyncConnection:
  833. """Return an :class:`_asyncio.AsyncConnection` object.
  834. The :class:`_asyncio.AsyncConnection` will procure a database
  835. connection from the underlying connection pool when it is entered
  836. as an async context manager::
  837. async with async_engine.connect() as conn:
  838. result = await conn.execute(select(user_table))
  839. The :class:`_asyncio.AsyncConnection` may also be started outside of a
  840. context manager by invoking its :meth:`_asyncio.AsyncConnection.start`
  841. method.
  842. """
  843. return self._connection_cls(self)
  844. async def raw_connection(self) -> PoolProxiedConnection:
  845. """Return a "raw" DBAPI connection from the connection pool.
  846. .. seealso::
  847. :ref:`dbapi_connections`
  848. """
  849. return await greenlet_spawn(self.sync_engine.raw_connection)
  850. @overload
  851. def execution_options(
  852. self,
  853. *,
  854. compiled_cache: Optional[CompiledCacheType] = ...,
  855. logging_token: str = ...,
  856. isolation_level: IsolationLevel = ...,
  857. insertmanyvalues_page_size: int = ...,
  858. schema_translate_map: Optional[SchemaTranslateMapType] = ...,
  859. **opt: Any,
  860. ) -> AsyncEngine:
  861. ...
  862. @overload
  863. def execution_options(self, **opt: Any) -> AsyncEngine:
  864. ...
  865. def execution_options(self, **opt: Any) -> AsyncEngine:
  866. """Return a new :class:`_asyncio.AsyncEngine` that will provide
  867. :class:`_asyncio.AsyncConnection` objects with the given execution
  868. options.
  869. Proxied from :meth:`_engine.Engine.execution_options`. See that
  870. method for details.
  871. """
  872. return AsyncEngine(self.sync_engine.execution_options(**opt))
  873. async def dispose(self, close: bool = True) -> None:
  874. """Dispose of the connection pool used by this
  875. :class:`_asyncio.AsyncEngine`.
  876. :param close: if left at its default of ``True``, has the
  877. effect of fully closing all **currently checked in**
  878. database connections. Connections that are still checked out
  879. will **not** be closed, however they will no longer be associated
  880. with this :class:`_engine.Engine`,
  881. so when they are closed individually, eventually the
  882. :class:`_pool.Pool` which they are associated with will
  883. be garbage collected and they will be closed out fully, if
  884. not already closed on checkin.
  885. If set to ``False``, the previous connection pool is de-referenced,
  886. and otherwise not touched in any way.
  887. .. seealso::
  888. :meth:`_engine.Engine.dispose`
  889. """
  890. await greenlet_spawn(self.sync_engine.dispose, close=close)
  891. # START PROXY METHODS AsyncEngine
  892. # code within this block is **programmatically,
  893. # statically generated** by tools/generate_proxy_methods.py
  894. def clear_compiled_cache(self) -> None:
  895. r"""Clear the compiled cache associated with the dialect.
  896. .. container:: class_bases
  897. Proxied for the :class:`_engine.Engine` class on
  898. behalf of the :class:`_asyncio.AsyncEngine` class.
  899. This applies **only** to the built-in cache that is established
  900. via the :paramref:`_engine.create_engine.query_cache_size` parameter.
  901. It will not impact any dictionary caches that were passed via the
  902. :paramref:`.Connection.execution_options.query_cache` parameter.
  903. .. versionadded:: 1.4
  904. """ # noqa: E501
  905. return self._proxied.clear_compiled_cache()
  906. def update_execution_options(self, **opt: Any) -> None:
  907. r"""Update the default execution_options dictionary
  908. of this :class:`_engine.Engine`.
  909. .. container:: class_bases
  910. Proxied for the :class:`_engine.Engine` class on
  911. behalf of the :class:`_asyncio.AsyncEngine` class.
  912. The given keys/values in \**opt are added to the
  913. default execution options that will be used for
  914. all connections. The initial contents of this dictionary
  915. can be sent via the ``execution_options`` parameter
  916. to :func:`_sa.create_engine`.
  917. .. seealso::
  918. :meth:`_engine.Connection.execution_options`
  919. :meth:`_engine.Engine.execution_options`
  920. """ # noqa: E501
  921. return self._proxied.update_execution_options(**opt)
  922. def get_execution_options(self) -> _ExecuteOptions:
  923. r"""Get the non-SQL options which will take effect during execution.
  924. .. container:: class_bases
  925. Proxied for the :class:`_engine.Engine` class on
  926. behalf of the :class:`_asyncio.AsyncEngine` class.
  927. .. versionadded: 1.3
  928. .. seealso::
  929. :meth:`_engine.Engine.execution_options`
  930. """ # noqa: E501
  931. return self._proxied.get_execution_options()
  932. @property
  933. def url(self) -> URL:
  934. r"""Proxy for the :attr:`_engine.Engine.url` attribute
  935. on behalf of the :class:`_asyncio.AsyncEngine` class.
  936. """ # noqa: E501
  937. return self._proxied.url
  938. @url.setter
  939. def url(self, attr: URL) -> None:
  940. self._proxied.url = attr
  941. @property
  942. def pool(self) -> Pool:
  943. r"""Proxy for the :attr:`_engine.Engine.pool` attribute
  944. on behalf of the :class:`_asyncio.AsyncEngine` class.
  945. """ # noqa: E501
  946. return self._proxied.pool
  947. @pool.setter
  948. def pool(self, attr: Pool) -> None:
  949. self._proxied.pool = attr
  950. @property
  951. def dialect(self) -> Dialect:
  952. r"""Proxy for the :attr:`_engine.Engine.dialect` attribute
  953. on behalf of the :class:`_asyncio.AsyncEngine` class.
  954. """ # noqa: E501
  955. return self._proxied.dialect
  956. @dialect.setter
  957. def dialect(self, attr: Dialect) -> None:
  958. self._proxied.dialect = attr
  959. @property
  960. def engine(self) -> Any:
  961. r"""Returns this :class:`.Engine`.
  962. .. container:: class_bases
  963. Proxied for the :class:`_engine.Engine` class
  964. on behalf of the :class:`_asyncio.AsyncEngine` class.
  965. Used for legacy schemes that accept :class:`.Connection` /
  966. :class:`.Engine` objects within the same variable.
  967. """ # noqa: E501
  968. return self._proxied.engine
  969. @property
  970. def name(self) -> Any:
  971. r"""String name of the :class:`~sqlalchemy.engine.interfaces.Dialect`
  972. in use by this :class:`Engine`.
  973. .. container:: class_bases
  974. Proxied for the :class:`_engine.Engine` class
  975. on behalf of the :class:`_asyncio.AsyncEngine` class.
  976. """ # noqa: E501
  977. return self._proxied.name
  978. @property
  979. def driver(self) -> Any:
  980. r"""Driver name of the :class:`~sqlalchemy.engine.interfaces.Dialect`
  981. in use by this :class:`Engine`.
  982. .. container:: class_bases
  983. Proxied for the :class:`_engine.Engine` class
  984. on behalf of the :class:`_asyncio.AsyncEngine` class.
  985. """ # noqa: E501
  986. return self._proxied.driver
  987. @property
  988. def echo(self) -> Any:
  989. r"""When ``True``, enable log output for this element.
  990. .. container:: class_bases
  991. Proxied for the :class:`_engine.Engine` class
  992. on behalf of the :class:`_asyncio.AsyncEngine` class.
  993. This has the effect of setting the Python logging level for the namespace
  994. of this element's class and object reference. A value of boolean ``True``
  995. indicates that the loglevel ``logging.INFO`` will be set for the logger,
  996. whereas the string value ``debug`` will set the loglevel to
  997. ``logging.DEBUG``.
  998. """ # noqa: E501
  999. return self._proxied.echo
  1000. @echo.setter
  1001. def echo(self, attr: Any) -> None:
  1002. self._proxied.echo = attr
  1003. # END PROXY METHODS AsyncEngine
  1004. class AsyncTransaction(
  1005. ProxyComparable[Transaction], StartableContext["AsyncTransaction"]
  1006. ):
  1007. """An asyncio proxy for a :class:`_engine.Transaction`."""
  1008. __slots__ = ("connection", "sync_transaction", "nested")
  1009. sync_transaction: Optional[Transaction]
  1010. connection: AsyncConnection
  1011. nested: bool
  1012. def __init__(self, connection: AsyncConnection, nested: bool = False):
  1013. self.connection = connection
  1014. self.sync_transaction = None
  1015. self.nested = nested
  1016. @classmethod
  1017. def _regenerate_proxy_for_target(
  1018. cls, target: Transaction
  1019. ) -> AsyncTransaction:
  1020. sync_connection = target.connection
  1021. sync_transaction = target
  1022. nested = isinstance(target, NestedTransaction)
  1023. async_connection = AsyncConnection._retrieve_proxy_for_target(
  1024. sync_connection
  1025. )
  1026. assert async_connection is not None
  1027. obj = cls.__new__(cls)
  1028. obj.connection = async_connection
  1029. obj.sync_transaction = obj._assign_proxied(sync_transaction)
  1030. obj.nested = nested
  1031. return obj
  1032. @util.ro_non_memoized_property
  1033. def _proxied(self) -> Transaction:
  1034. if not self.sync_transaction:
  1035. self._raise_for_not_started()
  1036. return self.sync_transaction
  1037. @property
  1038. def is_valid(self) -> bool:
  1039. return self._proxied.is_valid
  1040. @property
  1041. def is_active(self) -> bool:
  1042. return self._proxied.is_active
  1043. async def close(self) -> None:
  1044. """Close this :class:`.AsyncTransaction`.
  1045. If this transaction is the base transaction in a begin/commit
  1046. nesting, the transaction will rollback(). Otherwise, the
  1047. method returns.
  1048. This is used to cancel a Transaction without affecting the scope of
  1049. an enclosing transaction.
  1050. """
  1051. await greenlet_spawn(self._proxied.close)
  1052. async def rollback(self) -> None:
  1053. """Roll back this :class:`.AsyncTransaction`."""
  1054. await greenlet_spawn(self._proxied.rollback)
  1055. async def commit(self) -> None:
  1056. """Commit this :class:`.AsyncTransaction`."""
  1057. await greenlet_spawn(self._proxied.commit)
  1058. async def start(self, is_ctxmanager: bool = False) -> AsyncTransaction:
  1059. """Start this :class:`_asyncio.AsyncTransaction` object's context
  1060. outside of using a Python ``with:`` block.
  1061. """
  1062. self.sync_transaction = self._assign_proxied(
  1063. await greenlet_spawn(
  1064. self.connection._proxied.begin_nested
  1065. if self.nested
  1066. else self.connection._proxied.begin
  1067. )
  1068. )
  1069. if is_ctxmanager:
  1070. self.sync_transaction.__enter__()
  1071. return self
  1072. async def __aexit__(self, type_: Any, value: Any, traceback: Any) -> None:
  1073. await greenlet_spawn(self._proxied.__exit__, type_, value, traceback)
  1074. @overload
  1075. def _get_sync_engine_or_connection(async_engine: AsyncEngine) -> Engine:
  1076. ...
  1077. @overload
  1078. def _get_sync_engine_or_connection(
  1079. async_engine: AsyncConnection,
  1080. ) -> Connection:
  1081. ...
  1082. def _get_sync_engine_or_connection(
  1083. async_engine: Union[AsyncEngine, AsyncConnection]
  1084. ) -> Union[Engine, Connection]:
  1085. if isinstance(async_engine, AsyncConnection):
  1086. return async_engine._proxied
  1087. try:
  1088. return async_engine.sync_engine
  1089. except AttributeError as e:
  1090. raise exc.ArgumentError(
  1091. "AsyncEngine expected, got %r" % async_engine
  1092. ) from e
  1093. @inspection._inspects(AsyncConnection)
  1094. def _no_insp_for_async_conn_yet(
  1095. subject: AsyncConnection, # noqa: U100
  1096. ) -> NoReturn:
  1097. raise exc.NoInspectionAvailable(
  1098. "Inspection on an AsyncConnection is currently not supported. "
  1099. "Please use ``run_sync`` to pass a callable where it's possible "
  1100. "to call ``inspect`` on the passed connection.",
  1101. code="xd3s",
  1102. )
  1103. @inspection._inspects(AsyncEngine)
  1104. def _no_insp_for_async_engine_xyet(
  1105. subject: AsyncEngine, # noqa: U100
  1106. ) -> NoReturn:
  1107. raise exc.NoInspectionAvailable(
  1108. "Inspection on an AsyncEngine is currently not supported. "
  1109. "Please obtain a connection then use ``conn.run_sync`` to pass a "
  1110. "callable where it's possible to call ``inspect`` on the "
  1111. "passed connection.",
  1112. code="xd3s",
  1113. )