main commit
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-10-16 16:30:25 +09:00
parent 91c7e04474
commit 537e7b363f
1146 changed files with 45926 additions and 77196 deletions

View File

@@ -1,5 +1,5 @@
# ext/asyncio/session.py
# Copyright (C) 2020-2025 the SQLAlchemy authors and contributors
# Copyright (C) 2020-2023 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
@@ -38,9 +38,6 @@ from ...orm import Session
from ...orm import SessionTransaction
from ...orm import state as _instance_state
from ...util.concurrency import greenlet_spawn
from ...util.typing import Concatenate
from ...util.typing import ParamSpec
if TYPE_CHECKING:
from .engine import AsyncConnection
@@ -74,7 +71,6 @@ if TYPE_CHECKING:
_AsyncSessionBind = Union["AsyncEngine", "AsyncConnection"]
_P = ParamSpec("_P")
_T = TypeVar("_T", bound=Any)
@@ -336,12 +332,9 @@ class AsyncSession(ReversibleProxy[Session]):
)
async def run_sync(
self,
fn: Callable[Concatenate[Session, _P], _T],
*arg: _P.args,
**kw: _P.kwargs,
self, fn: Callable[..., _T], *arg: Any, **kw: Any
) -> _T:
'''Invoke the given synchronous (i.e. not async) callable,
"""Invoke the given synchronous (i.e. not async) callable,
passing a synchronous-style :class:`_orm.Session` as the first
argument.
@@ -351,27 +344,25 @@ class AsyncSession(ReversibleProxy[Session]):
E.g.::
def some_business_method(session: Session, param: str) -> str:
"""A synchronous function that does not require awaiting
'''A synchronous function that does not require awaiting
:param session: a SQLAlchemy Session, used synchronously
:return: an optional return value is supported
"""
'''
session.add(MyObject(param=param))
session.flush()
return "success"
async def do_something_async(async_engine: AsyncEngine) -> None:
"""an async function that uses awaiting"""
'''an async function that uses awaiting'''
with AsyncSession(async_engine) as async_session:
# run some_business_method() with a sync-style
# Session, proxied into an awaitable
return_code = await async_session.run_sync(
some_business_method, param="param1"
)
return_code = await async_session.run_sync(some_business_method, param="param1")
print(return_code)
This method maintains the asyncio event loop all the way through
@@ -393,11 +384,9 @@ class AsyncSession(ReversibleProxy[Session]):
:meth:`.AsyncConnection.run_sync`
:ref:`session_run_sync`
''' # noqa: E501
""" # noqa: E501
return await greenlet_spawn(
fn, self.sync_session, *arg, _require_await=False, **kw
)
return await greenlet_spawn(fn, self.sync_session, *arg, **kw)
@overload
async def execute(
@@ -409,7 +398,8 @@ class AsyncSession(ReversibleProxy[Session]):
bind_arguments: Optional[_BindArguments] = None,
_parent_execute_state: Optional[Any] = None,
_add_event: Optional[Any] = None,
) -> Result[_T]: ...
) -> Result[_T]:
...
@overload
async def execute(
@@ -421,7 +411,8 @@ class AsyncSession(ReversibleProxy[Session]):
bind_arguments: Optional[_BindArguments] = None,
_parent_execute_state: Optional[Any] = None,
_add_event: Optional[Any] = None,
) -> CursorResult[Any]: ...
) -> CursorResult[Any]:
...
@overload
async def execute(
@@ -433,7 +424,8 @@ class AsyncSession(ReversibleProxy[Session]):
bind_arguments: Optional[_BindArguments] = None,
_parent_execute_state: Optional[Any] = None,
_add_event: Optional[Any] = None,
) -> Result[Any]: ...
) -> Result[Any]:
...
async def execute(
self,
@@ -479,7 +471,8 @@ class AsyncSession(ReversibleProxy[Session]):
execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
bind_arguments: Optional[_BindArguments] = None,
**kw: Any,
) -> Optional[_T]: ...
) -> Optional[_T]:
...
@overload
async def scalar(
@@ -490,7 +483,8 @@ class AsyncSession(ReversibleProxy[Session]):
execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
bind_arguments: Optional[_BindArguments] = None,
**kw: Any,
) -> Any: ...
) -> Any:
...
async def scalar(
self,
@@ -534,7 +528,8 @@ class AsyncSession(ReversibleProxy[Session]):
execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
bind_arguments: Optional[_BindArguments] = None,
**kw: Any,
) -> ScalarResult[_T]: ...
) -> ScalarResult[_T]:
...
@overload
async def scalars(
@@ -545,7 +540,8 @@ class AsyncSession(ReversibleProxy[Session]):
execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
bind_arguments: Optional[_BindArguments] = None,
**kw: Any,
) -> ScalarResult[Any]: ...
) -> ScalarResult[Any]:
...
async def scalars(
self,
@@ -628,7 +624,8 @@ class AsyncSession(ReversibleProxy[Session]):
"""Return an instance based on the given primary key identifier,
or raise an exception if not found.
Raises :class:`_exc.NoResultFound` if the query selects no rows.
Raises ``sqlalchemy.orm.exc.NoResultFound`` if the query selects
no rows.
..versionadded: 2.0.22
@@ -658,7 +655,8 @@ class AsyncSession(ReversibleProxy[Session]):
execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
bind_arguments: Optional[_BindArguments] = None,
**kw: Any,
) -> AsyncResult[_T]: ...
) -> AsyncResult[_T]:
...
@overload
async def stream(
@@ -669,7 +667,8 @@ class AsyncSession(ReversibleProxy[Session]):
execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
bind_arguments: Optional[_BindArguments] = None,
**kw: Any,
) -> AsyncResult[Any]: ...
) -> AsyncResult[Any]:
...
async def stream(
self,
@@ -711,7 +710,8 @@ class AsyncSession(ReversibleProxy[Session]):
execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
bind_arguments: Optional[_BindArguments] = None,
**kw: Any,
) -> AsyncScalarResult[_T]: ...
) -> AsyncScalarResult[_T]:
...
@overload
async def stream_scalars(
@@ -722,7 +722,8 @@ class AsyncSession(ReversibleProxy[Session]):
execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
bind_arguments: Optional[_BindArguments] = None,
**kw: Any,
) -> AsyncScalarResult[Any]: ...
) -> AsyncScalarResult[Any]:
...
async def stream_scalars(
self,
@@ -811,9 +812,7 @@ class AsyncSession(ReversibleProxy[Session]):
"""
trans = self.sync_session.get_transaction()
if trans is not None:
return AsyncSessionTransaction._retrieve_proxy_for_target(
trans, async_session=self
)
return AsyncSessionTransaction._retrieve_proxy_for_target(trans)
else:
return None
@@ -829,9 +828,7 @@ class AsyncSession(ReversibleProxy[Session]):
trans = self.sync_session.get_nested_transaction()
if trans is not None:
return AsyncSessionTransaction._retrieve_proxy_for_target(
trans, async_session=self
)
return AsyncSessionTransaction._retrieve_proxy_for_target(trans)
else:
return None
@@ -882,28 +879,28 @@ class AsyncSession(ReversibleProxy[Session]):
# construct async engines w/ async drivers
engines = {
"leader": create_async_engine("sqlite+aiosqlite:///leader.db"),
"other": create_async_engine("sqlite+aiosqlite:///other.db"),
"follower1": create_async_engine("sqlite+aiosqlite:///follower1.db"),
"follower2": create_async_engine("sqlite+aiosqlite:///follower2.db"),
'leader':create_async_engine("sqlite+aiosqlite:///leader.db"),
'other':create_async_engine("sqlite+aiosqlite:///other.db"),
'follower1':create_async_engine("sqlite+aiosqlite:///follower1.db"),
'follower2':create_async_engine("sqlite+aiosqlite:///follower2.db"),
}
class RoutingSession(Session):
def get_bind(self, mapper=None, clause=None, **kw):
# within get_bind(), return sync engines
if mapper and issubclass(mapper.class_, MyOtherClass):
return engines["other"].sync_engine
return engines['other'].sync_engine
elif self._flushing or isinstance(clause, (Update, Delete)):
return engines["leader"].sync_engine
return engines['leader'].sync_engine
else:
return engines[
random.choice(["follower1", "follower2"])
random.choice(['follower1','follower2'])
].sync_engine
# apply to AsyncSession using sync_session_class
AsyncSessionMaker = async_sessionmaker(sync_session_class=RoutingSession)
AsyncSessionMaker = async_sessionmaker(
sync_session_class=RoutingSession
)
The :meth:`_orm.Session.get_bind` method is called in a non-asyncio,
implicitly non-blocking context in the same manner as ORM event hooks
@@ -959,7 +956,7 @@ class AsyncSession(ReversibleProxy[Session]):
object is entered::
async with async_session.begin():
... # ORM transaction is begun
# .. ORM transaction is begun
Note that database IO will not normally occur when the session-level
transaction is begun, as database transactions begin on an
@@ -1312,7 +1309,7 @@ class AsyncSession(ReversibleProxy[Session]):
This method retrieves the history for each instrumented
attribute on the instance and performs a comparison of the current
value to its previously flushed or committed value, if any.
value to its previously committed value, if any.
It is in effect a more expensive and accurate
version of checking for the given instance in the
@@ -1636,22 +1633,16 @@ class async_sessionmaker(Generic[_AS]):
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.ext.asyncio import async_sessionmaker
async def run_some_sql(
async_session: async_sessionmaker[AsyncSession],
) -> None:
async def run_some_sql(async_session: async_sessionmaker[AsyncSession]) -> None:
async with async_session() as session:
session.add(SomeObject(data="object"))
session.add(SomeOtherObject(name="other object"))
await session.commit()
async def main() -> None:
# an AsyncEngine, which the AsyncSession will use for connection
# resources
engine = create_async_engine(
"postgresql+asyncpg://scott:tiger@localhost/"
)
engine = create_async_engine('postgresql+asyncpg://scott:tiger@localhost/')
# create a reusable factory for new AsyncSession instances
async_session = async_sessionmaker(engine)
@@ -1695,7 +1686,8 @@ class async_sessionmaker(Generic[_AS]):
expire_on_commit: bool = ...,
info: Optional[_InfoType] = ...,
**kw: Any,
): ...
):
...
@overload
def __init__(
@@ -1706,7 +1698,8 @@ class async_sessionmaker(Generic[_AS]):
expire_on_commit: bool = ...,
info: Optional[_InfoType] = ...,
**kw: Any,
): ...
):
...
def __init__(
self,
@@ -1750,6 +1743,7 @@ class async_sessionmaker(Generic[_AS]):
# commits transaction, closes session
"""
session = self()
@@ -1782,7 +1776,7 @@ class async_sessionmaker(Generic[_AS]):
AsyncSession = async_sessionmaker(some_engine)
AsyncSession.configure(bind=create_async_engine("sqlite+aiosqlite://"))
AsyncSession.configure(bind=create_async_engine('sqlite+aiosqlite://'))
""" # noqa E501
self.kw.update(new_kw)
@@ -1868,27 +1862,12 @@ class AsyncSessionTransaction(
await greenlet_spawn(self._sync_transaction().commit)
@classmethod
def _regenerate_proxy_for_target( # type: ignore[override]
cls,
target: SessionTransaction,
async_session: AsyncSession,
**additional_kw: Any, # noqa: U100
) -> AsyncSessionTransaction:
sync_transaction = target
nested = target.nested
obj = cls.__new__(cls)
obj.session = async_session
obj.sync_transaction = obj._assign_proxied(sync_transaction)
obj.nested = nested
return obj
async def start(
self, is_ctxmanager: bool = False
) -> AsyncSessionTransaction:
self.sync_transaction = self._assign_proxied(
await greenlet_spawn(
self.session.sync_session.begin_nested
self.session.sync_session.begin_nested # type: ignore
if self.nested
else self.session.sync_session.begin
)