summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm/mapper.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/mapper.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/mapper.py')
-rw-r--r--lib/sqlalchemy/orm/mapper.py649
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",)