diff options
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",) |