diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-04-15 11:05:36 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-04-20 15:14:09 -0400 |
commit | aeeff72e806420bf85e2e6723b1f941df38a3e1a (patch) | |
tree | 0bed521b4d7c4860f998e51ba5e318d18b2f5900 /lib/sqlalchemy/orm/interfaces.py | |
parent | 13a8552053c21a9fa7ff6f992ed49ee92cca73e4 (diff) | |
download | sqlalchemy-aeeff72e806420bf85e2e6723b1f941df38a3e1a.tar.gz |
pep-484: ORM public API, constructors
for the moment, abandoning using @overload with
relationship() and mapped_column(). The overloads
are very difficult to get working at all, and
the overloads that were there all wouldn't pass on
mypy. various techniques of getting them to
"work", meaning having right hand side dictate
what's legal on the left, have mixed success
and wont give consistent results; additionally,
it's legal to have Optional / non-optional
independent of nullable in any case for columns.
relationship cases are less ambiguous but mypy
was not going along with things.
we have a comprehensive system of allowing
left side annotations to drive the right side,
in the absense of explicit settings on the right.
so type-centric SQLAlchemy will be left-side
driven just like dataclasses, and the various flags
and switches on the right side will just not be
needed very much.
in other matters, one surprise, forgot to remove string support
from orm.join(A, B, "somename") or do deprecations
for it in 1.4. This is a really not-directly-used
structure barely
mentioned in the docs for many years, the example
shows a relationship being used, not a string, so
we will just change it to raise the usual error here.
Change-Id: Iefbbb8d34548b538023890ab8b7c9a5d9496ec6e
Diffstat (limited to 'lib/sqlalchemy/orm/interfaces.py')
-rw-r--r-- | lib/sqlalchemy/orm/interfaces.py | 302 |
1 files changed, 204 insertions, 98 deletions
diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index abc1300d8..0ca62b7e3 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -21,10 +21,15 @@ from __future__ import annotations import collections import typing from typing import Any +from typing import Callable from typing import cast +from typing import ClassVar +from typing import Dict +from typing import Iterator from typing import List from typing import Optional from typing import Sequence +from typing import Set from typing import Tuple from typing import Type from typing import TypeVar @@ -45,7 +50,6 @@ from .base import NotExtension as NotExtension from .base import ONETOMANY as ONETOMANY from .base import SQLORMOperations from .. import ColumnElement -from .. import inspect from .. import inspection from .. import util from ..sql import operators @@ -53,19 +57,47 @@ from ..sql import roles from ..sql import visitors from ..sql.base import ExecutableOption from ..sql.cache_key import HasCacheKey -from ..sql.elements import SQLCoreOperations from ..sql.schema import Column from ..sql.type_api import TypeEngine from ..util.typing import TypedDict + if typing.TYPE_CHECKING: + from ._typing import _EntityType + from ._typing import _IdentityKeyType + from ._typing import _InstanceDict + from ._typing import _InternalEntityType + from ._typing import _ORMAdapterProto + from ._typing import _ORMColumnExprArgument + from .attributes import InstrumentedAttribute + from .context import _MapperEntity + from .context import ORMCompileState from .decl_api import RegistryType + from .loading import _PopulatorDict + from .mapper import Mapper + from .path_registry import AbstractEntityRegistry + from .path_registry import PathRegistry + from .query import Query + from .session import Session + from .state import InstanceState + from .strategy_options import _LoadElement + from .util import AliasedInsp + from .util import CascadeOptions + from .util import ORMAdapter + from ..engine.result import Result + from ..sql._typing import _ColumnExpressionArgument from ..sql._typing import _ColumnsClauseArgument from ..sql._typing import _DMLColumnArgument from ..sql._typing import _InfoType + from ..sql._typing import _PropagateAttrsType + from ..sql.operators import OperatorType + from ..sql.util import ColumnAdapter + from ..sql.visitors import _TraverseInternalsType _T = TypeVar("_T", bound=Any) +_TLS = TypeVar("_TLS", bound="Type[LoaderStrategy]") + class ORMStatementRole(roles.StatementRole): __slots__ = () @@ -91,7 +123,9 @@ class ORMFromClauseRole(roles.StrictFromClauseRole): class ORMColumnDescription(TypedDict): name: str - type: Union[Type, TypeEngine] + # TODO: add python_type and sql_type here; combining them + # into "type" is a bad idea + type: Union[Type[Any], TypeEngine[Any]] aliased: bool expr: _ColumnsClauseArgument entity: Optional[_ColumnsClauseArgument] @@ -102,10 +136,10 @@ class _IntrospectsAnnotations: def declarative_scan( self, - registry: "RegistryType", - cls: type, + registry: RegistryType, + cls: Type[Any], key: str, - annotation: Optional[type], + annotation: Optional[Type[Any]], is_dataclass_field: Optional[bool], ) -> None: """Perform class-specific initializaton at early declarative scanning @@ -124,12 +158,12 @@ class _MapsColumns(_MappedAttribute[_T]): __slots__ = () @property - def mapper_property_to_assign(self) -> Optional["MapperProperty[_T]"]: + def mapper_property_to_assign(self) -> Optional[MapperProperty[_T]]: """return a MapperProperty to be assigned to the declarative mapping""" raise NotImplementedError() @property - def columns_to_assign(self) -> List[Column]: + def columns_to_assign(self) -> List[Column[_T]]: """A list of Column objects that should be declaratively added to the new Table object. @@ -139,7 +173,10 @@ class _MapsColumns(_MappedAttribute[_T]): @inspection._self_inspects class MapperProperty( - HasCacheKey, _MappedAttribute[_T], InspectionAttr, util.MemoizedSlots + HasCacheKey, + _MappedAttribute[_T], + InspectionAttrInfo, + util.MemoizedSlots, ): """Represent a particular class attribute mapped by :class:`_orm.Mapper`. @@ -160,12 +197,12 @@ class MapperProperty( "info", ) - _cache_key_traversal = [ + _cache_key_traversal: _TraverseInternalsType = [ ("parent", visitors.ExtendedInternalTraversal.dp_has_cache_key), ("key", visitors.ExtendedInternalTraversal.dp_string), ] - cascade = frozenset() + cascade: Optional[CascadeOptions] = None """The set of 'cascade' attribute names. This collection is checked before the 'cascade_iterator' method is called. @@ -184,14 +221,20 @@ class MapperProperty( """The :class:`_orm.PropComparator` instance that implements SQL expression construction on behalf of this mapped attribute.""" - @property - def _links_to_entity(self): - """True if this MapperProperty refers to a mapped entity. + key: str + """name of class attribute""" - Should only be True for Relationship, False for all others. + parent: Mapper[Any] + """the :class:`.Mapper` managing this property.""" - """ - raise NotImplementedError() + _is_relationship = False + + _links_to_entity: bool + """True if this MapperProperty refers to a mapped entity. + + Should only be True for Relationship, False for all others. + + """ def _memoized_attr_info(self) -> _InfoType: """Info dictionary associated with the object, allowing user-defined @@ -217,7 +260,14 @@ class MapperProperty( """ return {} - def setup(self, context, query_entity, path, adapter, **kwargs): + def setup( + self, + context: ORMCompileState, + query_entity: _MapperEntity, + path: PathRegistry, + adapter: Optional[ColumnAdapter], + **kwargs: Any, + ) -> None: """Called by Query for the purposes of constructing a SQL statement. Each MapperProperty associated with the target mapper processes the @@ -227,16 +277,30 @@ class MapperProperty( """ def create_row_processor( - self, context, query_entity, path, mapper, result, adapter, populators - ): + self, + context: ORMCompileState, + query_entity: _MapperEntity, + path: PathRegistry, + mapper: Mapper[Any], + result: Result, + adapter: Optional[ColumnAdapter], + populators: _PopulatorDict, + ) -> None: """Produce row processing functions and append to the given set of populators lists. """ def cascade_iterator( - self, type_, state, dict_, visited_states, halt_on=None - ): + self, + type_: str, + state: InstanceState[Any], + dict_: _InstanceDict, + visited_states: Set[InstanceState[Any]], + halt_on: Optional[Callable[[InstanceState[Any]], bool]] = None, + ) -> Iterator[ + Tuple[object, Mapper[Any], InstanceState[Any], _InstanceDict] + ]: """Iterate through instances related to the given instance for a particular 'cascade', starting with this MapperProperty. @@ -251,7 +315,7 @@ class MapperProperty( return iter(()) - def set_parent(self, parent, init): + def set_parent(self, parent: Mapper[Any], init: bool) -> None: """Set the parent mapper that references this MapperProperty. This method is overridden by some subclasses to perform extra @@ -260,7 +324,7 @@ class MapperProperty( """ self.parent = parent - def instrument_class(self, mapper): + def instrument_class(self, mapper: Mapper[Any]) -> None: """Hook called by the Mapper to the property to initiate instrumentation of the class attribute managed by this MapperProperty. @@ -280,11 +344,11 @@ class MapperProperty( """ - def __init__(self): + def __init__(self) -> None: self._configure_started = False self._configure_finished = False - def init(self): + def init(self) -> None: """Called after all mappers are created to assemble relationships between mappers and perform other post-mapper-creation initialization steps. @@ -296,7 +360,7 @@ class MapperProperty( self._configure_finished = True @property - def class_attribute(self): + def class_attribute(self) -> InstrumentedAttribute[_T]: """Return the class-bound descriptor corresponding to this :class:`.MapperProperty`. @@ -319,9 +383,9 @@ class MapperProperty( """ - return getattr(self.parent.class_, self.key) + return getattr(self.parent.class_, self.key) # type: ignore - def do_init(self): + def do_init(self) -> None: """Perform subclass-specific initialization post-mapper-creation steps. @@ -330,7 +394,7 @@ class MapperProperty( """ - def post_instrument_class(self, mapper): + def post_instrument_class(self, mapper: Mapper[Any]) -> None: """Perform instrumentation adjustments that need to occur after init() has completed. @@ -347,21 +411,21 @@ class MapperProperty( def merge( self, - session, - source_state, - source_dict, - dest_state, - dest_dict, - load, - _recursive, - _resolve_conflict_map, - ): + session: Session, + source_state: InstanceState[Any], + source_dict: _InstanceDict, + dest_state: InstanceState[Any], + dest_dict: _InstanceDict, + load: bool, + _recursive: Set[InstanceState[Any]], + _resolve_conflict_map: Dict[_IdentityKeyType[Any], object], + ) -> None: """Merge the attribute represented by this ``MapperProperty`` from source to destination object. """ - def __repr__(self): + def __repr__(self) -> str: return "<%s at 0x%x; %s>" % ( self.__class__.__name__, id(self), @@ -452,21 +516,28 @@ class PropComparator(SQLORMOperations[_T]): """ - __slots__ = "prop", "property", "_parententity", "_adapt_to_entity" + __slots__ = "prop", "_parententity", "_adapt_to_entity" __visit_name__ = "orm_prop_comparator" + _parententity: _InternalEntityType[Any] + _adapt_to_entity: Optional[AliasedInsp[Any]] + def __init__( self, - prop, - parentmapper, - adapt_to_entity=None, + prop: MapperProperty[_T], + parentmapper: _InternalEntityType[Any], + adapt_to_entity: Optional[AliasedInsp[Any]] = None, ): - self.prop = self.property = prop + self.prop = prop self._parententity = adapt_to_entity or parentmapper self._adapt_to_entity = adapt_to_entity - def __clause_element__(self): + @util.ro_non_memoized_property + def property(self) -> Optional[MapperProperty[_T]]: + return self.prop + + def __clause_element__(self) -> _ORMColumnExprArgument[_T]: raise NotImplementedError("%r" % self) def _bulk_update_tuples( @@ -480,22 +551,24 @@ class PropComparator(SQLORMOperations[_T]): """ - return [(self.__clause_element__(), value)] + return [(cast("_DMLColumnArgument", self.__clause_element__()), value)] - def adapt_to_entity(self, adapt_to_entity): + def adapt_to_entity( + self, adapt_to_entity: AliasedInsp[Any] + ) -> PropComparator[_T]: """Return a copy of this PropComparator which will use the given :class:`.AliasedInsp` to produce corresponding expressions. """ return self.__class__(self.prop, self._parententity, adapt_to_entity) - @property - def _parentmapper(self): + @util.ro_non_memoized_property + def _parentmapper(self) -> Mapper[Any]: """legacy; this is renamed to _parententity to be compatible with QueryableAttribute.""" - return inspect(self._parententity).mapper + return self._parententity.mapper - @property - def _propagate_attrs(self): + @util.memoized_property + def _propagate_attrs(self) -> _PropagateAttrsType: # this suits the case in coercions where we don't actually # call ``__clause_element__()`` but still need to get # resolved._propagate_attrs. See #6558. @@ -507,12 +580,14 @@ class PropComparator(SQLORMOperations[_T]): ) def _criterion_exists( - self, criterion: Optional[SQLCoreOperations[Any]] = None, **kwargs: Any + self, + criterion: Optional[_ColumnExpressionArgument[bool]] = None, + **kwargs: Any, ) -> ColumnElement[Any]: return self.prop.comparator._criterion_exists(criterion, **kwargs) - @property - def adapter(self): + @util.ro_non_memoized_property + def adapter(self) -> Optional[_ORMAdapterProto[_T]]: """Produce a callable that adapts column expressions to suit an aliased version of this comparator. @@ -522,20 +597,20 @@ class PropComparator(SQLORMOperations[_T]): else: return self._adapt_to_entity._adapt_element - @util.non_memoized_property + @util.ro_non_memoized_property def info(self) -> _InfoType: - return self.property.info + return self.prop.info @staticmethod - def _any_op(a, b, **kwargs): + def _any_op(a: Any, b: Any, **kwargs: Any) -> Any: return a.any(b, **kwargs) @staticmethod - def _has_op(left, other, **kwargs): + def _has_op(left: Any, other: Any, **kwargs: Any) -> Any: return left.has(other, **kwargs) @staticmethod - def _of_type_op(a, class_): + def _of_type_op(a: Any, class_: Any) -> Any: return a.of_type(class_) any_op = cast(operators.OperatorType, _any_op) @@ -545,16 +620,16 @@ class PropComparator(SQLORMOperations[_T]): if typing.TYPE_CHECKING: def operate( - self, op: operators.OperatorType, *other: Any, **kwargs: Any - ) -> "SQLCoreOperations[Any]": + self, op: OperatorType, *other: Any, **kwargs: Any + ) -> ColumnElement[Any]: ... def reverse_operate( - self, op: operators.OperatorType, other: Any, **kwargs: Any - ) -> "SQLCoreOperations[Any]": + self, op: OperatorType, other: Any, **kwargs: Any + ) -> ColumnElement[Any]: ... - def of_type(self, class_) -> "SQLORMOperations[_T]": + def of_type(self, class_: _EntityType[Any]) -> PropComparator[_T]: r"""Redefine this object in terms of a polymorphic subclass, :func:`_orm.with_polymorphic` construct, or :func:`_orm.aliased` construct. @@ -578,9 +653,11 @@ class PropComparator(SQLORMOperations[_T]): """ - return self.operate(PropComparator.of_type_op, class_) + return self.operate(PropComparator.of_type_op, class_) # type: ignore - def and_(self, *criteria) -> "SQLORMOperations[_T]": + def and_( + self, *criteria: _ColumnExpressionArgument[bool] + ) -> ColumnElement[bool]: """Add additional criteria to the ON clause that's represented by this relationship attribute. @@ -606,10 +683,12 @@ class PropComparator(SQLORMOperations[_T]): :func:`.with_loader_criteria` """ - return self.operate(operators.and_, *criteria) + return self.operate(operators.and_, *criteria) # type: ignore def any( - self, criterion: Optional[SQLCoreOperations[Any]] = None, **kwargs + self, + criterion: Optional[_ColumnExpressionArgument[bool]] = None, + **kwargs: Any, ) -> ColumnElement[bool]: r"""Return a SQL expression representing true if this element references a member which meets the given criterion. @@ -626,10 +705,14 @@ class PropComparator(SQLORMOperations[_T]): """ - return self.operate(PropComparator.any_op, criterion, **kwargs) + return self.operate( # type: ignore + PropComparator.any_op, criterion, **kwargs + ) def has( - self, criterion: Optional[SQLCoreOperations[Any]] = None, **kwargs + self, + criterion: Optional[_ColumnExpressionArgument[bool]] = None, + **kwargs: Any, ) -> ColumnElement[bool]: r"""Return a SQL expression representing true if this element references a member which meets the given criterion. @@ -646,7 +729,9 @@ class PropComparator(SQLORMOperations[_T]): """ - return self.operate(PropComparator.has_op, criterion, **kwargs) + return self.operate( # type: ignore + PropComparator.has_op, criterion, **kwargs + ) class StrategizedProperty(MapperProperty[_T]): @@ -674,23 +759,30 @@ class StrategizedProperty(MapperProperty[_T]): "strategy_key", ) inherit_cache = True - strategy_wildcard_key = None + strategy_wildcard_key: ClassVar[str] strategy_key: Tuple[Any, ...] - def _memoized_attr__wildcard_token(self): + _strategies: Dict[Tuple[Any, ...], LoaderStrategy] + + def _memoized_attr__wildcard_token(self) -> Tuple[str]: return ( f"{self.strategy_wildcard_key}:{path_registry._WILDCARD_TOKEN}", ) - def _memoized_attr__default_path_loader_key(self): + def _memoized_attr__default_path_loader_key( + self, + ) -> Tuple[str, Tuple[str]]: return ( "loader", (f"{self.strategy_wildcard_key}:{path_registry._DEFAULT_TOKEN}",), ) - def _get_context_loader(self, context, path): - load = None + def _get_context_loader( + self, context: ORMCompileState, path: AbstractEntityRegistry + ) -> Optional[_LoadElement]: + + load: Optional[_LoadElement] = None search_path = path[self] @@ -714,7 +806,7 @@ class StrategizedProperty(MapperProperty[_T]): return load - def _get_strategy(self, key): + def _get_strategy(self, key: Tuple[Any, ...]) -> LoaderStrategy: try: return self._strategies[key] except KeyError: @@ -768,11 +860,13 @@ class StrategizedProperty(MapperProperty[_T]): ): self.strategy.init_class_attribute(mapper) - _all_strategies = collections.defaultdict(dict) + _all_strategies: collections.defaultdict[ + Type[Any], Dict[Tuple[Any, ...], Type[LoaderStrategy]] + ] = collections.defaultdict(dict) @classmethod - def strategy_for(cls, **kw): - def decorate(dec_cls): + def strategy_for(cls, **kw: Any) -> Callable[[_TLS], _TLS]: + def decorate(dec_cls: _TLS) -> _TLS: # ensure each subclass of the strategy has its # own _strategy_keys collection if "_strategy_keys" not in dec_cls.__dict__: @@ -785,7 +879,9 @@ class StrategizedProperty(MapperProperty[_T]): return decorate @classmethod - def _strategy_lookup(cls, requesting_property, *key): + def _strategy_lookup( + cls, requesting_property: MapperProperty[Any], *key: Any + ) -> Type[LoaderStrategy]: requesting_property.parent._with_polymorphic_mappers for prop_cls in cls.__mro__: @@ -984,10 +1080,10 @@ class MapperOption(ORMOption): """ - def process_query(self, query): + def process_query(self, query: Query[Any]) -> None: """Apply a modification to the given :class:`_query.Query`.""" - def process_query_conditionally(self, query): + def process_query_conditionally(self, query: Query[Any]) -> None: """same as process_query(), except that this option may not apply to the given query. @@ -1034,7 +1130,11 @@ class LoaderStrategy: "strategy_opts", ) - def __init__(self, parent, strategy_key): + _strategy_keys: ClassVar[List[Tuple[Any, ...]]] + + def __init__( + self, parent: MapperProperty[Any], strategy_key: Tuple[Any, ...] + ): self.parent_property = parent self.is_class_level = False self.parent = self.parent_property.parent @@ -1042,12 +1142,18 @@ class LoaderStrategy: self.strategy_key = strategy_key self.strategy_opts = dict(strategy_key) - def init_class_attribute(self, mapper): + def init_class_attribute(self, mapper: Mapper[Any]) -> None: pass def setup_query( - self, compile_state, query_entity, path, loadopt, adapter, **kwargs - ): + self, + compile_state: ORMCompileState, + query_entity: _MapperEntity, + path: AbstractEntityRegistry, + loadopt: Optional[_LoadElement], + adapter: Optional[ORMAdapter], + **kwargs: Any, + ) -> None: """Establish column and other state for a given QueryContext. This method fulfills the contract specified by MapperProperty.setup(). @@ -1059,15 +1165,15 @@ class LoaderStrategy: def create_row_processor( self, - context, - query_entity, - path, - loadopt, - mapper, - result, - adapter, - populators, - ): + context: ORMCompileState, + query_entity: _MapperEntity, + path: AbstractEntityRegistry, + loadopt: Optional[_LoadElement], + mapper: Mapper[Any], + result: Result, + adapter: Optional[ORMAdapter], + populators: _PopulatorDict, + ) -> None: """Establish row processing functions for a given QueryContext. This method fulfills the contract specified by |