This commit is contained in:
2025-05-22 20:25:38 +02:00
parent 09f6750c2b
commit ce03fbf12f
529 changed files with 3353 additions and 3312 deletions

View File

@@ -4,7 +4,6 @@
#
# This module is part of SQLAlchemy and is released under
# the MIT License: https://www.opensource.org/licenses/mit-license.php
# mypy: ignore-errors
"""Dynamic collection API.
@@ -23,13 +22,19 @@ from __future__ import annotations
from typing import Any
from typing import Iterable
from typing import Iterator
from typing import List
from typing import Optional
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union
from . import attributes
from . import exc as orm_exc
from . import relationships
from . import util as orm_util
from .base import PassiveFlag
from .query import Query
from .session import object_session
from .writeonly import AbstractCollectionWriter
@@ -39,15 +44,28 @@ from .writeonly import WriteOnlyLoader
from .. import util
from ..engine import result
if TYPE_CHECKING:
from .session import Session
if TYPE_CHECKING:
from . import QueryableAttribute
from .mapper import Mapper
from .relationships import _RelationshipOrderByArg
from .session import Session
from .state import InstanceState
from .util import AliasedClass
from ..event import _Dispatch
from ..sql.elements import ColumnElement
_T = TypeVar("_T", bound=Any)
class DynamicCollectionHistory(WriteOnlyHistory):
def __init__(self, attr, state, passive, apply_to=None):
class DynamicCollectionHistory(WriteOnlyHistory[_T]):
def __init__(
self,
attr: DynamicAttributeImpl,
state: InstanceState[_T],
passive: PassiveFlag,
apply_to: Optional[DynamicCollectionHistory[_T]] = None,
) -> None:
if apply_to:
coll = AppenderQuery(attr, state).autoflush(False)
self.unchanged_items = util.OrderedIdentitySet(coll)
@@ -63,21 +81,21 @@ class DynamicCollectionHistory(WriteOnlyHistory):
class DynamicAttributeImpl(WriteOnlyAttributeImpl):
_supports_dynamic_iteration = True
collection_history_cls = DynamicCollectionHistory
collection_history_cls = DynamicCollectionHistory[Any]
query_class: Type[AppenderMixin[Any]] # type: ignore[assignment]
def __init__(
self,
class_,
key,
typecallable,
dispatch,
target_mapper,
order_by,
query_class=None,
**kw,
):
class_: Union[Type[Any], AliasedClass[Any]],
key: str,
dispatch: _Dispatch[QueryableAttribute[Any]],
target_mapper: Mapper[_T],
order_by: _RelationshipOrderByArg,
query_class: Optional[Type[AppenderMixin[_T]]] = None,
**kw: Any,
) -> None:
attributes.AttributeImpl.__init__(
self, class_, key, typecallable, dispatch, **kw
self, class_, key, None, dispatch, **kw
)
self.target_mapper = target_mapper
if order_by:
@@ -102,21 +120,23 @@ class AppenderMixin(AbstractCollectionWriter[_T]):
"""
query_class = None
query_class: Optional[Type[Query[_T]]] = None
_order_by_clauses: Tuple[ColumnElement[Any], ...]
def __init__(self, attr, state):
Query.__init__(self, attr.target_mapper, None)
def __init__(
self, attr: DynamicAttributeImpl, state: InstanceState[_T]
) -> None:
Query.__init__(
self, # type: ignore[arg-type]
attr.target_mapper,
None,
)
super().__init__(attr, state)
@property
def session(self) -> Session:
def session(self) -> Optional[Session]:
sess = object_session(self.instance)
if (
sess is not None
and self.autoflush
and sess.autoflush
and self.instance in sess
):
if sess is not None and sess.autoflush and self.instance in sess:
sess.flush()
if not orm_util.has_identity(self.instance):
return None
@@ -127,7 +147,7 @@ class AppenderMixin(AbstractCollectionWriter[_T]):
def session(self, session: Session) -> None:
self.sess = session
def _iter(self):
def _iter(self) -> Union[result.ScalarResult[_T], result.Result[_T]]:
sess = self.session
if sess is None:
state = attributes.instance_state(self.instance)
@@ -141,9 +161,9 @@ class AppenderMixin(AbstractCollectionWriter[_T]):
return result.IteratorResult(
result.SimpleResultMetaData([self.attr.class_.__name__]),
self.attr._get_collection_history(
self.attr._get_collection_history( # type: ignore[arg-type]
attributes.instance_state(self.instance),
attributes.PASSIVE_NO_INITIALIZE,
PassiveFlag.PASSIVE_NO_INITIALIZE,
).added_items,
_source_supports_scalars=True,
).scalars()
@@ -155,15 +175,15 @@ class AppenderMixin(AbstractCollectionWriter[_T]):
def __iter__(self) -> Iterator[_T]:
...
def __getitem__(self, index: Any) -> _T:
def __getitem__(self, index: Any) -> Union[_T, List[_T]]:
sess = self.session
if sess is None:
return self.attr._get_collection_history(
attributes.instance_state(self.instance),
attributes.PASSIVE_NO_INITIALIZE,
PassiveFlag.PASSIVE_NO_INITIALIZE,
).indexed(index)
else:
return self._generate(sess).__getitem__(index)
return self._generate(sess).__getitem__(index) # type: ignore[no-any-return] # noqa: E501
def count(self) -> int:
sess = self.session
@@ -171,13 +191,16 @@ class AppenderMixin(AbstractCollectionWriter[_T]):
return len(
self.attr._get_collection_history(
attributes.instance_state(self.instance),
attributes.PASSIVE_NO_INITIALIZE,
PassiveFlag.PASSIVE_NO_INITIALIZE,
).added_items
)
else:
return self._generate(sess).count()
def _generate(self, sess=None):
def _generate(
self,
sess: Optional[Session] = None,
) -> Query[_T]:
# note we're returning an entirely new Query class instance
# here without any assignment capabilities; the class of this
# query is determined by the session.
@@ -259,7 +282,7 @@ class AppenderMixin(AbstractCollectionWriter[_T]):
self._remove_impl(item)
class AppenderQuery(AppenderMixin[_T], Query[_T]):
class AppenderQuery(AppenderMixin[_T], Query[_T]): # type: ignore[misc]
"""A dynamic query that supports basic collection storage operations.
Methods on :class:`.AppenderQuery` include all methods of
@@ -270,7 +293,7 @@ class AppenderQuery(AppenderMixin[_T], Query[_T]):
"""
def mixin_user_query(cls):
def mixin_user_query(cls: Any) -> type[AppenderMixin[Any]]:
"""Return a new class with AppenderQuery functionality layered over."""
name = "Appender" + cls.__name__
return type(name, (AppenderMixin, cls), {"query_class": cls})