engine.py 47 KB

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