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/mapper.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/mapper.py')
-rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 649 |
1 files changed, 417 insertions, 232 deletions
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index abe11cc68..b37c080ea 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -23,12 +23,23 @@ import sys import threading from typing import Any from typing import Callable +from typing import cast +from typing import Collection +from typing import Deque +from typing import Dict from typing import Generic +from typing import Iterable from typing import Iterator +from typing import List +from typing import Mapping from typing import Optional +from typing import Sequence +from typing import Set from typing import Tuple from typing import Type from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union import weakref from . import attributes @@ -39,8 +50,8 @@ from . import properties from . import util as orm_util from ._typing import _O from .base import _class_to_mapper +from .base import _parse_mapper_argument from .base import _state_mapper -from .base import class_mapper from .base import PassiveFlag from .base import state_str from .interfaces import _MappedAttribute @@ -58,6 +69,8 @@ from .. import log from .. import schema from .. import sql from .. import util +from ..event import dispatcher +from ..event import EventTarget from ..sql import base as sql_base from ..sql import coercions from ..sql import expression @@ -65,26 +78,68 @@ from ..sql import operators from ..sql import roles from ..sql import util as sql_util from ..sql import visitors +from ..sql.cache_key import MemoizedHasCacheKey +from ..sql.schema import Table from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL from ..util import HasMemoized +from ..util import HasMemoized_ro_memoized_attribute +from ..util.typing import Literal if TYPE_CHECKING: from ._typing import _IdentityKeyType from ._typing import _InstanceDict + from ._typing import _ORMColumnExprArgument + from ._typing import _RegistryType + from .decl_api import registry + from .dependency import DependencyProcessor + from .descriptor_props import Composite + from .descriptor_props import Synonym + from .events import MapperEvents from .instrumentation import ClassManager + from .path_registry import AbstractEntityRegistry + from .path_registry import CachingEntityRegistry + from .properties import ColumnProperty + from .relationships import Relationship from .state import InstanceState + from ..engine import Row + from ..engine import RowMapping + from ..sql._typing import _ColumnExpressionArgument + from ..sql._typing import _EquivalentColumnMap + from ..sql.base import ReadOnlyColumnCollection + from ..sql.elements import ColumnClause from ..sql.elements import ColumnElement from ..sql.schema import Column + from ..sql.schema import Table + from ..sql.selectable import FromClause + from ..sql.selectable import TableClause + from ..sql.util import ColumnAdapter + from ..util import OrderedSet -_mapper_registries = weakref.WeakKeyDictionary() +_T = TypeVar("_T", bound=Any) +_MP = TypeVar("_MP", bound="MapperProperty[Any]") -def _all_registries(): +_WithPolymorphicArg = Union[ + Literal["*"], + Tuple[ + Union[Literal["*"], Sequence[Union["Mapper[Any]", Type[Any]]]], + Optional["FromClause"], + ], + Sequence[Union["Mapper[Any]", Type[Any]]], +] + + +_mapper_registries: weakref.WeakKeyDictionary[ + _RegistryType, bool +] = weakref.WeakKeyDictionary() + + +def _all_registries() -> Set[registry]: with _CONFIGURE_MUTEX: return set(_mapper_registries) -def _unconfigured_mappers(): +def _unconfigured_mappers() -> Iterator[Mapper[Any]]: for reg in _all_registries(): for mapper in reg._mappers_to_configure(): yield mapper @@ -107,9 +162,11 @@ _CONFIGURE_MUTEX = threading.RLock() class Mapper( ORMFromClauseRole, ORMEntityColumnsClauseRole, - sql_base.MemoizedHasCacheKey, + MemoizedHasCacheKey, InspectionAttr, log.Identified, + inspection.Inspectable["Mapper[_O]"], + EventTarget, Generic[_O], ): """Defines an association between a Python class and a database table or @@ -123,18 +180,11 @@ class Mapper( """ + dispatch: dispatcher[Mapper[_O]] + _dispose_called = False _ready_for_configure = False - class_: Type[_O] - """The class to which this :class:`_orm.Mapper` is mapped.""" - - _identity_class: Type[_O] - - always_refresh: bool - allow_partial_pks: bool - version_id_col: Optional[ColumnElement[Any]] - @util.deprecated_params( non_primary=( "1.3", @@ -148,33 +198,39 @@ class Mapper( def __init__( self, class_: Type[_O], - local_table=None, - properties=None, - primary_key=None, - non_primary=False, - inherits=None, - inherit_condition=None, - inherit_foreign_keys=None, - always_refresh=False, - version_id_col=None, - version_id_generator=None, - polymorphic_on=None, - _polymorphic_map=None, - polymorphic_identity=None, - concrete=False, - with_polymorphic=None, - polymorphic_load=None, - allow_partial_pks=True, - batch=True, - column_prefix=None, - include_properties=None, - exclude_properties=None, - passive_updates=True, - passive_deletes=False, - confirm_deleted_rows=True, - eager_defaults=False, - legacy_is_orphan=False, - _compiled_cache_size=100, + local_table: Optional[FromClause] = None, + properties: Optional[Mapping[str, MapperProperty[Any]]] = None, + primary_key: Optional[Iterable[_ORMColumnExprArgument[Any]]] = None, + non_primary: bool = False, + inherits: Optional[Union[Mapper[Any], Type[Any]]] = None, + inherit_condition: Optional[_ColumnExpressionArgument[bool]] = None, + inherit_foreign_keys: Optional[ + Sequence[_ORMColumnExprArgument[Any]] + ] = None, + always_refresh: bool = False, + version_id_col: Optional[_ORMColumnExprArgument[Any]] = None, + version_id_generator: Optional[ + Union[Literal[False], Callable[[Any], Any]] + ] = None, + polymorphic_on: Optional[ + Union[_ORMColumnExprArgument[Any], str, MapperProperty[Any]] + ] = None, + _polymorphic_map: Optional[Dict[Any, Mapper[Any]]] = None, + polymorphic_identity: Optional[Any] = None, + concrete: bool = False, + with_polymorphic: Optional[_WithPolymorphicArg] = None, + polymorphic_load: Optional[Literal["selectin", "inline"]] = None, + allow_partial_pks: bool = True, + batch: bool = True, + column_prefix: Optional[str] = None, + include_properties: Optional[Sequence[str]] = None, + exclude_properties: Optional[Sequence[str]] = None, + passive_updates: bool = True, + passive_deletes: bool = False, + confirm_deleted_rows: bool = True, + eager_defaults: bool = False, + legacy_is_orphan: bool = False, + _compiled_cache_size: int = 100, ): r"""Direct constructor for a new :class:`_orm.Mapper` object. @@ -593,8 +649,6 @@ class Mapper( self.class_.__name__, ) - self.class_manager = None - self._primary_key_argument = util.to_list(primary_key) self.non_primary = non_primary @@ -623,17 +677,36 @@ class Mapper( self.concrete = concrete self.single = False - self.inherits = inherits + + if inherits is not None: + self.inherits = _parse_mapper_argument(inherits) + else: + self.inherits = None + if local_table is not None: self.local_table = coercions.expect( roles.StrictFromClauseRole, local_table ) + elif self.inherits: + # note this is a new flow as of 2.0 so that + # .local_table need not be Optional + self.local_table = self.inherits.local_table + self.single = True else: - self.local_table = None + raise sa_exc.ArgumentError( + f"Mapper[{self.class_.__name__}(None)] has None for a " + "primary table argument and does not specify 'inherits'" + ) + + if inherit_condition is not None: + self.inherit_condition = coercions.expect( + roles.OnClauseRole, inherit_condition + ) + else: + self.inherit_condition = None - self.inherit_condition = inherit_condition self.inherit_foreign_keys = inherit_foreign_keys - self._init_properties = properties or {} + self._init_properties = dict(properties) if properties else {} self._delete_orphans = [] self.batch = batch self.eager_defaults = eager_defaults @@ -694,7 +767,10 @@ class Mapper( # while a configure_mappers() is occurring (and defer a # configure_mappers() until construction succeeds) with _CONFIGURE_MUTEX: - self.dispatch._events._new_mapper_instance(class_, self) + + cast("MapperEvents", self.dispatch._events)._new_mapper_instance( + class_, self + ) self._configure_inheritance() self._configure_class_instrumentation() self._configure_properties() @@ -704,16 +780,21 @@ class Mapper( self._log("constructed") self._expire_memoizations() - # major attributes initialized at the classlevel so that - # they can be Sphinx-documented. + def _gen_cache_key(self, anon_map, bindparams): + return (self,) + + # ### BEGIN + # ATTRIBUTE DECLARATIONS START HERE is_mapper = True """Part of the inspection API.""" represents_outer_join = False + registry: _RegistryType + @property - def mapper(self): + def mapper(self) -> Mapper[_O]: """Part of the inspection API. Returns self. @@ -721,9 +802,6 @@ class Mapper( """ return self - def _gen_cache_key(self, anon_map, bindparams): - return (self,) - @property def entity(self): r"""Part of the inspection API. @@ -733,49 +811,109 @@ class Mapper( """ return self.class_ - local_table = None - """The :class:`_expression.Selectable` which this :class:`_orm.Mapper` - manages. + class_: Type[_O] + """The class to which this :class:`_orm.Mapper` is mapped.""" + + _identity_class: Type[_O] + + _delete_orphans: List[Tuple[str, Type[Any]]] + _dependency_processors: List[DependencyProcessor] + _memoized_values: Dict[Any, Callable[[], Any]] + _inheriting_mappers: util.WeakSequence[Mapper[Any]] + _all_tables: Set[Table] + + _pks_by_table: Dict[FromClause, OrderedSet[ColumnClause[Any]]] + _cols_by_table: Dict[FromClause, OrderedSet[ColumnElement[Any]]] + + _props: util.OrderedDict[str, MapperProperty[Any]] + _init_properties: Dict[str, MapperProperty[Any]] + + _columntoproperty: _ColumnMapping + + _set_polymorphic_identity: Optional[Callable[[InstanceState[_O]], None]] + _validate_polymorphic_identity: Optional[ + Callable[[Mapper[_O], InstanceState[_O], _InstanceDict], None] + ] + + tables: Sequence[Table] + """A sequence containing the collection of :class:`_schema.Table` objects + which this :class:`_orm.Mapper` is aware of. + + If the mapper is mapped to a :class:`_expression.Join`, or an + :class:`_expression.Alias` + representing a :class:`_expression.Select`, the individual + :class:`_schema.Table` + objects that comprise the full construct will be represented here. + + This is a *read only* attribute determined during mapper construction. + Behavior is undefined if directly modified. + + """ + + validators: util.immutabledict[str, Tuple[str, Dict[str, Any]]] + """An immutable dictionary of attributes which have been decorated + using the :func:`_orm.validates` decorator. + + The dictionary contains string attribute names as keys + mapped to the actual validation method. + + """ + + always_refresh: bool + allow_partial_pks: bool + version_id_col: Optional[ColumnElement[Any]] + + with_polymorphic: Optional[ + Tuple[ + Union[Literal["*"], Sequence[Union["Mapper[Any]", Type[Any]]]], + Optional["FromClause"], + ] + ] + + version_id_generator: Optional[Union[Literal[False], Callable[[Any], Any]]] + + local_table: FromClause + """The immediate :class:`_expression.FromClause` which this + :class:`_orm.Mapper` refers towards. - Typically is an instance of :class:`_schema.Table` or - :class:`_expression.Alias`. - May also be ``None``. + Typically is an instance of :class:`_schema.Table`, may be any + :class:`.FromClause`. The "local" table is the selectable that the :class:`_orm.Mapper` is directly responsible for managing from an attribute access and flush perspective. For - non-inheriting mappers, the local table is the same as the - "mapped" table. For joined-table inheritance mappers, local_table - will be the particular sub-table of the overall "join" which - this :class:`_orm.Mapper` represents. If this mapper is a - single-table inheriting mapper, local_table will be ``None``. + non-inheriting mappers, :attr:`.Mapper.local_table` will be the same + as :attr:`.Mapper.persist_selectable`. For inheriting mappers, + :attr:`.Mapper.local_table` refers to the specific portion of + :attr:`.Mapper.persist_selectable` that includes the columns to which + this :class:`.Mapper` is loading/persisting, such as a particular + :class:`.Table` within a join. .. seealso:: :attr:`_orm.Mapper.persist_selectable`. + :attr:`_orm.Mapper.selectable`. + """ - persist_selectable = None - """The :class:`_expression.Selectable` to which this :class:`_orm.Mapper` + persist_selectable: FromClause + """The :class:`_expression.FromClause` to which this :class:`_orm.Mapper` is mapped. - Typically an instance of :class:`_schema.Table`, - :class:`_expression.Join`, or :class:`_expression.Alias`. - - The :attr:`_orm.Mapper.persist_selectable` is separate from - :attr:`_orm.Mapper.selectable` in that the former represents columns - that are mapped on this class or its superclasses, whereas the - latter may be a "polymorphic" selectable that contains additional columns - which are in fact mapped on subclasses only. + Typically is an instance of :class:`_schema.Table`, may be any + :class:`.FromClause`. - "persist selectable" is the "thing the mapper writes to" and - "selectable" is the "thing the mapper selects from". - - :attr:`_orm.Mapper.persist_selectable` is also separate from - :attr:`_orm.Mapper.local_table`, which represents the set of columns that - are locally mapped on this class directly. + The :attr:`_orm.Mapper.persist_selectable` is similar to + :attr:`.Mapper.local_table`, but represents the :class:`.FromClause` that + represents the inheriting class hierarchy overall in an inheritance + scenario. + :attr.`.Mapper.persist_selectable` is also separate from the + :attr:`.Mapper.selectable` attribute, the latter of which may be an + alternate subquery used for selecting columns. + :attr.`.Mapper.persist_selectable` is oriented towards columns that + will be written on a persist operation. .. seealso:: @@ -785,16 +923,15 @@ class Mapper( """ - inherits = None + inherits: Optional[Mapper[Any]] """References the :class:`_orm.Mapper` which this :class:`_orm.Mapper` inherits from, if any. - This is a *read only* attribute determined during mapper construction. - Behavior is undefined if directly modified. - """ - configured = False + inherit_condition: Optional[ColumnElement[bool]] + + configured: bool = False """Represent ``True`` if this :class:`_orm.Mapper` has been configured. This is a *read only* attribute determined during mapper construction. @@ -806,7 +943,7 @@ class Mapper( """ - concrete = None + concrete: bool """Represent ``True`` if this :class:`_orm.Mapper` is a concrete inheritance mapper. @@ -815,21 +952,6 @@ class Mapper( """ - tables = None - """An iterable containing the collection of :class:`_schema.Table` objects - which this :class:`_orm.Mapper` is aware of. - - If the mapper is mapped to a :class:`_expression.Join`, or an - :class:`_expression.Alias` - representing a :class:`_expression.Select`, the individual - :class:`_schema.Table` - objects that comprise the full construct will be represented here. - - This is a *read only* attribute determined during mapper construction. - Behavior is undefined if directly modified. - - """ - primary_key: Tuple[Column[Any], ...] """An iterable containing the collection of :class:`_schema.Column` objects @@ -854,14 +976,6 @@ class Mapper( """ - class_: Type[_O] - """The Python class which this :class:`_orm.Mapper` maps. - - This is a *read only* attribute determined during mapper construction. - Behavior is undefined if directly modified. - - """ - class_manager: ClassManager[_O] """The :class:`.ClassManager` which maintains event listeners and class-bound descriptors for this :class:`_orm.Mapper`. @@ -871,7 +985,7 @@ class Mapper( """ - single = None + single: bool """Represent ``True`` if this :class:`_orm.Mapper` is a single table inheritance mapper. @@ -882,7 +996,7 @@ class Mapper( """ - non_primary = None + non_primary: bool """Represent ``True`` if this :class:`_orm.Mapper` is a "non-primary" mapper, e.g. a mapper that is used only to select rows but not for persistence management. @@ -892,7 +1006,7 @@ class Mapper( """ - polymorphic_on = None + polymorphic_on: Optional[ColumnElement[Any]] """The :class:`_schema.Column` or SQL expression specified as the ``polymorphic_on`` argument for this :class:`_orm.Mapper`, within an inheritance scenario. @@ -906,7 +1020,7 @@ class Mapper( """ - polymorphic_map = None + polymorphic_map: Dict[Any, Mapper[Any]] """A mapping of "polymorphic identity" identifiers mapped to :class:`_orm.Mapper` instances, within an inheritance scenario. @@ -922,7 +1036,7 @@ class Mapper( """ - polymorphic_identity = None + polymorphic_identity: Optional[Any] """Represent an identifier which is matched against the :attr:`_orm.Mapper.polymorphic_on` column during result row loading. @@ -935,7 +1049,7 @@ class Mapper( """ - base_mapper = None + base_mapper: Mapper[Any] """The base-most :class:`_orm.Mapper` in an inheritance chain. In a non-inheriting scenario, this attribute will always be this @@ -948,7 +1062,7 @@ class Mapper( """ - columns = None + columns: ReadOnlyColumnCollection[str, Column[Any]] """A collection of :class:`_schema.Column` or other scalar expression objects maintained by this :class:`_orm.Mapper`. @@ -965,25 +1079,16 @@ class Mapper( """ - validators = None - """An immutable dictionary of attributes which have been decorated - using the :func:`_orm.validates` decorator. - - The dictionary contains string attribute names as keys - mapped to the actual validation method. - - """ - - c = None + c: ReadOnlyColumnCollection[str, Column[Any]] """A synonym for :attr:`_orm.Mapper.columns`.""" - @property + @util.non_memoized_property @util.deprecated("1.3", "Use .persist_selectable") def mapped_table(self): return self.persist_selectable @util.memoized_property - def _path_registry(self) -> PathRegistry: + def _path_registry(self) -> CachingEntityRegistry: return PathRegistry.per_mapper(self) def _configure_inheritance(self): @@ -994,8 +1099,6 @@ class Mapper( self._inheriting_mappers = util.WeakSequence() if self.inherits: - if isinstance(self.inherits, type): - self.inherits = class_mapper(self.inherits, configure=False) if not issubclass(self.class_, self.inherits.class_): raise sa_exc.ArgumentError( "Class '%s' does not inherit from '%s'" @@ -1011,11 +1114,9 @@ class Mapper( "only allowed from a %s mapper" % (np, self.class_.__name__, np) ) - # inherit_condition is optional. - if self.local_table is None: - self.local_table = self.inherits.local_table + + if self.single: self.persist_selectable = self.inherits.persist_selectable - self.single = True elif self.local_table is not self.inherits.local_table: if self.concrete: self.persist_selectable = self.local_table @@ -1068,6 +1169,7 @@ class Mapper( self.local_table.description, ) ) from afe + assert self.inherits.persist_selectable is not None self.persist_selectable = sql.join( self.inherits.persist_selectable, self.local_table, @@ -1149,6 +1251,7 @@ class Mapper( else: self._all_tables = set() self.base_mapper = self + assert self.local_table is not None self.persist_selectable = self.local_table if self.polymorphic_identity is not None: self.polymorphic_map[self.polymorphic_identity] = self @@ -1160,21 +1263,34 @@ class Mapper( % self ) - def _set_with_polymorphic(self, with_polymorphic): + def _set_with_polymorphic( + self, with_polymorphic: Optional[_WithPolymorphicArg] + ) -> None: if with_polymorphic == "*": self.with_polymorphic = ("*", None) elif isinstance(with_polymorphic, (tuple, list)): if isinstance(with_polymorphic[0], (str, tuple, list)): - self.with_polymorphic = with_polymorphic + self.with_polymorphic = cast( + """Tuple[ + Union[ + Literal["*"], + Sequence[Union["Mapper[Any]", Type[Any]]], + ], + Optional["FromClause"], + ]""", + with_polymorphic, + ) else: self.with_polymorphic = (with_polymorphic, None) elif with_polymorphic is not None: - raise sa_exc.ArgumentError("Invalid setting for with_polymorphic") + raise sa_exc.ArgumentError( + f"Invalid setting for with_polymorphic: {with_polymorphic!r}" + ) else: self.with_polymorphic = None if self.with_polymorphic and self.with_polymorphic[1] is not None: - self.with_polymorphic = ( + self.with_polymorphic = ( # type: ignore self.with_polymorphic[0], coercions.expect( roles.StrictFromClauseRole, @@ -1191,6 +1307,7 @@ class Mapper( if self.with_polymorphic is None: self._set_with_polymorphic((subcl,)) elif self.with_polymorphic[0] != "*": + assert isinstance(self.with_polymorphic[0], tuple) self._set_with_polymorphic( (self.with_polymorphic[0] + (subcl,), self.with_polymorphic[1]) ) @@ -1241,7 +1358,7 @@ class Mapper( # we expect that declarative has applied the class manager # already and set up a registry. if this is None, # this raises as of 2.0. - manager = attributes.manager_of_class(self.class_) + manager = attributes.opt_manager_of_class(self.class_) if self.non_primary: if not manager or not manager.is_mapped: @@ -1251,6 +1368,8 @@ class Mapper( "Mapper." % self.class_ ) self.class_manager = manager + + assert manager.registry is not None self.registry = manager.registry self._identity_class = manager.mapper._identity_class manager.registry._add_non_primary_mapper(self) @@ -1275,7 +1394,7 @@ class Mapper( manager = instrumentation.register_class( self.class_, mapper=self, - expired_attribute_loader=util.partial( + expired_attribute_loader=util.partial( # type: ignore loading.load_scalar_attributes, self ), # finalize flag means instrument the __init__ method @@ -1284,6 +1403,8 @@ class Mapper( ) self.class_manager = manager + + assert manager.registry is not None self.registry = manager.registry # The remaining members can be added by any mapper, @@ -1315,15 +1436,25 @@ class Mapper( {name: (method, validation_opts)} ) - def _set_dispose_flags(self): + def _set_dispose_flags(self) -> None: self.configured = True self._ready_for_configure = True self._dispose_called = True self.__dict__.pop("_configure_failed", None) - def _configure_pks(self): - self.tables = sql_util.find_tables(self.persist_selectable) + def _configure_pks(self) -> None: + self.tables = cast( + "List[Table]", sql_util.find_tables(self.persist_selectable) + ) + for t in self.tables: + if not isinstance(t, Table): + raise sa_exc.ArgumentError( + f"ORM mappings can only be made against schema-level " + f"Table objects, not TableClause; got " + f"tableclause {t.name !r}" + ) + self._all_tables.update(t for t in self.tables if isinstance(t, Table)) self._pks_by_table = {} self._cols_by_table = {} @@ -1335,16 +1466,16 @@ class Mapper( pk_cols = util.column_set(c for c in all_cols if c.primary_key) # identify primary key columns which are also mapped by this mapper. - tables = set(self.tables + [self.persist_selectable]) - self._all_tables.update(tables) - for t in tables: - if t.primary_key and pk_cols.issuperset(t.primary_key): + for fc in set(self.tables).union([self.persist_selectable]): + if fc.primary_key and pk_cols.issuperset(fc.primary_key): # ordering is important since it determines the ordering of # mapper.primary_key (and therefore query.get()) - self._pks_by_table[t] = util.ordered_column_set( - t.primary_key - ).intersection(pk_cols) - self._cols_by_table[t] = util.ordered_column_set(t.c).intersection( + self._pks_by_table[fc] = util.ordered_column_set( # type: ignore # noqa: E501 + fc.primary_key + ).intersection( + pk_cols + ) + self._cols_by_table[fc] = util.ordered_column_set(fc.c).intersection( # type: ignore # noqa: E501 all_cols ) @@ -1386,10 +1517,15 @@ class Mapper( self.primary_key = self.inherits.primary_key else: # determine primary key from argument or persist_selectable pks + primary_key: Collection[ColumnElement[Any]] + if self._primary_key_argument: primary_key = [ - self.persist_selectable.corresponding_column(c) - for c in self._primary_key_argument + cc if cc is not None else c + for cc, c in ( + (self.persist_selectable.corresponding_column(c), c) + for c in self._primary_key_argument + ) ] else: # if heuristically determined PKs, reduce to the minimal set @@ -1413,7 +1549,7 @@ class Mapper( # determine cols that aren't expressed within our tables; mark these # as "read only" properties which are refreshed upon INSERT/UPDATE - self._readonly_props = set( + self._readonly_props = { self._columntoproperty[col] for col in self._columntoproperty if self._columntoproperty[col] not in self._identity_key_props @@ -1421,12 +1557,12 @@ class Mapper( not hasattr(col, "table") or col.table not in self._cols_by_table ) - ) + } - def _configure_properties(self): + def _configure_properties(self) -> None: # TODO: consider using DedupeColumnCollection - self.columns = self.c = sql_base.ColumnCollection() + self.columns = self.c = sql_base.ColumnCollection() # type: ignore # object attribute names mapped to MapperProperty objects self._props = util.OrderedDict() @@ -1454,7 +1590,6 @@ class Mapper( continue column_key = (self.column_prefix or "") + column.key - if self._should_exclude( column.key, column_key, @@ -1542,6 +1677,7 @@ class Mapper( col = self.polymorphic_on if isinstance(col, schema.Column) and ( self.with_polymorphic is None + or self.with_polymorphic[1] is None or self.with_polymorphic[1].corresponding_column(col) is None ): @@ -1763,8 +1899,8 @@ class Mapper( self.columns.add(col, key) for col in prop.columns + prop._orig_columns: - for col in col.proxy_set: - self._columntoproperty[col] = prop + for proxy_col in col.proxy_set: + self._columntoproperty[proxy_col] = prop prop.key = key @@ -2033,7 +2169,9 @@ class Mapper( self._check_configure() return iter(self._props.values()) - def _mappers_from_spec(self, spec, selectable): + def _mappers_from_spec( + self, spec: Any, selectable: Optional[FromClause] + ) -> Sequence[Mapper[Any]]: """given a with_polymorphic() argument, return the set of mappers it represents. @@ -2044,7 +2182,7 @@ class Mapper( if spec == "*": mappers = list(self.self_and_descendants) elif spec: - mappers = set() + mapper_set = set() for m in util.to_list(spec): m = _class_to_mapper(m) if not m.isa(self): @@ -2053,10 +2191,10 @@ class Mapper( ) if selectable is None: - mappers.update(m.iterate_to_root()) + mapper_set.update(m.iterate_to_root()) else: - mappers.add(m) - mappers = [m for m in self.self_and_descendants if m in mappers] + mapper_set.add(m) + mappers = [m for m in self.self_and_descendants if m in mapper_set] else: mappers = [] @@ -2067,7 +2205,9 @@ class Mapper( mappers = [m for m in mappers if m.local_table in tables] return mappers - def _selectable_from_mappers(self, mappers, innerjoin): + def _selectable_from_mappers( + self, mappers: Iterable[Mapper[Any]], innerjoin: bool + ) -> FromClause: """given a list of mappers (assumed to be within this mapper's inheritance hierarchy), construct an outerjoin amongst those mapper's mapped tables. @@ -2098,13 +2238,13 @@ class Mapper( def _single_table_criterion(self): if self.single and self.inherits and self.polymorphic_on is not None: return self.polymorphic_on._annotate({"parentmapper": self}).in_( - m.polymorphic_identity for m in self.self_and_descendants + [m.polymorphic_identity for m in self.self_and_descendants] ) else: return None @HasMemoized.memoized_attribute - def _with_polymorphic_mappers(self): + def _with_polymorphic_mappers(self) -> Sequence[Mapper[Any]]: self._check_configure() if not self.with_polymorphic: @@ -2124,8 +2264,8 @@ class Mapper( """ self._check_configure() - @HasMemoized.memoized_attribute - def _with_polymorphic_selectable(self): + @HasMemoized_ro_memoized_attribute + def _with_polymorphic_selectable(self) -> FromClause: if not self.with_polymorphic: return self.persist_selectable @@ -2143,7 +2283,7 @@ class Mapper( """ - @HasMemoized.memoized_attribute + @HasMemoized_ro_memoized_attribute def _insert_cols_evaluating_none(self): return dict( ( @@ -2250,7 +2390,7 @@ class Mapper( @HasMemoized.memoized_instancemethod def __clause_element__(self): - annotations = { + annotations: Dict[str, Any] = { "entity_namespace": self, "parententity": self, "parentmapper": self, @@ -2290,7 +2430,7 @@ class Mapper( ) @property - def selectable(self): + def selectable(self) -> FromClause: """The :class:`_schema.FromClause` construct this :class:`_orm.Mapper` selects from by default. @@ -2302,8 +2442,11 @@ class Mapper( return self._with_polymorphic_selectable def _with_polymorphic_args( - self, spec=None, selectable=False, innerjoin=False - ): + self, + spec: Any = None, + selectable: Union[Literal[False, None], FromClause] = False, + innerjoin: bool = False, + ) -> Tuple[Sequence[Mapper[Any]], FromClause]: if selectable not in (None, False): selectable = coercions.expect( roles.StrictFromClauseRole, selectable, allow_select=True @@ -2357,7 +2500,7 @@ class Mapper( ] @HasMemoized.memoized_attribute - def _polymorphic_adapter(self): + def _polymorphic_adapter(self) -> Optional[sql_util.ColumnAdapter]: if self.with_polymorphic: return sql_util.ColumnAdapter( self.selectable, equivalents=self._equivalent_columns @@ -2394,7 +2537,7 @@ class Mapper( yield c @HasMemoized.memoized_attribute - def attrs(self) -> util.ReadOnlyProperties["MapperProperty"]: + def attrs(self) -> util.ReadOnlyProperties[MapperProperty[Any]]: """A namespace of all :class:`.MapperProperty` objects associated this mapper. @@ -2432,7 +2575,7 @@ class Mapper( return util.ReadOnlyProperties(self._props) @HasMemoized.memoized_attribute - def all_orm_descriptors(self): + def all_orm_descriptors(self) -> util.ReadOnlyProperties[InspectionAttr]: """A namespace of all :class:`.InspectionAttr` attributes associated with the mapped class. @@ -2503,7 +2646,7 @@ class Mapper( @HasMemoized.memoized_attribute @util.preload_module("sqlalchemy.orm.descriptor_props") - def synonyms(self): + def synonyms(self) -> util.ReadOnlyProperties[Synonym[Any]]: """Return a namespace of all :class:`.Synonym` properties maintained by this :class:`_orm.Mapper`. @@ -2523,7 +2666,7 @@ class Mapper( return self.class_ @HasMemoized.memoized_attribute - def column_attrs(self): + def column_attrs(self) -> util.ReadOnlyProperties[ColumnProperty[Any]]: """Return a namespace of all :class:`.ColumnProperty` properties maintained by this :class:`_orm.Mapper`. @@ -2536,9 +2679,9 @@ class Mapper( """ return self._filter_properties(properties.ColumnProperty) - @util.preload_module("sqlalchemy.orm.relationships") @HasMemoized.memoized_attribute - def relationships(self): + @util.preload_module("sqlalchemy.orm.relationships") + def relationships(self) -> util.ReadOnlyProperties[Relationship[Any]]: """A namespace of all :class:`.Relationship` properties maintained by this :class:`_orm.Mapper`. @@ -2567,7 +2710,7 @@ class Mapper( @HasMemoized.memoized_attribute @util.preload_module("sqlalchemy.orm.descriptor_props") - def composites(self): + def composites(self) -> util.ReadOnlyProperties[Composite[Any]]: """Return a namespace of all :class:`.Composite` properties maintained by this :class:`_orm.Mapper`. @@ -2582,7 +2725,9 @@ class Mapper( util.preloaded.orm_descriptor_props.Composite ) - def _filter_properties(self, type_): + def _filter_properties( + self, type_: Type[_MP] + ) -> util.ReadOnlyProperties[_MP]: self._check_configure() return util.ReadOnlyProperties( util.OrderedDict( @@ -2610,7 +2755,7 @@ class Mapper( ) @HasMemoized.memoized_attribute - def _equivalent_columns(self): + def _equivalent_columns(self) -> _EquivalentColumnMap: """Create a map of all equivalent columns, based on the determination of column pairs that are equated to one another based on inherit condition. This is designed @@ -2630,18 +2775,18 @@ class Mapper( } """ - result = util.column_dict() + result: _EquivalentColumnMap = {} def visit_binary(binary): if binary.operator == operators.eq: if binary.left in result: result[binary.left].add(binary.right) else: - result[binary.left] = util.column_set((binary.right,)) + result[binary.left] = {binary.right} if binary.right in result: result[binary.right].add(binary.left) else: - result[binary.right] = util.column_set((binary.left,)) + result[binary.right] = {binary.left} for mapper in self.base_mapper.self_and_descendants: if mapper.inherit_condition is not None: @@ -2711,13 +2856,13 @@ class Mapper( return False - def common_parent(self, other): + def common_parent(self, other: Mapper[Any]) -> bool: """Return true if the given mapper shares a common inherited parent as this mapper.""" return self.base_mapper is other.base_mapper - def is_sibling(self, other): + def is_sibling(self, other: Mapper[Any]) -> bool: """return true if the other mapper is an inheriting sibling to this one. common parent but different branch @@ -2728,7 +2873,9 @@ class Mapper( and not other.isa(self) ) - def _canload(self, state, allow_subtypes): + def _canload( + self, state: InstanceState[Any], allow_subtypes: bool + ) -> bool: s = self.primary_mapper() if self.polymorphic_on is not None or allow_subtypes: return _state_mapper(state).isa(s) @@ -2738,19 +2885,19 @@ class Mapper( def isa(self, other: Mapper[Any]) -> bool: """Return True if the this mapper inherits from the given mapper.""" - m = self + m: Optional[Mapper[Any]] = self while m and m is not other: m = m.inherits return bool(m) - def iterate_to_root(self): - m = self + def iterate_to_root(self) -> Iterator[Mapper[Any]]: + m: Optional[Mapper[Any]] = self while m: yield m m = m.inherits @HasMemoized.memoized_attribute - def self_and_descendants(self): + def self_and_descendants(self) -> Sequence[Mapper[Any]]: """The collection including this mapper and all descendant mappers. This includes not just the immediately inheriting mappers but @@ -2765,7 +2912,7 @@ class Mapper( stack.extend(item._inheriting_mappers) return util.WeakSequence(descendants) - def polymorphic_iterator(self): + def polymorphic_iterator(self) -> Iterator[Mapper[Any]]: """Iterate through the collection including this mapper and all descendant mappers. @@ -2778,18 +2925,18 @@ class Mapper( """ return iter(self.self_and_descendants) - def primary_mapper(self): + def primary_mapper(self) -> Mapper[Any]: """Return the primary mapper corresponding to this mapper's class key (class).""" return self.class_manager.mapper @property - def primary_base_mapper(self): + def primary_base_mapper(self) -> Mapper[Any]: return self.class_manager.mapper.base_mapper def _result_has_identity_key(self, result, adapter=None): - pk_cols = self.primary_key + pk_cols: Sequence[ColumnClause[Any]] = self.primary_key if adapter: pk_cols = [adapter.columns[c] for c in pk_cols] rk = result.keys() @@ -2799,25 +2946,35 @@ class Mapper( else: return True - def identity_key_from_row(self, row, identity_token=None, adapter=None): + def identity_key_from_row( + self, + row: Optional[Union[Row, RowMapping]], + identity_token: Optional[Any] = None, + adapter: Optional[ColumnAdapter] = None, + ) -> _IdentityKeyType[_O]: """Return an identity-map key for use in storing/retrieving an item from the identity map. - :param row: A :class:`.Row` instance. The columns which are - mapped by this :class:`_orm.Mapper` should be locatable in the row, - preferably via the :class:`_schema.Column` - object directly (as is the case - when a :func:`_expression.select` construct is executed), or - via string names of the form ``<tablename>_<colname>``. + :param row: A :class:`.Row` or :class:`.RowMapping` produced from a + result set that selected from the ORM mapped primary key columns. + + .. versionchanged:: 2.0 + :class:`.Row` or :class:`.RowMapping` are accepted + for the "row" argument """ - pk_cols = self.primary_key + pk_cols: Sequence[ColumnClause[Any]] = self.primary_key if adapter: pk_cols = [adapter.columns[c] for c in pk_cols] + if hasattr(row, "_mapping"): + mapping = row._mapping # type: ignore + else: + mapping = cast("Mapping[Any, Any]", row) + return ( self._identity_class, - tuple(row[column] for column in pk_cols), + tuple(mapping[column] for column in pk_cols), # type: ignore identity_token, ) @@ -2852,12 +3009,12 @@ class Mapper( """ state = attributes.instance_state(instance) - return self._identity_key_from_state(state, attributes.PASSIVE_OFF) + return self._identity_key_from_state(state, PassiveFlag.PASSIVE_OFF) def _identity_key_from_state( self, state: InstanceState[_O], - passive: PassiveFlag = attributes.PASSIVE_RETURN_NO_VALUE, + passive: PassiveFlag = PassiveFlag.PASSIVE_RETURN_NO_VALUE, ) -> _IdentityKeyType[_O]: dict_ = state.dict manager = state.manager @@ -2884,7 +3041,7 @@ class Mapper( """ state = attributes.instance_state(instance) identity_key = self._identity_key_from_state( - state, attributes.PASSIVE_OFF + state, PassiveFlag.PASSIVE_OFF ) return identity_key[1] @@ -2913,14 +3070,14 @@ class Mapper( @HasMemoized.memoized_attribute def _all_pk_cols(self): - collection = set() + collection: Set[ColumnClause[Any]] = set() for table in self.tables: collection.update(self._pks_by_table[table]) return collection @HasMemoized.memoized_attribute def _should_undefer_in_wildcard(self): - cols = set(self.primary_key) + cols: Set[ColumnElement[Any]] = set(self.primary_key) if self.polymorphic_on is not None: cols.add(self.polymorphic_on) return cols @@ -2951,11 +3108,11 @@ class Mapper( state = attributes.instance_state(obj) dict_ = attributes.instance_dict(obj) return self._get_committed_state_attr_by_column( - state, dict_, column, passive=attributes.PASSIVE_OFF + state, dict_, column, passive=PassiveFlag.PASSIVE_OFF ) def _get_committed_state_attr_by_column( - self, state, dict_, column, passive=attributes.PASSIVE_RETURN_NO_VALUE + self, state, dict_, column, passive=PassiveFlag.PASSIVE_RETURN_NO_VALUE ): prop = self._columntoproperty[column] @@ -2978,7 +3135,7 @@ class Mapper( col_attribute_names = set(attribute_names).intersection( state.mapper.column_attrs.keys() ) - tables = set( + tables: Set[FromClause] = set( chain( *[ sql_util.find_tables(c, check_columns=True) @@ -3002,7 +3159,7 @@ class Mapper( state, state.dict, leftcol, - passive=attributes.PASSIVE_NO_INITIALIZE, + passive=PassiveFlag.PASSIVE_NO_INITIALIZE, ) if leftval in orm_util._none_set: raise _OptGetColumnsNotAvailable() @@ -3014,7 +3171,7 @@ class Mapper( state, state.dict, rightcol, - passive=attributes.PASSIVE_NO_INITIALIZE, + passive=PassiveFlag.PASSIVE_NO_INITIALIZE, ) if rightval in orm_util._none_set: raise _OptGetColumnsNotAvailable() @@ -3022,7 +3179,7 @@ class Mapper( None, rightval, type_=binary.right.type ) - allconds = [] + allconds: List[ColumnElement[bool]] = [] start = False @@ -3035,6 +3192,9 @@ class Mapper( elif not isinstance(mapper.local_table, expression.TableClause): return None if start and not mapper.single: + assert mapper.inherits + assert not mapper.concrete + assert mapper.inherit_condition is not None allconds.append(mapper.inherit_condition) tables.add(mapper.local_table) @@ -3043,11 +3203,13 @@ class Mapper( # descendant-most class should all be present and joined to each # other. try: - allconds[0] = visitors.cloned_traverse( + _traversed = visitors.cloned_traverse( allconds[0], {}, {"binary": visit_binary} ) except _OptGetColumnsNotAvailable: return None + else: + allconds[0] = _traversed cond = sql.and_(*allconds) @@ -3145,6 +3307,8 @@ class Mapper( for pk in self.primary_key ] + in_expr: ColumnElement[Any] + if len(primary_key) > 1: in_expr = sql.tuple_(*primary_key) else: @@ -3209,11 +3373,22 @@ class Mapper( traverse all objects without relying on cascades. """ - visited_states = set() + visited_states: Set[InstanceState[Any]] = set() prp, mpp = object(), object() assert state.mapper.isa(self) + # this is actually a recursive structure, fully typing it seems + # a little too difficult for what it's worth here + visitables: Deque[ + Tuple[ + Deque[Any], + object, + Optional[InstanceState[Any]], + Optional[_InstanceDict], + ] + ] + visitables = deque( [(deque(state.mapper._props.values()), prp, state, state.dict)] ) @@ -3226,8 +3401,10 @@ class Mapper( if item_type is prp: prop = iterator.popleft() - if type_ not in prop.cascade: + if not prop.cascade or type_ not in prop.cascade: continue + assert parent_state is not None + assert parent_dict is not None queue = deque( prop.cascade_iterator( type_, @@ -3267,7 +3444,7 @@ class Mapper( @HasMemoized.memoized_attribute def _sorted_tables(self): - table_to_mapper = {} + table_to_mapper: Dict[Table, Mapper[Any]] = {} for mapper in self.base_mapper.self_and_descendants: for t in mapper.tables: @@ -3316,9 +3493,9 @@ class Mapper( ret[t] = table_to_mapper[t] return ret - def _memo(self, key, callable_): + def _memo(self, key: Any, callable_: Callable[[], _T]) -> _T: if key in self._memoized_values: - return self._memoized_values[key] + return cast(_T, self._memoized_values[key]) else: self._memoized_values[key] = value = callable_() return value @@ -3328,14 +3505,22 @@ class Mapper( """memoized map of tables to collections of columns to be synchronized upwards to the base mapper.""" - result = util.defaultdict(list) + result: util.defaultdict[ + Table, + List[ + Tuple[ + Mapper[Any], + List[Tuple[ColumnElement[Any], ColumnElement[Any]]], + ] + ], + ] = util.defaultdict(list) for table in self._sorted_tables: cols = set(table.c) for m in self.iterate_to_root(): if m._inherits_equated_pairs and cols.intersection( reduce( - set.union, + set.union, # type: ignore [l.proxy_set for l, r in m._inherits_equated_pairs], ) ): @@ -3440,7 +3625,7 @@ def _configure_registries(registries, cascade): else: return - Mapper.dispatch._for_class(Mapper).before_configured() + Mapper.dispatch._for_class(Mapper).before_configured() # type: ignore # noqa: E501 # initialize properties on all mappers # note that _mapper_registry is unordered, which # may randomly conceal/reveal issues related to @@ -3449,7 +3634,7 @@ def _configure_registries(registries, cascade): _do_configure_registries(registries, cascade) finally: _already_compiling = False - Mapper.dispatch._for_class(Mapper).after_configured() + Mapper.dispatch._for_class(Mapper).after_configured() # type: ignore @util.preload_module("sqlalchemy.orm.decl_api") @@ -3480,7 +3665,7 @@ def _do_configure_registries(registries, cascade): "Original exception was: %s" % (mapper, mapper._configure_failed) ) - e._configure_failed = mapper._configure_failed + e._configure_failed = mapper._configure_failed # type: ignore raise e if not mapper.configured: @@ -3636,7 +3821,7 @@ def _event_on_init(state, args, kwargs): instrumenting_mapper._set_polymorphic_identity(state) -class _ColumnMapping(dict): +class _ColumnMapping(Dict["ColumnElement[Any]", "MapperProperty[Any]"]): """Error reporting helper for mapper._columntoproperty.""" __slots__ = ("mapper",) |