codekingpro/portable-devtools
114k
1# ext/asyncio/scoping.py
2# Copyright (C) 2005-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
8from __future__ import annotations
9
10from typing import Any
11from typing import Callable
12from typing import Generic
13from typing import Iterable
14from typing import Iterator
15from typing import Optional
16from typing import overload
17from typing import Sequence
18from typing import Tuple
19from typing import Type
20from typing import TYPE_CHECKING
21from typing import TypeVar
22from typing import Union
23
24from .session import _AS
25from .session import async_sessionmaker
26from .session import AsyncSession
27from ... import exc as sa_exc
28from ... import util
29from ...orm.session import Session
30from ...util import create_proxy_methods
31from ...util import ScopedRegistry
32from ...util import warn
33from ...util import warn_deprecated
34
35if TYPE_CHECKING:
36 from .engine import AsyncConnection
37 from .result import AsyncResult
38 from .result import AsyncScalarResult
39 from .session import AsyncSessionTransaction
40 from ...engine import Connection
41 from ...engine import CursorResult
42 from ...engine import Engine
43 from ...engine import Result
44 from ...engine import Row
45 from ...engine import RowMapping
46 from ...engine.interfaces import _CoreAnyExecuteParams
47 from ...engine.interfaces import CoreExecuteOptionsParameter
48 from ...engine.result import ScalarResult
49 from ...orm._typing import _IdentityKeyType
50 from ...orm._typing import _O
51 from ...orm._typing import OrmExecuteOptionsParameter
52 from ...orm.interfaces import ORMOption
53 from ...orm.session import _BindArguments
54 from ...orm.session import _EntityBindKey
55 from ...orm.session import _PKIdentityArgument
56 from ...orm.session import _SessionBind
57 from ...sql.base import Executable
58 from ...sql.dml import UpdateBase
59 from ...sql.elements import ClauseElement
60 from ...sql.selectable import ForUpdateParameter
61 from ...sql.selectable import TypedReturnsRows
62
63_T = TypeVar("_T", bound=Any)
64
65
66@create_proxy_methods(
67 AsyncSession,
68 ":class:`_asyncio.AsyncSession`",
69 ":class:`_asyncio.scoping.async_scoped_session`",
70 classmethods=["close_all", "object_session", "identity_key"],
71 methods=[
72 "__contains__",
73 "__iter__",
74 "aclose",
75 "add",
76 "add_all",
77 "begin",
78 "begin_nested",
79 "close",
80 "reset",
81 "commit",
82 "connection",
83 "delete",
84 "execute",
85 "expire",
86 "expire_all",
87 "expunge",
88 "expunge_all",
89 "flush",
90 "get_bind",
91 "is_modified",
92 "invalidate",
93 "merge",
94 "refresh",
95 "rollback",
96 "scalar",
97 "scalars",
98 "get",
99 "get_one",
100 "stream",
101 "stream_scalars",
102 ],
103 attributes=[
104 "bind",
105 "dirty",
106 "deleted",
107 "new",
108 "identity_map",
109 "is_active",
110 "autoflush",
111 "no_autoflush",
112 "info",
113 ],
114 use_intermediate_variable=["get"],
115)
116class async_scoped_session(Generic[_AS]):
117 """Provides scoped management of :class:`.AsyncSession` objects.
118
119 See the section :ref:`asyncio_scoped_session` for usage details.
120
121 .. versionadded:: 1.4.19
122
123
124 """
125
126 _support_async = True
127
128 session_factory: async_sessionmaker[_AS]
129 """The `session_factory` provided to `__init__` is stored in this
130 attribute and may be accessed at a later time. This can be useful when
131 a new non-scoped :class:`.AsyncSession` is needed."""
132
133 registry: ScopedRegistry[_AS]
134
135 def __init__(
136 self,
137 session_factory: async_sessionmaker[_AS],
138 scopefunc: Callable[[], Any],
139 ):
140 """Construct a new :class:`_asyncio.async_scoped_session`.
141
142 :param session_factory: a factory to create new :class:`_asyncio.AsyncSession`
143 instances. This is usually, but not necessarily, an instance
144 of :class:`_asyncio.async_sessionmaker`.
145
146 :param scopefunc: function which defines
147 the current scope. A function such as ``asyncio.current_task``
148 may be useful here.
149
150 """ # noqa: E501
151
152 self.session_factory = session_factory
153 self.registry = ScopedRegistry(session_factory, scopefunc)
154
155 @property
156 def _proxied(self) -> _AS:
157 return self.registry()
158
159 def __call__(self, **kw: Any) -> _AS:
160 r"""Return the current :class:`.AsyncSession`, creating it
161 using the :attr:`.scoped_session.session_factory` if not present.
162
163 :param \**kw: Keyword arguments will be passed to the
164 :attr:`.scoped_session.session_factory` callable, if an existing
165 :class:`.AsyncSession` is not present. If the
166 :class:`.AsyncSession` is present
167 and keyword arguments have been passed,
168 :exc:`~sqlalchemy.exc.InvalidRequestError` is raised.
169
170 """
171 if kw:
172 if self.registry.has():
173 raise sa_exc.InvalidRequestError(
174 "Scoped session is already present; "
175 "no new arguments may be specified."
176 )
177 else:
178 sess = self.session_factory(**kw)
179 self.registry.set(sess)
180 else:
181 sess = self.registry()
182 if not self._support_async and sess._is_asyncio:
183 warn_deprecated(
184 "Using `scoped_session` with asyncio is deprecated and "
185 "will raise an error in a future version. "
186 "Please use `async_scoped_session` instead.",
187 "1.4.23",
188 )
189 return sess
190
191 def configure(self, **kwargs: Any) -> None:
192 """reconfigure the :class:`.sessionmaker` used by this
193 :class:`.scoped_session`.
194
195 See :meth:`.sessionmaker.configure`.
196
197 """
198
199 if self.registry.has():
200 warn(
201 "At least one scoped session is already present. "
202 " configure() can not affect sessions that have "
203 "already been created."
204 )
205
206 self.session_factory.configure(**kwargs)
207
208 async def remove(self) -> None:
209 """Dispose of the current :class:`.AsyncSession`, if present.
210
211 Different from scoped_session's remove method, this method would use
212 await to wait for the close method of AsyncSession.
213
214 """
215
216 if self.registry.has():
217 await self.registry().close()
218 self.registry.clear()
219
220 # START PROXY METHODS async_scoped_session
221
222 # code within this block is **programmatically,
223 # statically generated** by tools/generate_proxy_methods.py
224
225 def __contains__(self, instance: object) -> bool:
226 r"""Return True if the instance is associated with this session.
227
228 .. container:: class_bases
229
230 Proxied for the :class:`_asyncio.AsyncSession` class on
231 behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
232
233 .. container:: class_bases
234
235 Proxied for the :class:`_orm.Session` class on
236 behalf of the :class:`_asyncio.AsyncSession` class.
237
238 The instance may be pending or persistent within the Session for a
239 result of True.
240
241
242
243 """ # noqa: E501
244
245 return self._proxied.__contains__(instance)
246
247 def __iter__(self) -> Iterator[object]:
248 r"""Iterate over all pending or persistent instances within this
249 Session.
250
251 .. container:: class_bases
252
253 Proxied for the :class:`_asyncio.AsyncSession` class on
254 behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
255
256 .. container:: class_bases
257
258 Proxied for the :class:`_orm.Session` class on
259 behalf of the :class:`_asyncio.AsyncSession` class.
260
261
262
263 """ # noqa: E501
264
265 return self._proxied.__iter__()
266
267 async def aclose(self) -> None:
268 r"""A synonym for :meth:`_asyncio.AsyncSession.close`.
269
270 .. container:: class_bases
271
272 Proxied for the :class:`_asyncio.AsyncSession` class on
273 behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
274
275 The :meth:`_asyncio.AsyncSession.aclose` name is specifically
276 to support the Python standard library ``@contextlib.aclosing``
277 context manager function.
278
279 .. versionadded:: 2.0.20
280
281
282 """ # noqa: E501
283
284 return await self._proxied.aclose()
285
286 def add(self, instance: object, _warn: bool = True) -> None:
287 r"""Place an object into this :class:`_orm.Session`.
288
289 .. container:: class_bases
290
291 Proxied for the :class:`_asyncio.AsyncSession` class on
292 behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
293
294 .. container:: class_bases
295
296 Proxied for the :class:`_orm.Session` class on
297 behalf of the :class:`_asyncio.AsyncSession` class.
298
299 Objects that are in the :term:`transient` state when passed to the
300 :meth:`_orm.Session.add` method will move to the
301 :term:`pending` state, until the next flush, at which point they
302 will move to the :term:`persistent` state.
303
304 Objects that are in the :term:`detached` state when passed to the
305 :meth:`_orm.Session.add` method will move to the :term:`persistent`
306 state directly.
307
308 If the transaction used by the :class:`_orm.Session` is rolled back,
309 objects which were transient when they were passed to
310 :meth:`_orm.Session.add` will be moved back to the
311 :term:`transient` state, and will no longer be present within this
312 :class:`_orm.Session`.
313
314 .. seealso::
315
316 :meth:`_orm.Session.add_all`
317
318 :ref:`session_adding` - at :ref:`session_basics`
319
320
321
322 """ # noqa: E501
323
324 return self._proxied.add(instance, _warn=_warn)
325
326 def add_all(self, instances: Iterable[object]) -> None:
327 r"""Add the given collection of instances to this :class:`_orm.Session`.
328
329 .. container:: class_bases
330
331 Proxied for the :class:`_asyncio.AsyncSession` class on
332 behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
333
334 .. container:: class_bases
335
336 Proxied for the :class:`_orm.Session` class on
337 behalf of the :class:`_asyncio.AsyncSession` class.
338
339 See the documentation for :meth:`_orm.Session.add` for a general
340 behavioral description.
341
342 .. seealso::
343
344 :meth:`_orm.Session.add`
345
346 :ref:`session_adding` - at :ref:`session_basics`
347
348
349
350 """ # noqa: E501
351
352 return self._proxied.add_all(instances)
353
354 def begin(self) -> AsyncSessionTransaction:
355 r"""Return an :class:`_asyncio.AsyncSessionTransaction` object.
356
357 .. container:: class_bases
358
359 Proxied for the :class:`_asyncio.AsyncSession` class on
360 behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
361
362 The underlying :class:`_orm.Session` will perform the
363 "begin" action when the :class:`_asyncio.AsyncSessionTransaction`
364 object is entered::
365
366 async with async_session.begin():
367 # .. ORM transaction is begun
368
369 Note that database IO will not normally occur when the session-level
370 transaction is begun, as database transactions begin on an
371 on-demand basis. However, the begin block is async to accommodate
372 for a :meth:`_orm.SessionEvents.after_transaction_create`
373 event hook that may perform IO.
374
375 For a general description of ORM begin, see
376 :meth:`_orm.Session.begin`.
377
378
379 """ # noqa: E501
380
381 return self._proxied.begin()
382
383 def begin_nested(self) -> AsyncSessionTransaction:
384 r"""Return an :class:`_asyncio.AsyncSessionTransaction` object
385 which will begin a "nested" transaction, e.g. SAVEPOINT.
386
387 .. container:: class_bases
388
389 Proxied for the :class:`_asyncio.AsyncSession` class on
390 behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
391
392 Behavior is the same as that of :meth:`_asyncio.AsyncSession.begin`.
393
394 For a general description of ORM begin nested, see
395 :meth:`_orm.Session.begin_nested`.
396
397 .. seealso::
398
399 :ref:`aiosqlite_serializable` - special workarounds required
400 with the SQLite asyncio driver in order for SAVEPOINT to work
401 correctly.
402
403
404 """ # noqa: E501
405
406 return self._proxied.begin_nested()
407
408 async def close(self) -> None:
409 r"""Close out the transactional resources and ORM objects used by this
410 :class:`_asyncio.AsyncSession`.
411
412 .. container:: class_bases
413
414 Proxied for the :class:`_asyncio.AsyncSession` class on
415 behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
416
417 .. seealso::
418
419 :meth:`_orm.Session.close` - main documentation for
420 "close"
421
422 :ref:`session_closing` - detail on the semantics of
423 :meth:`_asyncio.AsyncSession.close` and
424 :meth:`_asyncio.AsyncSession.reset`.
425
426
427 """ # noqa: E501
428
429 return await self._proxied.close()
430
431 async def reset(self) -> None:
432 r"""Close out the transactional resources and ORM objects used by this
433 :class:`_orm.Session`, resetting the session to its initial state.
434
435 .. container:: class_bases
436
437 Proxied for the :class:`_asyncio.AsyncSession` class on
438 behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
439
440 .. versionadded:: 2.0.22
441
442 .. seealso::
443
444 :meth:`_orm.Session.reset` - main documentation for
445 "reset"
446
447 :ref:`session_closing` - detail on the semantics of
448 :meth:`_asyncio.AsyncSession.close` and
449 :meth:`_asyncio.AsyncSession.reset`.
450
451
452 """ # noqa: E501
453
454 return await self._proxied.reset()
455
456 async def commit(self) -> None:
457 r"""Commit the current transaction in progress.
458
459 .. container:: class_bases
460
461 Proxied for the :class:`_asyncio.AsyncSession` class on
462 behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
463
464 .. seealso::
465
466 :meth:`_orm.Session.commit` - main documentation for
467 "commit"
468
469 """ # noqa: E501
470
471 return await self._proxied.commit()
472
473 async def connection(
474 self,
475 bind_arguments: Optional[_BindArguments] = None,
476 execution_options: Optional[CoreExecuteOptionsParameter] = None,
477 **kw: Any,
478 ) -> AsyncConnection:
479 r"""Return a :class:`_asyncio.AsyncConnection` object corresponding to
480 this :class:`.Session` object's transactional state.
481
482 .. container:: class_bases
483
484 Proxied for the :class:`_asyncio.AsyncSession` class on
485 behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
486
487 This method may also be used to establish execution options for the
488 database connection used by the current transaction.
489
490 .. versionadded:: 1.4.24 Added \**kw arguments which are passed
491 through to the underlying :meth:`_orm.Session.connection` method.
492
493 .. seealso::
494
495 :meth:`_orm.Session.connection` - main documentation for
496 "connection"
497
498
499 """ # noqa: E501
500
501 return await self._proxied.connection(
502 bind_arguments=bind_arguments,
503 execution_options=execution_options,
504 **kw,
505 )
506
507 async def delete(self, instance: object) -> None:
508 r"""Mark an instance as deleted.
509
510 .. container:: class_bases
511
512 Proxied for the :class:`_asyncio.AsyncSession` class on
513 behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
514
515 The database delete operation occurs upon ``flush()``.
516
517 As this operation may need to cascade along unloaded relationships,
518 it is awaitable to allow for those queries to take place.
519
520 .. seealso::
521
522 :meth:`_orm.Session.delete` - main documentation for delete
523
524
525 """ # noqa: E501
526
527 return await self._proxied.delete(instance)
528
529 @overload
530 async def execute(
531 self,
532 statement: TypedReturnsRows[_T],
533 params: Optional[_CoreAnyExecuteParams] = None,
534 *,
535 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
536 bind_arguments: Optional[_BindArguments] = None,
537 _parent_execute_state: Optional[Any] = None,
538 _add_event: Optional[Any] = None,
539 ) -> Result[_T]: ...
540
541 @overload
542 async def execute(
543 self,
544 statement: UpdateBase,
545 params: Optional[_CoreAnyExecuteParams] = None,
546 *,
547 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
548 bind_arguments: Optional[_BindArguments] = None,
549 _parent_execute_state: Optional[Any] = None,
550 _add_event: Optional[Any] = None,
551 ) -> CursorResult[Any]: ...
552
553 @overload
554 async def execute(
555 self,
556 statement: Executable,
557 params: Optional[_CoreAnyExecuteParams] = None,
558 *,
559 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
560 bind_arguments: Optional[_BindArguments] = None,
561 _parent_execute_state: Optional[Any] = None,
562 _add_event: Optional[Any] = None,
563 ) -> Result[Any]: ...
564
565 async def execute(
566 self,
567 statement: Executable,
568 params: Optional[_CoreAnyExecuteParams] = None,
569 *,
570 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
571 bind_arguments: Optional[_BindArguments] = None,
572 **kw: Any,
573 ) -> Result[Any]:
574 r"""Execute a statement and return a buffered
575 :class:`_engine.Result` object.
576
577 .. container:: class_bases
578
579 Proxied for the :class:`_asyncio.AsyncSession` class on
580 behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
581
582 .. seealso::
583
584 :meth:`_orm.Session.execute` - main documentation for execute
585
586
587 """ # noqa: E501
588
589 return await self._proxied.execute(
590 statement,
591 params=params,
592 execution_options=execution_options,
593 bind_arguments=bind_arguments,
594 **kw,
595 )
596
597 def expire(
598 self, instance: object, attribute_names: Optional[Iterable[str]] = None
599 ) -> None:
600 r"""Expire the attributes on an instance.
601
602 .. container:: class_bases
603
604 Proxied for the :class:`_asyncio.AsyncSession` class on
605 behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
606
607 .. container:: class_bases
608
609 Proxied for the :class:`_orm.Session` class on
610 behalf of the :class:`_asyncio.AsyncSession` class.
611
612 Marks the attributes of an instance as out of date. When an expired
613 attribute is next accessed, a query will be issued to the
614 :class:`.Session` object's current transactional context in order to
615 load all expired attributes for the given instance. Note that
616 a highly isolated transaction will return the same values as were
617 previously read in that same transaction, regardless of changes
618 in database state outside of that transaction.
619
620 To expire all objects in the :class:`.Session` simultaneously,
621 use :meth:`Session.expire_all`.
622
623 The :class:`.Session` object's default behavior is to
624 expire all state whenever the :meth:`Session.rollback`
625 or :meth:`Session.commit` methods are called, so that new
626 state can be loaded for the new transaction. For this reason,
627 calling :meth:`Session.expire` only makes sense for the specific
628 case that a non-ORM SQL statement was emitted in the current
629 transaction.
630
631 :param instance: The instance to be refreshed.
632 :param attribute_names: optional list of string attribute names
633 indicating a subset of attributes to be expired.
634
635 .. seealso::
636
637 :ref:`session_expire` - introductory material
638
639 :meth:`.Session.expire`
640
641 :meth:`.Session.refresh`
642
643 :meth:`_orm.Query.populate_existing`
644
645
646
647 """ # noqa: E501
648
649 return self._proxied.expire(instance, attribute_names=attribute_names)
650
651 def expire_all(self) -> None:
652 r"""Expires all persistent instances within this Session.
653
654 .. container:: class_bases
655
656 Proxied for the :class:`_asyncio.AsyncSession` class on
657 behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
658
659 .. container:: class_bases
660
661 Proxied for the :class:`_orm.Session` class on
662 behalf of the :class:`_asyncio.AsyncSession` class.
663
664 When any attributes on a persistent instance is next accessed,
665 a query will be issued using the
666 :class:`.Session` object's current transactional context in order to
667 load all expired attributes for the given instance. Note that
668 a highly isolated transaction will return the same values as were
669 previously read in that same transaction, regardless of changes
670 in database state outside of that transaction.
671
672 To expire individual objects and individual attributes
673 on those objects, use :meth:`Session.expire`.
674
675 The :class:`.Session` object's default behavior is to
676 expire all state whenever the :meth:`Session.rollback`
677 or :meth:`Session.commit` methods are called, so that new
678 state can be loaded for the new transaction. For this reason,
679 calling :meth:`Session.expire_all` is not usually needed,
680 assuming the transaction is isolated.
681
682 .. seealso::
683
684 :ref:`session_expire` - introductory material
685
686 :meth:`.Session.expire`
687
688 :meth:`.Session.refresh`
689
690 :meth:`_orm.Query.populate_existing`
691
692
693
694 """ # noqa: E501
695
696 return self._proxied.expire_all()
697
698 def expunge(self, instance: object) -> None:
699 r"""Remove the `instance` from this ``Session``.
700
701 .. container:: class_bases
702
703 Proxied for the :class:`_asyncio.AsyncSession` class on
704 behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
705
706 .. container:: class_bases
707
708 Proxied for the :class:`_orm.Session` class on
709 behalf of the :class:`_asyncio.AsyncSession` class.
710
711 This will free all internal references to the instance. Cascading
712 will be applied according to the *expunge* cascade rule.
713
714
715
716 """ # noqa: E501
717
718 return self._proxied.expunge(instance)
719
720 def expunge_all(self) -> None:
721 r"""Remove all object instances from this ``Session``.
722
723 .. container:: class_bases
724
725 Proxied for the :class:`_asyncio.AsyncSession` class on
726 behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
727
728 .. container:: class_bases
729
730 Proxied for the :class:`_orm.Session` class on
731 behalf of the :class:`_asyncio.AsyncSession` class.
732
733 This is equivalent to calling ``expunge(obj)`` on all objects in this
734 ``Session``.
735
736
737
738 """ # noqa: E501
739
740 return self._proxied.expunge_all()
741
742 async def flush(self, objects: Optional[Sequence[Any]] = None) -> None:
743 r"""Flush all the object changes to the database.
744
745 .. container:: class_bases
746
747 Proxied for the :class:`_asyncio.AsyncSession` class on
748 behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
749
750 .. seealso::
751
752 :meth:`_orm.Session.flush` - main documentation for flush
753
754
755 """ # noqa: E501
756
757 return await self._proxied.flush(objects=objects)
758
759 def get_bind(
760 self,
761 mapper: Optional[_EntityBindKey[_O]] = None,
762 clause: Optional[ClauseElement] = None,
763 bind: Optional[_SessionBind] = None,
764 **kw: Any,
765 ) -> Union[Engine, Connection]:
766 r"""Return a "bind" to which the synchronous proxied :class:`_orm.Session`
767 is bound.
768
769 .. container:: class_bases
770
771 Proxied for the :class:`_asyncio.AsyncSession` class on
772 behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
773
774 Unlike the :meth:`_orm.Session.get_bind` method, this method is
775 currently **not** used by this :class:`.AsyncSession` in any way
776 in order to resolve engines for requests.
777
778 .. note::
779
780 This method proxies directly to the :meth:`_orm.Session.get_bind`
781 method, however is currently **not** useful as an override target,
782 in contrast to that of the :meth:`_orm.Session.get_bind` method.
783 The example below illustrates how to implement custom
784 :meth:`_orm.Session.get_bind` schemes that work with
785 :class:`.AsyncSession` and :class:`.AsyncEngine`.
786
787 The pattern introduced at :ref:`session_custom_partitioning`
788 illustrates how to apply a custom bind-lookup scheme to a
789 :class:`_orm.Session` given a set of :class:`_engine.Engine` objects.
790 To apply a corresponding :meth:`_orm.Session.get_bind` implementation
791 for use with a :class:`.AsyncSession` and :class:`.AsyncEngine`
792 objects, continue to subclass :class:`_orm.Session` and apply it to
793 :class:`.AsyncSession` using
794 :paramref:`.AsyncSession.sync_session_class`. The inner method must
795 continue to return :class:`_engine.Engine` instances, which can be
796 acquired from a :class:`_asyncio.AsyncEngine` using the
797 :attr:`_asyncio.AsyncEngine.sync_engine` attribute::
798
799 # using example from "Custom Vertical Partitioning"
800
801
802 import random
803
804 from sqlalchemy.ext.asyncio import AsyncSession
805 from sqlalchemy.ext.asyncio import create_async_engine
806 from sqlalchemy.ext.asyncio import async_sessionmaker
807 from sqlalchemy.orm import Session
808
809 # construct async engines w/ async drivers
810 engines = {
811 'leader':create_async_engine("sqlite+aiosqlite:///leader.db"),
812 'other':create_async_engine("sqlite+aiosqlite:///other.db"),
813 'follower1':create_async_engine("sqlite+aiosqlite:///follower1.db"),
814 'follower2':create_async_engine("sqlite+aiosqlite:///follower2.db"),
815 }
816
817 class RoutingSession(Session):
818 def get_bind(self, mapper=None, clause=None, **kw):
819 # within get_bind(), return sync engines
820 if mapper and issubclass(mapper.class_, MyOtherClass):
821 return engines['other'].sync_engine
822 elif self._flushing or isinstance(clause, (Update, Delete)):
823 return engines['leader'].sync_engine
824 else:
825 return engines[
826 random.choice(['follower1','follower2'])
827 ].sync_engine
828
829 # apply to AsyncSession using sync_session_class
830 AsyncSessionMaker = async_sessionmaker(
831 sync_session_class=RoutingSession
832 )
833
834 The :meth:`_orm.Session.get_bind` method is called in a non-asyncio,
835 implicitly non-blocking context in the same manner as ORM event hooks
836 and functions that are invoked via :meth:`.AsyncSession.run_sync`, so
837 routines that wish to run SQL commands inside of
838 :meth:`_orm.Session.get_bind` can continue to do so using
839 blocking-style code, which will be translated to implicitly async calls
840 at the point of invoking IO on the database drivers.
841
842
843 """ # noqa: E501
844
845 return self._proxied.get_bind(
846 mapper=mapper, clause=clause, bind=bind, **kw
847 )
848
849 def is_modified(
850 self, instance: object, include_collections: bool = True
851 ) -> bool:
852 r"""Return ``True`` if the given instance has locally
853 modified attributes.
854
855 .. container:: class_bases
856
857 Proxied for the :class:`_asyncio.AsyncSession` class on
858 behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
859
860 .. container:: class_bases
861
862 Proxied for the :class:`_orm.Session` class on
863 behalf of the :class:`_asyncio.AsyncSession` class.
864
865 This method retrieves the history for each instrumented
866 attribute on the instance and performs a comparison of the current
867 value to its previously committed value, if any.
868
869 It is in effect a more expensive and accurate
870 version of checking for the given instance in the
871 :attr:`.Session.dirty` collection; a full test for
872 each attribute's net "dirty" status is performed.
873
874 E.g.::
875
876 return session.is_modified(someobject)
877
878 A few caveats to this method apply:
879
880 * Instances present in the :attr:`.Session.dirty` collection may
881 report ``False`` when tested with this method. This is because
882 the object may have received change events via attribute mutation,
883 thus placing it in :attr:`.Session.dirty`, but ultimately the state
884 is the same as that loaded from the database, resulting in no net
885 change here.
886 * Scalar attributes may not have recorded the previously set
887 value when a new value was applied, if the attribute was not loaded,
888 or was expired, at the time the new value was received - in these
889 cases, the attribute is assumed to have a change, even if there is
890 ultimately no net change against its database value. SQLAlchemy in
891 most cases does not need the "old" value when a set event occurs, so
892 it skips the expense of a SQL call if the old value isn't present,
893 based on the assumption that an UPDATE of the scalar value is
894 usually needed, and in those few cases where it isn't, is less
895 expensive on average than issuing a defensive SELECT.
896
897 The "old" value is fetched unconditionally upon set only if the
898 attribute container has the ``active_history`` flag set to ``True``.
899 This flag is set typically for primary key attributes and scalar
900 object references that are not a simple many-to-one. To set this
901 flag for any arbitrary mapped column, use the ``active_history``
902 argument with :func:`.column_property`.
903
904 :param instance: mapped instance to be tested for pending changes.
905 :param include_collections: Indicates if multivalued collections
906 should be included in the operation. Setting this to ``False`` is a
907 way to detect only local-column based properties (i.e. scalar columns
908 or many-to-one foreign keys) that would result in an UPDATE for this
909 instance upon flush.
910
911
912
913 """ # noqa: E501
914
915 return self._proxied.is_modified(
916 instance, include_collections=include_collections
917 )
918
919 async def invalidate(self) -> None:
920 r"""Close this Session, using connection invalidation.
921
922 .. container:: class_bases
923
924 Proxied for the :class:`_asyncio.AsyncSession` class on
925 behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
926
927 For a complete description, see :meth:`_orm.Session.invalidate`.
928
929 """ # noqa: E501
930
931 return await self._proxied.invalidate()
932
933 async def merge(
934 self,
935 instance: _O,
936 *,
937 load: bool = True,
938 options: Optional[Sequence[ORMOption]] = None,
939 ) -> _O:
940 r"""Copy the state of a given instance into a corresponding instance
941 within this :class:`_asyncio.AsyncSession`.
942
943 .. container:: class_bases
944
945 Proxied for the :class:`_asyncio.AsyncSession` class on
946 behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
947
948 .. seealso::
949
950 :meth:`_orm.Session.merge` - main documentation for merge
951
952
953 """ # noqa: E501
954
955 return await self._proxied.merge(instance, load=load, options=options)
956
957 async def refresh(
958 self,
959 instance: object,
960 attribute_names: Optional[Iterable[str]] = None,
961 with_for_update: ForUpdateParameter = None,
962 ) -> None:
963 r"""Expire and refresh the attributes on the given instance.
964
965 .. container:: class_bases
966
967 Proxied for the :class:`_asyncio.AsyncSession` class on
968 behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
969
970 A query will be issued to the database and all attributes will be
971 refreshed with their current database value.
972
973 This is the async version of the :meth:`_orm.Session.refresh` method.
974 See that method for a complete description of all options.
975
976 .. seealso::
977
978 :meth:`_orm.Session.refresh` - main documentation for refresh
979
980
981 """ # noqa: E501
982
983 return await self._proxied.refresh(
984 instance,
985 attribute_names=attribute_names,
986 with_for_update=with_for_update,
987 )
988
989 async def rollback(self) -> None:
990 r"""Rollback the current transaction in progress.
991
992 .. container:: class_bases
993
994 Proxied for the :class:`_asyncio.AsyncSession` class on
995 behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
996
997 .. seealso::
998
999 :meth:`_orm.Session.rollback` - main documentation for
1000 "rollback"
1001
1002 """ # noqa: E501
1003
1004 return await self._proxied.rollback()
1005
1006 @overload
1007 async def scalar(
1008 self,
1009 statement: TypedReturnsRows[Tuple[_T]],
1010 params: Optional[_CoreAnyExecuteParams] = None,
1011 *,
1012 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
1013 bind_arguments: Optional[_BindArguments] = None,
1014 **kw: Any,
1015 ) -> Optional[_T]: ...
1016
1017 @overload
1018 async def scalar(
1019 self,
1020 statement: Executable,
1021 params: Optional[_CoreAnyExecuteParams] = None,
1022 *,
1023 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
1024 bind_arguments: Optional[_BindArguments] = None,
1025 **kw: Any,
1026 ) -> Any: ...
1027
1028 async def scalar(
1029 self,
1030 statement: Executable,
1031 params: Optional[_CoreAnyExecuteParams] = None,
1032 *,
1033 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
1034 bind_arguments: Optional[_BindArguments] = None,
1035 **kw: Any,
1036 ) -> Any:
1037 r"""Execute a statement and return a scalar result.
1038
1039 .. container:: class_bases
1040
1041 Proxied for the :class:`_asyncio.AsyncSession` class on
1042 behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
1043
1044 .. seealso::
1045
1046 :meth:`_orm.Session.scalar` - main documentation for scalar
1047
1048
1049 """ # noqa: E501
1050
1051 return await self._proxied.scalar(
1052 statement,
1053 params=params,
1054 execution_options=execution_options,
1055 bind_arguments=bind_arguments,
1056 **kw,
1057 )
1058
1059 @overload
1060 async def scalars(
1061 self,
1062 statement: TypedReturnsRows[Tuple[_T]],
1063 params: Optional[_CoreAnyExecuteParams] = None,
1064 *,
1065 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
1066 bind_arguments: Optional[_BindArguments] = None,
1067 **kw: Any,
1068 ) -> ScalarResult[_T]: ...
1069
1070 @overload
1071 async def scalars(
1072 self,
1073 statement: Executable,
1074 params: Optional[_CoreAnyExecuteParams] = None,
1075 *,
1076 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
1077 bind_arguments: Optional[_BindArguments] = None,
1078 **kw: Any,
1079 ) -> ScalarResult[Any]: ...
1080
1081 async def scalars(
1082 self,
1083 statement: Executable,
1084 params: Optional[_CoreAnyExecuteParams] = None,
1085 *,
1086 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
1087 bind_arguments: Optional[_BindArguments] = None,
1088 **kw: Any,
1089 ) -> ScalarResult[Any]:
1090 r"""Execute a statement and return scalar results.
1091
1092 .. container:: class_bases
1093
1094 Proxied for the :class:`_asyncio.AsyncSession` class on
1095 behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
1096
1097 :return: a :class:`_result.ScalarResult` object
1098
1099 .. versionadded:: 1.4.24 Added :meth:`_asyncio.AsyncSession.scalars`
1100
1101 .. versionadded:: 1.4.26 Added
1102 :meth:`_asyncio.async_scoped_session.scalars`
1103
1104 .. seealso::
1105
1106 :meth:`_orm.Session.scalars` - main documentation for scalars
1107
1108 :meth:`_asyncio.AsyncSession.stream_scalars` - streaming version
1109
1110
1111 """ # noqa: E501
1112
1113 return await self._proxied.scalars(
1114 statement,
1115 params=params,
1116 execution_options=execution_options,
1117 bind_arguments=bind_arguments,
1118 **kw,
1119 )
1120
1121 async def get(
1122 self,
1123 entity: _EntityBindKey[_O],
1124 ident: _PKIdentityArgument,
1125 *,
1126 options: Optional[Sequence[ORMOption]] = None,
1127 populate_existing: bool = False,
1128 with_for_update: ForUpdateParameter = None,
1129 identity_token: Optional[Any] = None,
1130 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
1131 ) -> Union[_O, None]:
1132 r"""Return an instance based on the given primary key identifier,
1133 or ``None`` if not found.
1134
1135 .. container:: class_bases
1136
1137 Proxied for the :class:`_asyncio.AsyncSession` class on
1138 behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
1139
1140 .. seealso::
1141
1142 :meth:`_orm.Session.get` - main documentation for get
1143
1144
1145
1146 """ # noqa: E501
1147
1148 result = await self._proxied.get(
1149 entity,
1150 ident,
1151 options=options,
1152 populate_existing=populate_existing,
1153 with_for_update=with_for_update,
1154 identity_token=identity_token,
1155 execution_options=execution_options,
1156 )
1157 return result
1158
1159 async def get_one(
1160 self,
1161 entity: _EntityBindKey[_O],
1162 ident: _PKIdentityArgument,
1163 *,
1164 options: Optional[Sequence[ORMOption]] = None,
1165 populate_existing: bool = False,
1166 with_for_update: ForUpdateParameter = None,
1167 identity_token: Optional[Any] = None,
1168 execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
1169 ) -> _O:
1170 r"""Return an instance based on the given primary key identifier,
1171 or raise an exception if not found.
1172
1173 .. container:: class_bases
1174
1175 Proxied for the :class:`_asyncio.AsyncSession` class on
1176 behalf of the :class:`_asyncio.scoping.async_scoped_session` class.
1177
1178 Raises ``sqlalchemy.orm.exc.NoResultFound`` if the query selects
1179 no rows.
1180
1181 ..versionadded: 2.0.22
1182
1183 .. seealso::
1184
1185 :meth:`_orm.Session.get_one` - main documentation for get_one
1186
1187
1188 """ # noqa: E501
1189
1190 return await self._proxied.get_one(
1191 entity,
1192 ident,
1193 options=options,
1194 populate_existing=populate_existing,
1195 with_for_update=with_for_update,
1196 identity_token=identity_token,
1197 execution_options=execution_options,
1198 )
1199
1200 @overload
