summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm/interfaces.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2022-04-15 11:05:36 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2022-04-20 15:14:09 -0400
commitaeeff72e806420bf85e2e6723b1f941df38a3e1a (patch)
tree0bed521b4d7c4860f998e51ba5e318d18b2f5900 /lib/sqlalchemy/orm/interfaces.py
parent13a8552053c21a9fa7ff6f992ed49ee92cca73e4 (diff)
downloadsqlalchemy-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.py302
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