1# orm/mapper.py
2# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: https://www.opensource.org/licenses/mit-license.php
7# mypy: allow-untyped-defs, allow-untyped-calls
8
9"""Logic to map Python classes to and from selectables.
10
11Defines the :class:`~sqlalchemy.orm.mapper.Mapper` class, the central
12configurational unit which associates a class with a database table.
13
14This is a semi-private module; the main configurational API of the ORM is
15available in :class:`~sqlalchemy.orm.`.
16
17"""
18
19from __future__ import annotations
20
21from collections import deque
22from functools import reduce
23from itertools import chain
24import sys
25import threading
26from typing import Any
27from typing import Callable
28from typing import cast
29from typing import Collection
30from typing import Deque
31from typing import Dict
32from typing import FrozenSet
33from typing import Generic
34from typing import Iterable
35from typing import Iterator
36from typing import List
37from typing import Literal
38from typing import Mapping
39from typing import Optional
40from typing import Sequence
41from typing import Set
42from typing import Tuple
43from typing import Type
44from typing import TYPE_CHECKING
45from typing import TypeVar
46from typing import Union
47import weakref
48
49from . import attributes
50from . import exc as orm_exc
51from . import instrumentation
52from . import loading
53from . import properties
54from . import util as orm_util
55from ._typing import _O
56from .base import _class_to_mapper
57from .base import _parse_mapper_argument
58from .base import _state_mapper
59from .base import PassiveFlag
60from .base import state_str
61from .interfaces import _MappedAttribute
62from .interfaces import EXT_SKIP
63from .interfaces import InspectionAttr
64from .interfaces import MapperProperty
65from .interfaces import ORMEntityColumnsClauseRole
66from .interfaces import ORMFromClauseRole
67from .interfaces import StrategizedProperty
68from .path_registry import PathRegistry
69from .. import event
70from .. import exc as sa_exc
71from .. import inspection
72from .. import log
73from .. import schema
74from .. import sql
75from .. import util
76from ..event import dispatcher
77from ..event import EventTarget
78from ..sql import base as sql_base
79from ..sql import coercions
80from ..sql import expression
81from ..sql import operators
82from ..sql import roles
83from ..sql import TableClause
84from ..sql import util as sql_util
85from ..sql import visitors
86from ..sql.cache_key import MemoizedHasCacheKey
87from ..sql.elements import KeyedColumnElement
88from ..sql.schema import Column
89from ..sql.schema import Table
90from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
91from ..util import HasMemoized
92from ..util import HasMemoized_ro_memoized_attribute
93from ..util.typing import TupleAny
94from ..util.typing import Unpack
95
96if TYPE_CHECKING:
97 from ._typing import _IdentityKeyType
98 from ._typing import _InstanceDict
99 from ._typing import _ORMColumnExprArgument
100 from ._typing import _RegistryType
101 from .decl_api import registry
102 from .decl_base import _DeclarativeMapperConfig
103 from .dependency import _DependencyProcessor
104 from .descriptor_props import CompositeProperty
105 from .descriptor_props import SynonymProperty
106 from .events import MapperEvents
107 from .instrumentation import ClassManager
108 from .path_registry import _CachingEntityRegistry
109 from .properties import ColumnProperty
110 from .relationships import RelationshipProperty
111 from .state import InstanceState
112 from .util import ORMAdapter
113 from ..engine import Row
114 from ..engine import RowMapping
115 from ..sql._typing import _ColumnExpressionArgument
116 from ..sql._typing import _EquivalentColumnMap
117 from ..sql.base import _EntityNamespace
118 from ..sql.base import ReadOnlyColumnCollection
119 from ..sql.elements import ColumnClause
120 from ..sql.elements import ColumnElement
121 from ..sql.selectable import FromClause
122 from ..util import OrderedSet
123
124
125_T = TypeVar("_T", bound=Any)
126_MP = TypeVar("_MP", bound="MapperProperty[Any]")
127_Fn = TypeVar("_Fn", bound="Callable[..., Any]")
128
129
130_WithPolymorphicArg = Union[
131 Literal["*"],
132 Tuple[
133 Union[Literal["*"], Sequence[Union["Mapper[Any]", Type[Any]]]],
134 Optional["FromClause"],
135 ],
136 Sequence[Union["Mapper[Any]", Type[Any]]],
137]
138
139
140_mapper_registries: weakref.WeakKeyDictionary[_RegistryType, bool] = (
141 weakref.WeakKeyDictionary()
142)
143
144
145def _all_registries() -> Set[registry]:
146 with _CONFIGURE_MUTEX:
147 return set(_mapper_registries)
148
149
150def _unconfigured_mappers() -> Iterator[Mapper[Any]]:
151 for reg in _all_registries():
152 yield from reg._mappers_to_configure()
153
154
155_already_compiling = False
156
157
158# a constant returned by _get_attr_by_column to indicate
159# this mapper is not handling an attribute for a particular
160# column
161NO_ATTRIBUTE = util.symbol("NO_ATTRIBUTE")
162
163# lock used to synchronize the "mapper configure" step
164_CONFIGURE_MUTEX = threading.RLock()
165
166
167@inspection._self_inspects
168@log.class_logger
169class Mapper(
170 ORMFromClauseRole,
171 ORMEntityColumnsClauseRole[_O],
172 MemoizedHasCacheKey,
173 InspectionAttr,
174 log.Identified,
175 inspection.Inspectable["Mapper[_O]"],
176 EventTarget,
177 Generic[_O],
178):
179 """Defines an association between a Python class and a database table or
180 other relational structure, so that ORM operations against the class may
181 proceed.
182
183 The :class:`_orm.Mapper` object is instantiated using mapping methods
184 present on the :class:`_orm.registry` object. For information
185 about instantiating new :class:`_orm.Mapper` objects, see
186 :ref:`orm_mapping_classes_toplevel`.
187
188 """
189
190 dispatch: dispatcher[Mapper[_O]]
191
192 _dispose_called = False
193 _configure_failed: Any = False
194 _ready_for_configure = False
195
196 def __init__(
197 self,
198 class_: Type[_O],
199 local_table: Optional[FromClause] = None,
200 properties: Optional[Mapping[str, MapperProperty[Any]]] = None,
201 primary_key: Optional[Iterable[_ORMColumnExprArgument[Any]]] = None,
202 inherits: Optional[Union[Mapper[Any], Type[Any]]] = None,
203 inherit_condition: Optional[_ColumnExpressionArgument[bool]] = None,
204 inherit_foreign_keys: Optional[
205 Sequence[_ORMColumnExprArgument[Any]]
206 ] = None,
207 always_refresh: bool = False,
208 version_id_col: Optional[_ORMColumnExprArgument[Any]] = None,
209 version_id_generator: Optional[
210 Union[Literal[False], Callable[[Any], Any]]
211 ] = None,
212 polymorphic_on: Optional[
213 Union[_ORMColumnExprArgument[Any], str, MapperProperty[Any]]
214 ] = None,
215 _polymorphic_map: Optional[Dict[Any, Mapper[Any]]] = None,
216 polymorphic_identity: Optional[Any] = None,
217 concrete: bool = False,
218 with_polymorphic: Optional[_WithPolymorphicArg] = None,
219 polymorphic_abstract: bool = False,
220 polymorphic_load: Optional[Literal["selectin", "inline"]] = None,
221 allow_partial_pks: bool = True,
222 batch: bool = True,
223 column_prefix: Optional[str] = None,
224 include_properties: Optional[Sequence[str]] = None,
225 exclude_properties: Optional[Sequence[str]] = None,
226 passive_updates: bool = True,
227 passive_deletes: bool = False,
228 confirm_deleted_rows: bool = True,
229 eager_defaults: Literal[True, False, "auto"] = "auto",
230 legacy_is_orphan: bool = False,
231 _compiled_cache_size: int = 100,
232 ):
233 r"""Direct constructor for a new :class:`_orm.Mapper` object.
234
235 The :class:`_orm.Mapper` constructor is not called directly, and
236 is normally invoked through the
237 use of the :class:`_orm.registry` object through either the
238 :ref:`Declarative <orm_declarative_mapping>` or
239 :ref:`Imperative <orm_imperative_mapping>` mapping styles.
240
241 .. versionchanged:: 2.0 The public facing ``mapper()`` function is
242 removed; for a classical mapping configuration, use the
243 :meth:`_orm.registry.map_imperatively` method.
244
245 Parameters documented below may be passed to either the
246 :meth:`_orm.registry.map_imperatively` method, or may be passed in the
247 ``__mapper_args__`` declarative class attribute described at
248 :ref:`orm_declarative_mapper_options`.
249
250 :param class\_: The class to be mapped. When using Declarative,
251 this argument is automatically passed as the declared class
252 itself.
253
254 :param local_table: The :class:`_schema.Table` or other
255 :class:`_sql.FromClause` (i.e. selectable) to which the class is
256 mapped. May be ``None`` if this mapper inherits from another mapper
257 using single-table inheritance. When using Declarative, this
258 argument is automatically passed by the extension, based on what is
259 configured via the :attr:`_orm.DeclarativeBase.__table__` attribute
260 or via the :class:`_schema.Table` produced as a result of
261 the :attr:`_orm.DeclarativeBase.__tablename__` attribute being
262 present.
263
264 :param polymorphic_abstract: Indicates this class will be mapped in a
265 polymorphic hierarchy, but not directly instantiated. The class is
266 mapped normally, except that it has no requirement for a
267 :paramref:`_orm.Mapper.polymorphic_identity` within an inheritance
268 hierarchy. The class however must be part of a polymorphic
269 inheritance scheme which uses
270 :paramref:`_orm.Mapper.polymorphic_on` at the base.
271
272 .. versionadded:: 2.0
273
274 .. seealso::
275
276 :ref:`orm_inheritance_abstract_poly`
277
278 :param always_refresh: If True, all query operations for this mapped
279 class will overwrite all data within object instances that already
280 exist within the session, erasing any in-memory changes with
281 whatever information was loaded from the database. Usage of this
282 flag is highly discouraged; as an alternative, see the method
283 :meth:`_query.Query.populate_existing`.
284
285 :param allow_partial_pks: Defaults to True. Indicates that a
286 composite primary key with some NULL values should be considered as
287 possibly existing within the database. This affects whether a
288 mapper will assign an incoming row to an existing identity, as well
289 as if :meth:`.Session.merge` will check the database first for a
290 particular primary key value. A "partial primary key" can occur if
291 one has mapped to an OUTER JOIN, for example.
292
293 The :paramref:`.orm.Mapper.allow_partial_pks` parameter also
294 indicates to the ORM relationship lazy loader, when loading a
295 many-to-one related object, if a composite primary key that has
296 partial NULL values should result in an attempt to load from the
297 database, or if a load attempt is not necessary.
298
299 .. versionadded:: 2.0.36 :paramref:`.orm.Mapper.allow_partial_pks`
300 is consulted by the relationship lazy loader strategy, such that
301 when set to False, a SELECT for a composite primary key that
302 has partial NULL values will not be emitted.
303
304 :param batch: Defaults to ``True``, indicating that save operations
305 of multiple entities can be batched together for efficiency.
306 Setting to False indicates
307 that an instance will be fully saved before saving the next
308 instance. This is used in the extremely rare case that a
309 :class:`.MapperEvents` listener requires being called
310 in between individual row persistence operations.
311
312 :param column_prefix: A string which will be prepended
313 to the mapped attribute name when :class:`_schema.Column`
314 objects are automatically assigned as attributes to the
315 mapped class. Does not affect :class:`.Column` objects that
316 are mapped explicitly in the :paramref:`.Mapper.properties`
317 dictionary.
318
319 This parameter is typically useful with imperative mappings
320 that keep the :class:`.Table` object separate. Below, assuming
321 the ``user_table`` :class:`.Table` object has columns named
322 ``user_id``, ``user_name``, and ``password``::
323
324 class User(Base):
325 __table__ = user_table
326 __mapper_args__ = {"column_prefix": "_"}
327
328 The above mapping will assign the ``user_id``, ``user_name``, and
329 ``password`` columns to attributes named ``_user_id``,
330 ``_user_name``, and ``_password`` on the mapped ``User`` class.
331
332 The :paramref:`.Mapper.column_prefix` parameter is uncommon in
333 modern use. For dealing with reflected tables, a more flexible
334 approach to automating a naming scheme is to intercept the
335 :class:`.Column` objects as they are reflected; see the section
336 :ref:`mapper_automated_reflection_schemes` for notes on this usage
337 pattern.
338
339 :param concrete: If True, indicates this mapper should use concrete
340 table inheritance with its parent mapper.
341
342 See the section :ref:`concrete_inheritance` for an example.
343
344 :param confirm_deleted_rows: defaults to True; when a DELETE occurs
345 of one more rows based on specific primary keys, a warning is
346 emitted when the number of rows matched does not equal the number
347 of rows expected. This parameter may be set to False to handle the
348 case where database ON DELETE CASCADE rules may be deleting some of
349 those rows automatically. The warning may be changed to an
350 exception in a future release.
351
352 :param eager_defaults: if True, the ORM will immediately fetch the
353 value of server-generated default values after an INSERT or UPDATE,
354 rather than leaving them as expired to be fetched on next access.
355 This can be used for event schemes where the server-generated values
356 are needed immediately before the flush completes.
357
358 The fetch of values occurs either by using ``RETURNING`` inline
359 with the ``INSERT`` or ``UPDATE`` statement, or by adding an
360 additional ``SELECT`` statement subsequent to the ``INSERT`` or
361 ``UPDATE``, if the backend does not support ``RETURNING``.
362
363 The use of ``RETURNING`` is extremely performant in particular for
364 ``INSERT`` statements where SQLAlchemy can take advantage of
365 :ref:`insertmanyvalues <engine_insertmanyvalues>`, whereas the use of
366 an additional ``SELECT`` is relatively poor performing, adding
367 additional SQL round trips which would be unnecessary if these new
368 attributes are not to be accessed in any case.
369
370 For this reason, :paramref:`.Mapper.eager_defaults` defaults to the
371 string value ``"auto"``, which indicates that server defaults for
372 INSERT should be fetched using ``RETURNING`` if the backing database
373 supports it and if the dialect in use supports "insertmanyreturning"
374 for an INSERT statement. If the backing database does not support
375 ``RETURNING`` or "insertmanyreturning" is not available, server
376 defaults will not be fetched.
377
378 .. versionchanged:: 2.0.0rc1 added the "auto" option for
379 :paramref:`.Mapper.eager_defaults`
380
381 .. seealso::
382
383 :ref:`orm_server_defaults`
384
385 .. versionchanged:: 2.0.0 RETURNING now works with multiple rows
386 INSERTed at once using the
387 :ref:`insertmanyvalues <engine_insertmanyvalues>` feature, which
388 among other things allows the :paramref:`.Mapper.eager_defaults`
389 feature to be very performant on supporting backends.
390
391 :param exclude_properties: A list or set of string column names to
392 be excluded from mapping.
393
394 .. seealso::
395
396 :ref:`include_exclude_cols`
397
398 :param include_properties: An inclusive list or set of string column
399 names to map.
400
401 .. seealso::
402
403 :ref:`include_exclude_cols`
404
405 :param inherits: A mapped class or the corresponding
406 :class:`_orm.Mapper`
407 of one indicating a superclass to which this :class:`_orm.Mapper`
408 should *inherit* from. The mapped class here must be a subclass
409 of the other mapper's class. When using Declarative, this argument
410 is passed automatically as a result of the natural class
411 hierarchy of the declared classes.
412
413 .. seealso::
414
415 :ref:`inheritance_toplevel`
416
417 :param inherit_condition: For joined table inheritance, a SQL
418 expression which will
419 define how the two tables are joined; defaults to a natural join
420 between the two tables.
421
422 :param inherit_foreign_keys: When ``inherit_condition`` is used and
423 the columns present are missing a :class:`_schema.ForeignKey`
424 configuration, this parameter can be used to specify which columns
425 are "foreign". In most cases can be left as ``None``.
426
427 :param legacy_is_orphan: Boolean, defaults to ``False``.
428 When ``True``, specifies that "legacy" orphan consideration
429 is to be applied to objects mapped by this mapper, which means
430 that a pending (that is, not persistent) object is auto-expunged
431 from an owning :class:`.Session` only when it is de-associated
432 from *all* parents that specify a ``delete-orphan`` cascade towards
433 this mapper. The new default behavior is that the object is
434 auto-expunged when it is de-associated with *any* of its parents
435 that specify ``delete-orphan`` cascade. This behavior is more
436 consistent with that of a persistent object, and allows behavior to
437 be consistent in more scenarios independently of whether or not an
438 orphan object has been flushed yet or not.
439
440 See the change note and example at :ref:`legacy_is_orphan_addition`
441 for more detail on this change.
442
443 :param passive_deletes: Indicates DELETE behavior of foreign key
444 columns when a joined-table inheritance entity is being deleted.
445 Defaults to ``False`` for a base mapper; for an inheriting mapper,
446 defaults to ``False`` unless the value is set to ``True``
447 on the superclass mapper.
448
449 When ``True``, it is assumed that ON DELETE CASCADE is configured
450 on the foreign key relationships that link this mapper's table
451 to its superclass table, so that when the unit of work attempts
452 to delete the entity, it need only emit a DELETE statement for the
453 superclass table, and not this table.
454
455 When ``False``, a DELETE statement is emitted for this mapper's
456 table individually. If the primary key attributes local to this
457 table are unloaded, then a SELECT must be emitted in order to
458 validate these attributes; note that the primary key columns
459 of a joined-table subclass are not part of the "primary key" of
460 the object as a whole.
461
462 Note that a value of ``True`` is **always** forced onto the
463 subclass mappers; that is, it's not possible for a superclass
464 to specify passive_deletes without this taking effect for
465 all subclass mappers.
466
467 .. seealso::
468
469 :ref:`passive_deletes` - description of similar feature as
470 used with :func:`_orm.relationship`
471
472 :paramref:`.mapper.passive_updates` - supporting ON UPDATE
473 CASCADE for joined-table inheritance mappers
474
475 :param passive_updates: Indicates UPDATE behavior of foreign key
476 columns when a primary key column changes on a joined-table
477 inheritance mapping. Defaults to ``True``.
478
479 When True, it is assumed that ON UPDATE CASCADE is configured on
480 the foreign key in the database, and that the database will handle
481 propagation of an UPDATE from a source column to dependent columns
482 on joined-table rows.
483
484 When False, it is assumed that the database does not enforce
485 referential integrity and will not be issuing its own CASCADE
486 operation for an update. The unit of work process will
487 emit an UPDATE statement for the dependent columns during a
488 primary key change.
489
490 .. seealso::
491
492 :ref:`passive_updates` - description of a similar feature as
493 used with :func:`_orm.relationship`
494
495 :paramref:`.mapper.passive_deletes` - supporting ON DELETE
496 CASCADE for joined-table inheritance mappers
497
498 :param polymorphic_load: Specifies "polymorphic loading" behavior
499 for a subclass in an inheritance hierarchy (joined and single
500 table inheritance only). Valid values are:
501
502 * "'inline'" - specifies this class should be part of
503 the "with_polymorphic" mappers, e.g. its columns will be included
504 in a SELECT query against the base.
505
506 * "'selectin'" - specifies that when instances of this class
507 are loaded, an additional SELECT will be emitted to retrieve
508 the columns specific to this subclass. The SELECT uses
509 IN to fetch multiple subclasses at once.
510
511 .. seealso::
512
513 :ref:`with_polymorphic_mapper_config`
514
515 :ref:`polymorphic_selectin`
516
517 :param polymorphic_on: Specifies the column, attribute, or
518 SQL expression used to determine the target class for an
519 incoming row, when inheriting classes are present.
520
521 May be specified as a string attribute name, or as a SQL
522 expression such as a :class:`_schema.Column` or in a Declarative
523 mapping a :func:`_orm.mapped_column` object. It is typically
524 expected that the SQL expression corresponds to a column in the
525 base-most mapped :class:`.Table`::
526
527 class Employee(Base):
528 __tablename__ = "employee"
529
530 id: Mapped[int] = mapped_column(primary_key=True)
531 discriminator: Mapped[str] = mapped_column(String(50))
532
533 __mapper_args__ = {
534 "polymorphic_on": discriminator,
535 "polymorphic_identity": "employee",
536 }
537
538 It may also be specified
539 as a SQL expression, as in this example where we
540 use the :func:`.case` construct to provide a conditional
541 approach::
542
543 class Employee(Base):
544 __tablename__ = "employee"
545
546 id: Mapped[int] = mapped_column(primary_key=True)
547 discriminator: Mapped[str] = mapped_column(String(50))
548
549 __mapper_args__ = {
550 "polymorphic_on": case(
551 (discriminator == "EN", "engineer"),
552 (discriminator == "MA", "manager"),
553 else_="employee",
554 ),
555 "polymorphic_identity": "employee",
556 }
557
558 It may also refer to any attribute using its string name,
559 which is of particular use when using annotated column
560 configurations::
561
562 class Employee(Base):
563 __tablename__ = "employee"
564
565 id: Mapped[int] = mapped_column(primary_key=True)
566 discriminator: Mapped[str]
567
568 __mapper_args__ = {
569 "polymorphic_on": "discriminator",
570 "polymorphic_identity": "employee",
571 }
572
573 When setting ``polymorphic_on`` to reference an
574 attribute or expression that's not present in the
575 locally mapped :class:`_schema.Table`, yet the value
576 of the discriminator should be persisted to the database,
577 the value of the
578 discriminator is not automatically set on new
579 instances; this must be handled by the user,
580 either through manual means or via event listeners.
581 A typical approach to establishing such a listener
582 looks like::
583
584 from sqlalchemy import event
585 from sqlalchemy.orm import object_mapper
586
587
588 @event.listens_for(Employee, "init", propagate=True)
589 def set_identity(instance, *arg, **kw):
590 mapper = object_mapper(instance)
591 instance.discriminator = mapper.polymorphic_identity
592
593 Where above, we assign the value of ``polymorphic_identity``
594 for the mapped class to the ``discriminator`` attribute,
595 thus persisting the value to the ``discriminator`` column
596 in the database.
597
598 .. warning::
599
600 Currently, **only one discriminator column may be set**, typically
601 on the base-most class in the hierarchy. "Cascading" polymorphic
602 columns are not yet supported.
603
604 .. seealso::
605
606 :ref:`inheritance_toplevel`
607
608 :param polymorphic_identity: Specifies the value which
609 identifies this particular class as returned by the column expression
610 referred to by the :paramref:`_orm.Mapper.polymorphic_on` setting. As
611 rows are received, the value corresponding to the
612 :paramref:`_orm.Mapper.polymorphic_on` column expression is compared
613 to this value, indicating which subclass should be used for the newly
614 reconstructed object.
615
616 .. seealso::
617
618 :ref:`inheritance_toplevel`
619
620 :param properties: A dictionary mapping the string names of object
621 attributes to :class:`.MapperProperty` instances, which define the
622 persistence behavior of that attribute. Note that
623 :class:`_schema.Column`
624 objects present in
625 the mapped :class:`_schema.Table` are automatically placed into
626 ``ColumnProperty`` instances upon mapping, unless overridden.
627 When using Declarative, this argument is passed automatically,
628 based on all those :class:`.MapperProperty` instances declared
629 in the declared class body.
630
631 .. seealso::
632
633 :ref:`orm_mapping_properties` - in the
634 :ref:`orm_mapping_classes_toplevel`
635
636 :param primary_key: A list of :class:`_schema.Column`
637 objects, or alternatively string names of attribute names which
638 refer to :class:`_schema.Column`, which define
639 the primary key to be used against this mapper's selectable unit.
640 This is normally simply the primary key of the ``local_table``, but
641 can be overridden here.
642
643 .. versionchanged:: 2.0.2 :paramref:`_orm.Mapper.primary_key`
644 arguments may be indicated as string attribute names as well.
645
646 .. seealso::
647
648 :ref:`mapper_primary_key` - background and example use
649
650 :param version_id_col: A :class:`_schema.Column`
651 that will be used to keep a running version id of rows
652 in the table. This is used to detect concurrent updates or
653 the presence of stale data in a flush. The methodology is to
654 detect if an UPDATE statement does not match the last known
655 version id, a
656 :class:`~sqlalchemy.orm.exc.StaleDataError` exception is
657 thrown.
658 By default, the column must be of :class:`.Integer` type,
659 unless ``version_id_generator`` specifies an alternative version
660 generator.
661
662 .. seealso::
663
664 :ref:`mapper_version_counter` - discussion of version counting
665 and rationale.
666
667 :param version_id_generator: Define how new version ids should
668 be generated. Defaults to ``None``, which indicates that
669 a simple integer counting scheme be employed. To provide a custom
670 versioning scheme, provide a callable function of the form::
671
672 def generate_version(version):
673 return next_version
674
675 Alternatively, server-side versioning functions such as triggers,
676 or programmatic versioning schemes outside of the version id
677 generator may be used, by specifying the value ``False``.
678 Please see :ref:`server_side_version_counter` for a discussion
679 of important points when using this option.
680
681 .. seealso::
682
683 :ref:`custom_version_counter`
684
685 :ref:`server_side_version_counter`
686
687
688 :param with_polymorphic: A tuple in the form ``(<classes>,
689 <selectable>)`` indicating the default style of "polymorphic"
690 loading, that is, which tables are queried at once. <classes> is
691 any single or list of mappers and/or classes indicating the
692 inherited classes that should be loaded at once. The special value
693 ``'*'`` may be used to indicate all descending classes should be
694 loaded immediately. The second tuple argument <selectable>
695 indicates a selectable that will be used to query for multiple
696 classes.
697
698 The :paramref:`_orm.Mapper.polymorphic_load` parameter may be
699 preferable over the use of :paramref:`_orm.Mapper.with_polymorphic`
700 in modern mappings to indicate a per-subclass technique of
701 indicating polymorphic loading styles.
702
703 .. seealso::
704
705 :ref:`with_polymorphic_mapper_config`
706
707 """
708 self.class_ = util.assert_arg_type(class_, type, "class_")
709 self._sort_key = "%s.%s" % (
710 self.class_.__module__,
711 self.class_.__name__,
712 )
713
714 self._primary_key_argument = util.to_list(primary_key)
715
716 self.always_refresh = always_refresh
717
718 if isinstance(version_id_col, MapperProperty):
719 self.version_id_prop = version_id_col
720 self.version_id_col = None
721 else:
722 self.version_id_col = (
723 coercions.expect(
724 roles.ColumnArgumentOrKeyRole,
725 version_id_col,
726 argname="version_id_col",
727 )
728 if version_id_col is not None
729 else None
730 )
731
732 if version_id_generator is False:
733 self.version_id_generator = False
734 elif version_id_generator is None:
735 self.version_id_generator = lambda x: (x or 0) + 1
736 else:
737 self.version_id_generator = version_id_generator
738
739 self.concrete = concrete
740 self.single = False
741
742 if inherits is not None:
743 self.inherits = _parse_mapper_argument(inherits)
744 else:
745 self.inherits = None
746
747 if local_table is not None:
748 self.local_table = coercions.expect(
749 roles.FromClauseRole,
750 local_table,
751 disable_inspection=True,
752 argname="local_table",
753 )
754 elif self.inherits:
755 # note this is a new flow as of 2.0 so that
756 # .local_table need not be Optional
757 self.local_table = self.inherits.local_table
758 self.single = True
759 else:
760 raise sa_exc.ArgumentError(
761 f"Mapper[{self.class_.__name__}(None)] has None for a "
762 "primary table argument and does not specify 'inherits'"
763 )
764
765 if inherit_condition is not None:
766 self.inherit_condition = coercions.expect(
767 roles.OnClauseRole, inherit_condition
768 )
769 else:
770 self.inherit_condition = None
771
772 self.inherit_foreign_keys = inherit_foreign_keys
773 self._init_properties = dict(properties) if properties else {}
774 self._delete_orphans = []
775 self.batch = batch
776 self.eager_defaults = eager_defaults
777 self.column_prefix = column_prefix
778
779 # interim - polymorphic_on is further refined in
780 # _configure_polymorphic_setter
781 self.polymorphic_on = (
782 coercions.expect( # type: ignore[assignment]
783 roles.ColumnArgumentOrKeyRole,
784 polymorphic_on,
785 argname="polymorphic_on",
786 )
787 if polymorphic_on is not None
788 else None
789 )
790 self.polymorphic_abstract = polymorphic_abstract
791 self._dependency_processors = []
792 self.validators = util.EMPTY_DICT
793 self.passive_updates = passive_updates
794 self.passive_deletes = passive_deletes
795 self.legacy_is_orphan = legacy_is_orphan
796 self._clause_adapter = None
797 self._requires_row_aliasing = False
798 self._inherits_equated_pairs = None
799 self._memoized_values = {}
800 self._compiled_cache_size = _compiled_cache_size
801 self._reconstructor = None
802 self.allow_partial_pks = allow_partial_pks
803
804 if self.inherits and not self.concrete:
805 self.confirm_deleted_rows = False
806 else:
807 self.confirm_deleted_rows = confirm_deleted_rows
808
809 self._set_with_polymorphic(with_polymorphic)
810 self.polymorphic_load = polymorphic_load
811
812 # our 'polymorphic identity', a string name that when located in a
813 # result set row indicates this Mapper should be used to construct
814 # the object instance for that row.
815 self.polymorphic_identity = polymorphic_identity
816
817 # a dictionary of 'polymorphic identity' names, associating those
818 # names with Mappers that will be used to construct object instances
819 # upon a select operation.
820 if _polymorphic_map is None:
821 self.polymorphic_map = {}
822 else:
823 self.polymorphic_map = _polymorphic_map
824
825 if include_properties is not None:
826 self.include_properties = util.to_set(include_properties)
827 else:
828 self.include_properties = None
829 if exclude_properties:
830 self.exclude_properties = util.to_set(exclude_properties)
831 else:
832 self.exclude_properties = None
833
834 # prevent this mapper from being constructed
835 # while a configure_mappers() is occurring (and defer a
836 # configure_mappers() until construction succeeds)
837 with _CONFIGURE_MUTEX:
838 cast("MapperEvents", self.dispatch._events)._new_mapper_instance(
839 class_, self
840 )
841 self._configure_inheritance()
842 self._configure_class_instrumentation()
843 self._configure_properties()
844 self._configure_polymorphic_setter()
845 self._configure_pks()
846 self.registry._flag_new_mapper(self)
847 self._log("constructed")
848 self._expire_memoizations()
849
850 self.dispatch.after_mapper_constructed(self, self.class_)
851
852 def _prefer_eager_defaults(self, dialect, table):
853 if self.eager_defaults == "auto":
854 if not table.implicit_returning:
855 return False
856
857 return (
858 table in self._server_default_col_keys
859 and dialect.insert_executemany_returning
860 )
861 else:
862 return self.eager_defaults
863
864 def _gen_cache_key(self, anon_map, bindparams):
865 return (self,)
866
867 # ### BEGIN
868 # ATTRIBUTE DECLARATIONS START HERE
869
870 is_mapper = True
871 """Part of the inspection API."""
872
873 represents_outer_join = False
874
875 registry: _RegistryType
876
877 @property
878 def mapper(self) -> Mapper[_O]:
879 """Part of the inspection API.
880
881 Returns self.
882
883 """
884 return self
885
886 @property
887 def entity(self):
888 r"""Part of the inspection API.
889
890 Returns self.class\_.
891
892 """
893 return self.class_
894
895 class_: Type[_O]
896 """The class to which this :class:`_orm.Mapper` is mapped."""
897
898 _identity_class: Type[_O]
899
900 _delete_orphans: List[Tuple[str, Type[Any]]]
901 _dependency_processors: List[_DependencyProcessor]
902 _memoized_values: Dict[Any, Callable[[], Any]]
903 _inheriting_mappers: util.WeakSequence[Mapper[Any]]
904 _all_tables: Set[TableClause]
905 _polymorphic_attr_key: Optional[str]
906
907 _pks_by_table: Dict[FromClause, OrderedSet[ColumnClause[Any]]]
908 _cols_by_table: Dict[FromClause, OrderedSet[ColumnElement[Any]]]
909
910 _props: util.OrderedDict[str, MapperProperty[Any]]
911 _init_properties: Dict[str, MapperProperty[Any]]
912
913 _columntoproperty: _ColumnMapping
914
915 _set_polymorphic_identity: Optional[Callable[[InstanceState[_O]], None]]
916 _validate_polymorphic_identity: Optional[
917 Callable[[Mapper[_O], InstanceState[_O], _InstanceDict], None]
918 ]
919
920 tables: Sequence[TableClause]
921 """A sequence containing the collection of :class:`_schema.Table`
922 or :class:`_schema.TableClause` objects which this :class:`_orm.Mapper`
923 is aware of.
924
925 If the mapper is mapped to a :class:`_expression.Join`, or an
926 :class:`_expression.Alias`
927 representing a :class:`_expression.Select`, the individual
928 :class:`_schema.Table`
929 objects that comprise the full construct will be represented here.
930
931 This is a *read only* attribute determined during mapper construction.
932 Behavior is undefined if directly modified.
933
934 """
935
936 validators: util.immutabledict[str, Tuple[str, Dict[str, Any]]]
937 """An immutable dictionary of attributes which have been decorated
938 using the :func:`_orm.validates` decorator.
939
940 The dictionary contains string attribute names as keys
941 mapped to the actual validation method.
942
943 """
944
945 always_refresh: bool
946 allow_partial_pks: bool
947 version_id_col: Optional[ColumnElement[Any]]
948
949 with_polymorphic: Optional[
950 Tuple[
951 Union[Literal["*"], Sequence[Union[Mapper[Any], Type[Any]]]],
952 Optional[FromClause],
953 ]
954 ]
955
956 version_id_generator: Optional[Union[Literal[False], Callable[[Any], Any]]]
957
958 local_table: FromClause
959 """The immediate :class:`_expression.FromClause` to which this
960 :class:`_orm.Mapper` refers.
961
962 Typically is an instance of :class:`_schema.Table`, may be any
963 :class:`.FromClause`.
964
965 The "local" table is the
966 selectable that the :class:`_orm.Mapper` is directly responsible for
967 managing from an attribute access and flush perspective. For
968 non-inheriting mappers, :attr:`.Mapper.local_table` will be the same
969 as :attr:`.Mapper.persist_selectable`. For inheriting mappers,
970 :attr:`.Mapper.local_table` refers to the specific portion of
971 :attr:`.Mapper.persist_selectable` that includes the columns to which
972 this :class:`.Mapper` is loading/persisting, such as a particular
973 :class:`.Table` within a join.
974
975 .. seealso::
976
977 :attr:`_orm.Mapper.persist_selectable`.
978
979 :attr:`_orm.Mapper.selectable`.
980
981 """
982
983 persist_selectable: FromClause
984 """The :class:`_expression.FromClause` to which this :class:`_orm.Mapper`
985 is mapped.
986
987 Typically is an instance of :class:`_schema.Table`, may be any
988 :class:`.FromClause`.
989
990 The :attr:`_orm.Mapper.persist_selectable` is similar to
991 :attr:`.Mapper.local_table`, but represents the :class:`.FromClause` that
992 represents the inheriting class hierarchy overall in an inheritance
993 scenario.
994
995 :attr.`.Mapper.persist_selectable` is also separate from the
996 :attr:`.Mapper.selectable` attribute, the latter of which may be an
997 alternate subquery used for selecting columns.
998 :attr.`.Mapper.persist_selectable` is oriented towards columns that
999 will be written on a persist operation.
1000
1001 .. seealso::
1002
1003 :attr:`_orm.Mapper.selectable`.
1004
1005 :attr:`_orm.Mapper.local_table`.
1006
1007 """
1008
1009 inherits: Optional[Mapper[Any]]
1010 """References the :class:`_orm.Mapper` which this :class:`_orm.Mapper`
1011 inherits from, if any.
1012
1013 """
1014
1015 inherit_condition: Optional[ColumnElement[bool]]
1016
1017 configured: bool = False
1018 """Represent ``True`` if this :class:`_orm.Mapper` has been configured.
1019
1020 This is a *read only* attribute determined during mapper construction.
1021 Behavior is undefined if directly modified.
1022
1023 .. seealso::
1024
1025 :func:`.configure_mappers`.
1026
1027 """
1028
1029 concrete: bool
1030 """Represent ``True`` if this :class:`_orm.Mapper` is a concrete
1031 inheritance mapper.
1032
1033 This is a *read only* attribute determined during mapper construction.
1034 Behavior is undefined if directly modified.
1035
1036 """
1037
1038 primary_key: Tuple[ColumnElement[Any], ...]
1039 """An iterable containing the collection of :class:`_schema.Column`
1040 objects
1041 which comprise the 'primary key' of the mapped table, from the
1042 perspective of this :class:`_orm.Mapper`.
1043
1044 This list is against the selectable in
1045 :attr:`_orm.Mapper.persist_selectable`.
1046 In the case of inheriting mappers, some columns may be managed by a
1047 superclass mapper. For example, in the case of a
1048 :class:`_expression.Join`, the
1049 primary key is determined by all of the primary key columns across all
1050 tables referenced by the :class:`_expression.Join`.
1051
1052 The list is also not necessarily the same as the primary key column
1053 collection associated with the underlying tables; the :class:`_orm.Mapper`
1054 features a ``primary_key`` argument that can override what the
1055 :class:`_orm.Mapper` considers as primary key columns.
1056
1057 This is a *read only* attribute determined during mapper construction.
1058 Behavior is undefined if directly modified.
1059
1060 """
1061
1062 class_manager: ClassManager[_O]
1063 """The :class:`.ClassManager` which maintains event listeners
1064 and class-bound descriptors for this :class:`_orm.Mapper`.
1065
1066 This is a *read only* attribute determined during mapper construction.
1067 Behavior is undefined if directly modified.
1068
1069 """
1070
1071 single: bool
1072 """Represent ``True`` if this :class:`_orm.Mapper` is a single table
1073 inheritance mapper.
1074
1075 :attr:`_orm.Mapper.local_table` will be ``None`` if this flag is set.
1076
1077 This is a *read only* attribute determined during mapper construction.
1078 Behavior is undefined if directly modified.
1079
1080 """
1081
1082 polymorphic_on: Optional[KeyedColumnElement[Any]]
1083 """The :class:`_schema.Column` or SQL expression specified as the
1084 ``polymorphic_on`` argument
1085 for this :class:`_orm.Mapper`, within an inheritance scenario.
1086
1087 This attribute is normally a :class:`_schema.Column` instance but
1088 may also be an expression, such as one derived from
1089 :func:`.cast`.
1090
1091 This is a *read only* attribute determined during mapper construction.
1092 Behavior is undefined if directly modified.
1093
1094 """
1095
1096 polymorphic_map: Dict[Any, Mapper[Any]]
1097 """A mapping of "polymorphic identity" identifiers mapped to
1098 :class:`_orm.Mapper` instances, within an inheritance scenario.
1099
1100 The identifiers can be of any type which is comparable to the
1101 type of column represented by :attr:`_orm.Mapper.polymorphic_on`.
1102
1103 An inheritance chain of mappers will all reference the same
1104 polymorphic map object. The object is used to correlate incoming
1105 result rows to target mappers.
1106
1107 This is a *read only* attribute determined during mapper construction.
1108 Behavior is undefined if directly modified.
1109
1110 """
1111
1112 polymorphic_identity: Optional[Any]
1113 """Represent an identifier which is matched against the
1114 :attr:`_orm.Mapper.polymorphic_on` column during result row loading.
1115
1116 Used only with inheritance, this object can be of any type which is
1117 comparable to the type of column represented by
1118 :attr:`_orm.Mapper.polymorphic_on`.
1119
1120 This is a *read only* attribute determined during mapper construction.
1121 Behavior is undefined if directly modified.
1122
1123 """
1124
1125 base_mapper: Mapper[Any]
1126 """The base-most :class:`_orm.Mapper` in an inheritance chain.
1127
1128 In a non-inheriting scenario, this attribute will always be this
1129 :class:`_orm.Mapper`. In an inheritance scenario, it references
1130 the :class:`_orm.Mapper` which is parent to all other :class:`_orm.Mapper`
1131 objects in the inheritance chain.
1132
1133 This is a *read only* attribute determined during mapper construction.
1134 Behavior is undefined if directly modified.
1135
1136 """
1137
1138 _columns: sql_base.WriteableColumnCollection[str, Column[Any]]
1139
1140 @util.memoized_property
1141 def _path_registry(self) -> _CachingEntityRegistry:
1142 return PathRegistry.per_mapper(self)
1143
1144 def _configure_inheritance(self):
1145 """Configure settings related to inheriting and/or inherited mappers
1146 being present."""
1147
1148 # a set of all mappers which inherit from this one.
1149 self._inheriting_mappers = util.WeakSequence()
1150
1151 if self.inherits:
1152 if not issubclass(self.class_, self.inherits.class_):
1153 raise sa_exc.ArgumentError(
1154 "Class '%s' does not inherit from '%s'"
1155 % (self.class_.__name__, self.inherits.class_.__name__)
1156 )
1157
1158 self.dispatch._update(self.inherits.dispatch)
1159
1160 if self.single:
1161 self.persist_selectable = self.inherits.persist_selectable
1162 elif self.local_table is not self.inherits.local_table:
1163 if self.concrete:
1164 self.persist_selectable = self.local_table
1165 for mapper in self.iterate_to_root():
1166 if mapper.polymorphic_on is not None:
1167 mapper._requires_row_aliasing = True
1168 else:
1169 if self.inherit_condition is None:
1170 # figure out inherit condition from our table to the
1171 # immediate table of the inherited mapper, not its
1172 # full table which could pull in other stuff we don't
1173 # want (allows test/inheritance.InheritTest4 to pass)
1174 try:
1175 self.inherit_condition = sql_util.join_condition(
1176 self.inherits.local_table, self.local_table
1177 )
1178 except sa_exc.NoForeignKeysError as nfe:
1179 assert self.inherits.local_table is not None
1180 assert self.local_table is not None
1181 raise sa_exc.NoForeignKeysError(
1182 "Can't determine the inherit condition "
1183 "between inherited table '%s' and "
1184 "inheriting "
1185 "table '%s'; tables have no "
1186 "foreign key relationships established. "
1187 "Please ensure the inheriting table has "
1188 "a foreign key relationship to the "
1189 "inherited "
1190 "table, or provide an "
1191 "'on clause' using "
1192 "the 'inherit_condition' mapper argument."
1193 % (
1194 self.inherits.local_table.description,
1195 self.local_table.description,
1196 )
1197 ) from nfe
1198 except sa_exc.AmbiguousForeignKeysError as afe:
1199 assert self.inherits.local_table is not None
1200 assert self.local_table is not None
1201 raise sa_exc.AmbiguousForeignKeysError(
1202 "Can't determine the inherit condition "
1203 "between inherited table '%s' and "
1204 "inheriting "
1205 "table '%s'; tables have more than one "
1206 "foreign key relationship established. "
1207 "Please specify the 'on clause' using "
1208 "the 'inherit_condition' mapper argument."
1209 % (
1210 self.inherits.local_table.description,
1211 self.local_table.description,
1212 )
1213 ) from afe
1214 assert self.inherits.persist_selectable is not None
1215 self.persist_selectable = sql.join(
1216 self.inherits.persist_selectable,
1217 self.local_table,
1218 self.inherit_condition,
1219 )
1220
1221 fks = util.to_set(self.inherit_foreign_keys)
1222 self._inherits_equated_pairs = sql_util.criterion_as_pairs(
1223 self.persist_selectable.onclause,
1224 consider_as_foreign_keys=fks,
1225 )
1226 else:
1227 self.persist_selectable = self.local_table
1228
1229 if self.polymorphic_identity is None:
1230 self._identity_class = self.class_
1231
1232 if (
1233 not self.polymorphic_abstract
1234 and self.inherits.base_mapper.polymorphic_on is not None
1235 ):
1236 util.warn(
1237 f"{self} does not indicate a 'polymorphic_identity', "
1238 "yet is part of an inheritance hierarchy that has a "
1239 f"'polymorphic_on' column of "
1240 f"'{self.inherits.base_mapper.polymorphic_on}'. "
1241 "If this is an intermediary class that should not be "
1242 "instantiated, the class may either be left unmapped, "
1243 "or may include the 'polymorphic_abstract=True' "
1244 "parameter in its Mapper arguments. To leave the "
1245 "class unmapped when using Declarative, set the "
1246 "'__abstract__ = True' attribute on the class."
1247 )
1248 elif self.concrete:
1249 self._identity_class = self.class_
1250 else:
1251 self._identity_class = self.inherits._identity_class
1252
1253 if self.version_id_col is None:
1254 self.version_id_col = self.inherits.version_id_col
1255 self.version_id_generator = self.inherits.version_id_generator
1256 elif (
1257 self.inherits.version_id_col is not None
1258 and self.version_id_col is not self.inherits.version_id_col
1259 ):
1260 util.warn(
1261 "Inheriting version_id_col '%s' does not match inherited "
1262 "version_id_col '%s' and will not automatically populate "
1263 "the inherited versioning column. "
1264 "version_id_col should only be specified on "
1265 "the base-most mapper that includes versioning."
1266 % (
1267 self.version_id_col.description,
1268 self.inherits.version_id_col.description,
1269 )
1270 )
1271
1272 self.polymorphic_map = self.inherits.polymorphic_map
1273 self.batch = self.inherits.batch
1274 self.inherits._inheriting_mappers.append(self)
1275 self.base_mapper = self.inherits.base_mapper
1276 self.passive_updates = self.inherits.passive_updates
1277 self.passive_deletes = (
1278 self.inherits.passive_deletes or self.passive_deletes
1279 )
1280 self._all_tables = self.inherits._all_tables
1281
1282 if self.polymorphic_identity is not None:
1283 if self.polymorphic_identity in self.polymorphic_map:
1284 util.warn(
1285 "Reassigning polymorphic association for identity %r "
1286 "from %r to %r: Check for duplicate use of %r as "
1287 "value for polymorphic_identity."
1288 % (
1289 self.polymorphic_identity,
1290 self.polymorphic_map[self.polymorphic_identity],
1291 self,
1292 self.polymorphic_identity,
1293 )
1294 )
1295 self.polymorphic_map[self.polymorphic_identity] = self
1296
1297 if self.polymorphic_load and self.concrete:
1298 raise sa_exc.ArgumentError(
1299 "polymorphic_load is not currently supported "
1300 "with concrete table inheritance"
1301 )
1302 if self.polymorphic_load == "inline":
1303 self.inherits._add_with_polymorphic_subclass(self)
1304 elif self.polymorphic_load == "selectin":
1305 pass
1306 elif self.polymorphic_load is not None:
1307 raise sa_exc.ArgumentError(
1308 "unknown argument for polymorphic_load: %r"
1309 % self.polymorphic_load
1310 )
1311
1312 else:
1313 self._all_tables = set()
1314 self.base_mapper = self
1315 assert self.local_table is not None
1316 self.persist_selectable = self.local_table
1317 if self.polymorphic_identity is not None:
1318 self.polymorphic_map[self.polymorphic_identity] = self
1319 self._identity_class = self.class_
1320
1321 if self.persist_selectable is None:
1322 raise sa_exc.ArgumentError(
1323 "Mapper '%s' does not have a persist_selectable specified."
1324 % self
1325 )
1326
1327 def _set_with_polymorphic(
1328 self, with_polymorphic: Optional[_WithPolymorphicArg]
1329 ) -> None:
1330 if with_polymorphic == "*":
1331 self.with_polymorphic = ("*", None)
1332 elif isinstance(with_polymorphic, (tuple, list)):
1333 if isinstance(with_polymorphic[0], (str, tuple, list)):
1334 self.with_polymorphic = cast(
1335 """Tuple[
1336 Union[
1337 Literal["*"],
1338 Sequence[Union["Mapper[Any]", Type[Any]]],
1339 ],
1340 Optional["FromClause"],
1341 ]""",
1342 with_polymorphic,
1343 )
1344 else:
1345 self.with_polymorphic = (with_polymorphic, None)
1346 elif with_polymorphic is not None:
1347 raise sa_exc.ArgumentError(
1348 f"Invalid setting for with_polymorphic: {with_polymorphic!r}"
1349 )
1350 else:
1351 self.with_polymorphic = None
1352
1353 if self.with_polymorphic and self.with_polymorphic[1] is not None:
1354 self.with_polymorphic = (
1355 self.with_polymorphic[0],
1356 coercions.expect(
1357 roles.FromClauseRole,
1358 self.with_polymorphic[1],
1359 ),
1360 )
1361
1362 if self.configured:
1363 self._expire_memoizations()
1364
1365 def _add_with_polymorphic_subclass(self, mapper):
1366 subcl = mapper.class_
1367 if self.with_polymorphic is None:
1368 self._set_with_polymorphic((subcl,))
1369 elif self.with_polymorphic[0] != "*":
1370 assert isinstance(self.with_polymorphic[0], tuple)
1371 self._set_with_polymorphic(
1372 (self.with_polymorphic[0] + (subcl,), self.with_polymorphic[1])
1373 )
1374
1375 def _set_concrete_base(self, mapper):
1376 """Set the given :class:`_orm.Mapper` as the 'inherits' for this
1377 :class:`_orm.Mapper`, assuming this :class:`_orm.Mapper` is concrete
1378 and does not already have an inherits."""
1379
1380 assert self.concrete
1381 assert not self.inherits
1382 assert isinstance(mapper, Mapper)
1383 self.inherits = mapper
1384 self.inherits.polymorphic_map.update(self.polymorphic_map)
1385 self.polymorphic_map = self.inherits.polymorphic_map
1386 for mapper in self.iterate_to_root():
1387 if mapper.polymorphic_on is not None:
1388 mapper._requires_row_aliasing = True
1389 self.batch = self.inherits.batch
1390 for mp in self.self_and_descendants:
1391 mp.base_mapper = self.inherits.base_mapper
1392 self.inherits._inheriting_mappers.append(self)
1393 self.passive_updates = self.inherits.passive_updates
1394 self._all_tables = self.inherits._all_tables
1395
1396 for key, prop in mapper._props.items():
1397 if key not in self._props and not self._should_exclude(
1398 key, key, local=False, column=None
1399 ):
1400 self._adapt_inherited_property(key, prop, False)
1401
1402 def _set_polymorphic_on(self, polymorphic_on):
1403 self.polymorphic_on = polymorphic_on
1404 self._configure_polymorphic_setter(True)
1405
1406 def _configure_class_instrumentation(self):
1407 """Associate this Mapper with the
1408 given class and entity name.
1409
1410 Subsequent calls to ``class_mapper()`` for the ``class_`` / ``entity``
1411 name combination will return this mapper. Also decorate the
1412 `__init__` method on the mapped class to include optional
1413 auto-session attachment logic.
1414
1415 """
1416
1417 # we expect that declarative has applied the class manager
1418 # already and set up a registry. if this is None,
1419 # this raises as of 2.0.
1420 manager = attributes.opt_manager_of_class(self.class_)
1421
1422 if manager is None or not manager.registry:
1423 raise sa_exc.InvalidRequestError(
1424 "The _mapper() function and Mapper() constructor may not be "
1425 "invoked directly outside of a declarative registry."
1426 " Please use the sqlalchemy.orm.registry.map_imperatively() "
1427 "function for a classical mapping."
1428 )
1429
1430 self.dispatch.instrument_class(self, self.class_)
1431
1432 # this invokes the class_instrument event and sets up
1433 # the __init__ method. documented behavior is that this must
1434 # occur after the instrument_class event above.
1435 # yes two events with the same two words reversed and different APIs.
1436 # :(
1437
1438 manager = instrumentation.register_class(
1439 self.class_,
1440 mapper=self,
1441 expired_attribute_loader=util.partial(
1442 loading._load_scalar_attributes, self
1443 ),
1444 # finalize flag means instrument the __init__ method
1445 # and call the class_instrument event
1446 finalize=True,
1447 )
1448
1449 self.class_manager = manager
1450
1451 assert manager.registry is not None
1452 self.registry = manager.registry
1453
1454 # The remaining members can be added by any mapper,
1455 # e_name None or not.
1456 if manager.mapper is None:
1457 return
1458
1459 event.listen(manager, "init", _event_on_init, raw=True)
1460
1461 for key, method in util.iterate_attributes(self.class_):
1462 if key == "__init__" and hasattr(method, "_sa_original_init"):
1463 method = method._sa_original_init
1464 if hasattr(method, "__func__"):
1465 method = method.__func__
1466 if callable(method):
1467 if hasattr(method, "__sa_reconstructor__"):
1468 self._reconstructor = method
1469 event.listen(manager, "load", _event_on_load, raw=True)
1470 elif hasattr(method, "__sa_validators__"):
1471 validation_opts = method.__sa_validation_opts__
1472 for name in method.__sa_validators__:
1473 if name in self.validators:
1474 raise sa_exc.InvalidRequestError(
1475 "A validation function for mapped "
1476 "attribute %r on mapper %s already exists."
1477 % (name, self)
1478 )
1479 self.validators = self.validators.union(
1480 {name: (method, validation_opts)}
1481 )
1482
1483 def _set_dispose_flags(self) -> None:
1484 self.configured = True
1485 self._ready_for_configure = True
1486 self._dispose_called = True
1487
1488 self.__dict__.pop("_configure_failed", None)
1489
1490 def _str_arg_to_mapped_col(self, argname: str, key: str) -> Column[Any]:
1491 try:
1492 prop = self._props[key]
1493 except KeyError as err:
1494 raise sa_exc.ArgumentError(
1495 f"Can't determine {argname} column '{key}' - "
1496 "no attribute is mapped to this name."
1497 ) from err
1498 try:
1499 expr = prop.expression
1500 except AttributeError as ae:
1501 raise sa_exc.ArgumentError(
1502 f"Can't determine {argname} column '{key}'; "
1503 "property does not refer to a single mapped Column"
1504 ) from ae
1505 if not isinstance(expr, Column):
1506 raise sa_exc.ArgumentError(
1507 f"Can't determine {argname} column '{key}'; "
1508 "property does not refer to a single "
1509 "mapped Column"
1510 )
1511 return expr
1512
1513 def _configure_pks(self) -> None:
1514 self.tables = sql_util.find_tables(self.persist_selectable)
1515
1516 self._all_tables.update(t for t in self.tables)
1517
1518 self._pks_by_table = {}
1519 self._cols_by_table = {}
1520
1521 all_cols = util.column_set(
1522 chain(*[col.proxy_set for col in self._columntoproperty])
1523 )
1524
1525 pk_cols = util.column_set(c for c in all_cols if c.primary_key)
1526
1527 # identify primary key columns which are also mapped by this mapper.
1528 for fc in set(self.tables).union([self.persist_selectable]):
1529 if fc.primary_key and pk_cols.issuperset(fc.primary_key):
1530 # ordering is important since it determines the ordering of
1531 # mapper.primary_key (and therefore query.get())
1532 self._pks_by_table[fc] = util.ordered_column_set( # type: ignore[assignment] # noqa: E501
1533 fc.primary_key
1534 ).intersection(
1535 pk_cols
1536 )
1537 self._cols_by_table[fc] = util.ordered_column_set(fc.c).intersection( # type: ignore[assignment] # noqa: E501
1538 all_cols
1539 )
1540
1541 if self._primary_key_argument:
1542 coerced_pk_arg = [
1543 (
1544 self._str_arg_to_mapped_col("primary_key", c)
1545 if isinstance(c, str)
1546 else c
1547 )
1548 for c in (
1549 coercions.expect(
1550 roles.DDLConstraintColumnRole,
1551 coerce_pk,
1552 argname="primary_key",
1553 )
1554 for coerce_pk in self._primary_key_argument
1555 )
1556 ]
1557 else:
1558 coerced_pk_arg = None
1559
1560 # if explicit PK argument sent, add those columns to the
1561 # primary key mappings
1562 if coerced_pk_arg:
1563 for k in coerced_pk_arg:
1564 if k.table not in self._pks_by_table:
1565 self._pks_by_table[k.table] = util.OrderedSet()
1566 self._pks_by_table[k.table].add(k)
1567
1568 # otherwise, see that we got a full PK for the mapped table
1569 elif (
1570 self.persist_selectable not in self._pks_by_table
1571 or len(self._pks_by_table[self.persist_selectable]) == 0
1572 ):
1573 raise sa_exc.ArgumentError(
1574 "Mapper %s could not assemble any primary "
1575 "key columns for mapped table '%s'"
1576 % (self, self.persist_selectable.description)
1577 )
1578 elif self.local_table not in self._pks_by_table and isinstance(
1579 self.local_table, schema.Table
1580 ):
1581 util.warn(
1582 "Could not assemble any primary "
1583 "keys for locally mapped table '%s' - "
1584 "no rows will be persisted in this Table."
1585 % self.local_table.description
1586 )
1587
1588 if (
1589 self.inherits
1590 and not self.concrete
1591 and not self._primary_key_argument
1592 ):
1593 # if inheriting, the "primary key" for this mapper is
1594 # that of the inheriting (unless concrete or explicit)
1595 self.primary_key = self.inherits.primary_key
1596 else:
1597 # determine primary key from argument or persist_selectable pks
1598 primary_key: Collection[ColumnElement[Any]]
1599
1600 if coerced_pk_arg:
1601 primary_key = [
1602 cc if cc is not None else c
1603 for cc, c in (
1604 (self.persist_selectable.corresponding_column(c), c)
1605 for c in coerced_pk_arg
1606 )
1607 ]
1608 else:
1609 # if heuristically determined PKs, reduce to the minimal set
1610 # of columns by eliminating FK->PK pairs for a multi-table
1611 # expression. May over-reduce for some kinds of UNIONs
1612 # / CTEs; use explicit PK argument for these special cases
1613 primary_key = sql_util.reduce_columns(
1614 self._pks_by_table[self.persist_selectable],
1615 ignore_nonexistent_tables=True,
1616 )
1617
1618 if len(primary_key) == 0:
1619 raise sa_exc.ArgumentError(
1620 "Mapper %s could not assemble any primary "
1621 "key columns for mapped table '%s'"
1622 % (self, self.persist_selectable.description)
1623 )
1624
1625 self.primary_key = tuple(primary_key)
1626 self._log("Identified primary key columns: %s", primary_key)
1627
1628 # determine cols that aren't expressed within our tables; mark these
1629 # as "read only" properties which are refreshed upon INSERT/UPDATE
1630 self._readonly_props = {
1631 self._columntoproperty[col]
1632 for col in self._columntoproperty
1633 if self._columntoproperty[col] not in self._identity_key_props
1634 and (
1635 not hasattr(col, "table")
1636 or col.table not in self._cols_by_table
1637 )
1638 }
1639
1640 def _configure_properties(self) -> None:
1641 self._columns = sql_base.WriteableColumnCollection()
1642
1643 # object attribute names mapped to MapperProperty objects
1644 self._props = util.OrderedDict()
1645
1646 # table columns mapped to MapperProperty
1647 self._columntoproperty = _ColumnMapping(self)
1648
1649 explicit_col_props_by_column: Dict[
1650 KeyedColumnElement[Any], Tuple[str, ColumnProperty[Any]]
1651 ] = {}
1652 explicit_col_props_by_key: Dict[str, ColumnProperty[Any]] = {}
1653
1654 # step 1: go through properties that were explicitly passed
1655 # in the properties dictionary. For Columns that are local, put them
1656 # aside in a separate collection we will reconcile with the Table
1657 # that's given. For other properties, set them up in _props now.
1658 if self._init_properties:
1659 for key, prop_arg in self._init_properties.items():
1660 if not isinstance(prop_arg, MapperProperty):
1661 possible_col_prop = self._make_prop_from_column(
1662 key, prop_arg
1663 )
1664 else:
1665 possible_col_prop = prop_arg
1666
1667 if isinstance(
1668 possible_col_prop, properties.ColumnProperty
1669 ) and self._excluded_by_include_exclude(
1670 key, possible_col_prop.columns
1671 ):
1672 # explicit properties are mapped regardless of
1673 # include_properties / exclude_properties
1674 self._warn_for_excluded_declared_attr(
1675 key, possible_col_prop.columns
1676 )
1677
1678 # issue #8705. if the explicit property is actually a
1679 # Column that is local to the local Table, don't set it up
1680 # in ._props yet, integrate it into the order given within
1681 # the Table.
1682
1683 _map_as_property_now = True
1684 if isinstance(possible_col_prop, properties.ColumnProperty):
1685 for given_col in possible_col_prop.columns:
1686 if self.local_table.c.contains_column(given_col):
1687 _map_as_property_now = False
1688 explicit_col_props_by_key[key] = possible_col_prop
1689 explicit_col_props_by_column[given_col] = (
1690 key,
1691 possible_col_prop,
1692 )
1693
1694 if _map_as_property_now:
1695 self._configure_property(
1696 key,
1697 possible_col_prop,
1698 init=False,
1699 )
1700
1701 # step 2: pull properties from the inherited mapper. reconcile
1702 # columns with those which are explicit above. for properties that
1703 # are only in the inheriting mapper, set them up as local props
1704 if self.inherits:
1705 for key, inherited_prop in self.inherits._props.items():
1706 if self._should_exclude(key, key, local=False, column=None):
1707 continue
1708
1709 incoming_prop = explicit_col_props_by_key.get(key)
1710 if incoming_prop:
1711 new_prop = self._reconcile_prop_with_incoming_columns(
1712 key,
1713 inherited_prop,
1714 warn_only=False,
1715 incoming_prop=incoming_prop,
1716 )
1717 explicit_col_props_by_key[key] = new_prop
1718
1719 for inc_col in incoming_prop.columns:
1720 explicit_col_props_by_column[inc_col] = (
1721 key,
1722 new_prop,
1723 )
1724 elif key not in self._props:
1725 self._adapt_inherited_property(key, inherited_prop, False)
1726
1727 # step 3. Iterate through all columns in the persist selectable.
1728 # this includes not only columns in the local table / fromclause,
1729 # but also those columns in the superclass table if we are joined
1730 # inh or single inh mapper. map these columns as well. additional
1731 # reconciliation against inherited columns occurs here also.
1732
1733 for column in self.persist_selectable.columns:
1734 if column in explicit_col_props_by_column:
1735 # column was explicitly passed to properties; configure
1736 # it now in the order in which it corresponds to the
1737 # Table / selectable
1738 key, prop = explicit_col_props_by_column[column]
1739 self._configure_property(key, prop, init=False)
1740 continue
1741
1742 elif column in self._columntoproperty:
1743 continue
1744
1745 column_key = (self.column_prefix or "") + column.key
1746 if self._should_exclude(
1747 column.key,
1748 column_key,
1749 local=self.local_table.c.contains_column(column),
1750 column=column,
1751 ):
1752 continue
1753
1754 # adjust the "key" used for this column to that
1755 # of the inheriting mapper
1756 for mapper in self.iterate_to_root():
1757 if column in mapper._columntoproperty:
1758 column_key = mapper._columntoproperty[column].key
1759
1760 self._configure_property(
1761 column_key,
1762 column,
1763 init=False,
1764 setparent=True,
1765 )
1766
1767 def _configure_polymorphic_setter(self, init=False):
1768 """Configure an attribute on the mapper representing the
1769 'polymorphic_on' column, if applicable, and not
1770 already generated by _configure_properties (which is typical).
1771
1772 Also create a setter function which will assign this
1773 attribute to the value of the 'polymorphic_identity'
1774 upon instance construction, also if applicable. This
1775 routine will run when an instance is created.
1776
1777 """
1778 setter = False
1779 polymorphic_key: Optional[str] = None
1780
1781 if self.polymorphic_on is not None:
1782 setter = True
1783
1784 if isinstance(self.polymorphic_on, str):
1785 # polymorphic_on specified as a string - link
1786 # it to mapped ColumnProperty
1787 try:
1788 self.polymorphic_on = self._props[self.polymorphic_on]
1789 except KeyError as err:
1790 raise sa_exc.ArgumentError(
1791 "Can't determine polymorphic_on "
1792 "value '%s' - no attribute is "
1793 "mapped to this name." % self.polymorphic_on
1794 ) from err
1795
1796 if self.polymorphic_on in self._columntoproperty:
1797 # polymorphic_on is a column that is already mapped
1798 # to a ColumnProperty
1799 prop = self._columntoproperty[self.polymorphic_on]
1800 elif isinstance(self.polymorphic_on, MapperProperty):
1801 # polymorphic_on is directly a MapperProperty,
1802 # ensure it's a ColumnProperty
1803 if not isinstance(
1804 self.polymorphic_on, properties.ColumnProperty
1805 ):
1806 raise sa_exc.ArgumentError(
1807 "Only direct column-mapped "
1808 "property or SQL expression "
1809 "can be passed for polymorphic_on"
1810 )
1811 prop = self.polymorphic_on
1812 else:
1813 # polymorphic_on is a Column or SQL expression and
1814 # doesn't appear to be mapped. this means it can be 1.
1815 # only present in the with_polymorphic selectable or
1816 # 2. a totally standalone SQL expression which we'd
1817 # hope is compatible with this mapper's persist_selectable
1818 col = self.persist_selectable.corresponding_column(
1819 self.polymorphic_on
1820 )
1821 if col is None:
1822 # polymorphic_on doesn't derive from any
1823 # column/expression isn't present in the mapped
1824 # table. we will make a "hidden" ColumnProperty
1825 # for it. Just check that if it's directly a
1826 # schema.Column and we have with_polymorphic, it's
1827 # likely a user error if the schema.Column isn't
1828 # represented somehow in either persist_selectable or
1829 # with_polymorphic. Otherwise as of 0.7.4 we
1830 # just go with it and assume the user wants it
1831 # that way (i.e. a CASE statement)
1832 setter = False
1833 instrument = False
1834 col = self.polymorphic_on
1835 if isinstance(col, schema.Column) and (
1836 self.with_polymorphic is None
1837 or self.with_polymorphic[1] is None
1838 or self.with_polymorphic[1].corresponding_column(col)
1839 is None
1840 ):
1841 raise sa_exc.InvalidRequestError(
1842 "Could not map polymorphic_on column "
1843 "'%s' to the mapped table - polymorphic "
1844 "loads will not function properly"
1845 % col.description
1846 )
1847 else:
1848 # column/expression that polymorphic_on derives from
1849 # is present in our mapped table
1850 # and is probably mapped, but polymorphic_on itself
1851 # is not. This happens when
1852 # the polymorphic_on is only directly present in the
1853 # with_polymorphic selectable, as when use
1854 # polymorphic_union.
1855 # we'll make a separate ColumnProperty for it.
1856 instrument = True
1857 key = getattr(col, "key", None)
1858 if key:
1859 if self._should_exclude(key, key, False, col):
1860 raise sa_exc.InvalidRequestError(
1861 "Cannot exclude or override the "
1862 "discriminator column %r" % key
1863 )
1864 else:
1865 self.polymorphic_on = col = col.label("_sa_polymorphic_on")
1866 key = col.key
1867
1868 prop = properties.ColumnProperty(col, _instrument=instrument)
1869 self._configure_property(key, prop, init=init, setparent=True)
1870
1871 # the actual polymorphic_on should be the first public-facing
1872 # column in the property
1873 self.polymorphic_on = prop.columns[0]
1874 polymorphic_key = prop.key
1875 else:
1876 # no polymorphic_on was set.
1877 # check inheriting mappers for one.
1878 for mapper in self.iterate_to_root():
1879 # determine if polymorphic_on of the parent
1880 # should be propagated here. If the col
1881 # is present in our mapped table, or if our mapped
1882 # table is the same as the parent (i.e. single table
1883 # inheritance), we can use it
1884 if mapper.polymorphic_on is not None:
1885 if self.persist_selectable is mapper.persist_selectable:
1886 self.polymorphic_on = mapper.polymorphic_on
1887 else:
1888 self.polymorphic_on = (
1889 self.persist_selectable
1890 ).corresponding_column(mapper.polymorphic_on)
1891 # we can use the parent mapper's _set_polymorphic_identity
1892 # directly; it ensures the polymorphic_identity of the
1893 # instance's mapper is used so is portable to subclasses.
1894 if self.polymorphic_on is not None:
1895 self._set_polymorphic_identity = (
1896 mapper._set_polymorphic_identity
1897 )
1898 self._polymorphic_attr_key = (
1899 mapper._polymorphic_attr_key
1900 )
1901 self._validate_polymorphic_identity = (
1902 mapper._validate_polymorphic_identity
1903 )
1904 else:
1905 self._set_polymorphic_identity = None
1906 self._polymorphic_attr_key = None
1907 return
1908
1909 if self.polymorphic_abstract and self.polymorphic_on is None:
1910 raise sa_exc.InvalidRequestError(
1911 "The Mapper.polymorphic_abstract parameter may only be used "
1912 "on a mapper hierarchy which includes the "
1913 "Mapper.polymorphic_on parameter at the base of the hierarchy."
1914 )
1915
1916 if setter:
1917
1918 def _set_polymorphic_identity(state):
1919 dict_ = state.dict
1920 # TODO: what happens if polymorphic_on column attribute name
1921 # does not match .key?
1922
1923 polymorphic_identity = (
1924 state.manager.mapper.polymorphic_identity
1925 )
1926 if (
1927 polymorphic_identity is None
1928 and state.manager.mapper.polymorphic_abstract
1929 ):
1930 raise sa_exc.InvalidRequestError(
1931 f"Can't instantiate class for {state.manager.mapper}; "
1932 "mapper is marked polymorphic_abstract=True"
1933 )
1934
1935 state.get_impl(polymorphic_key).set(
1936 state,
1937 dict_,
1938 polymorphic_identity,
1939 None,
1940 )
1941
1942 self._polymorphic_attr_key = polymorphic_key
1943
1944 def _validate_polymorphic_identity(mapper, state, dict_):
1945 if (
1946 polymorphic_key in dict_
1947 and dict_[polymorphic_key]
1948 not in mapper._acceptable_polymorphic_identities
1949 ):
1950 util.warn_limited(
1951 "Flushing object %s with "
1952 "incompatible polymorphic identity %r; the "
1953 "object may not refresh and/or load correctly",
1954 (state_str(state), dict_[polymorphic_key]),
1955 )
1956
1957 self._set_polymorphic_identity = _set_polymorphic_identity
1958 self._validate_polymorphic_identity = (
1959 _validate_polymorphic_identity
1960 )
1961 else:
1962 self._polymorphic_attr_key = None
1963 self._set_polymorphic_identity = None
1964
1965 _validate_polymorphic_identity = None
1966
1967 @HasMemoized.memoized_attribute
1968 def _local_pk_cols(self) -> set[Any]:
1969 pk_cols = util.column_set(self.primary_key)
1970 equiv = self._equivalent_columns
1971 for pk_col in pk_cols.intersection(equiv):
1972 pk_cols.update(equiv[pk_col])
1973 return util.column_set(
1974 col for col in pk_cols if col.table is self.local_table
1975 )
1976
1977 @HasMemoized.memoized_attribute
1978 def _version_id_prop(self):
1979 if self.version_id_col is not None:
1980 return self._columntoproperty[self.version_id_col]
1981 else:
1982 return None
1983
1984 @HasMemoized.memoized_attribute
1985 def _acceptable_polymorphic_identities(self):
1986 identities = set()
1987
1988 stack = deque([self])
1989 while stack:
1990 item = stack.popleft()
1991 if item.persist_selectable is self.persist_selectable:
1992 identities.add(item.polymorphic_identity)
1993 stack.extend(item._inheriting_mappers)
1994
1995 return identities
1996
1997 @HasMemoized.memoized_attribute
1998 def _prop_set(self):
1999 return frozenset(self._props.values())
2000
2001 @util.preload_module("sqlalchemy.orm.descriptor_props")
2002 def _adapt_inherited_property(self, key, prop, init):
2003 descriptor_props = util.preloaded.orm_descriptor_props
2004
2005 if not self.concrete:
2006 self._configure_property(key, prop, init=False, setparent=False)
2007 elif key not in self._props:
2008 # determine if the class implements this attribute; if not,
2009 # or if it is implemented by the attribute that is handling the
2010 # given superclass-mapped property, then we need to report that we
2011 # can't use this at the instance level since we are a concrete
2012 # mapper and we don't map this. don't trip user-defined
2013 # descriptors that might have side effects when invoked.
2014 implementing_attribute = self.class_manager._get_class_attr_mro(
2015 key, prop
2016 )
2017 if implementing_attribute is prop or (
2018 isinstance(
2019 implementing_attribute, attributes.InstrumentedAttribute
2020 )
2021 and implementing_attribute._parententity is prop.parent
2022 ):
2023 self._configure_property(
2024 key,
2025 descriptor_props.ConcreteInheritedProperty(),
2026 init=init,
2027 setparent=True,
2028 )
2029
2030 @util.preload_module("sqlalchemy.orm.descriptor_props")
2031 def _configure_property(
2032 self,
2033 key: str,
2034 prop_arg: Union[KeyedColumnElement[Any], MapperProperty[Any]],
2035 *,
2036 init: bool = True,
2037 setparent: bool = True,
2038 warn_for_existing: bool = False,
2039 ) -> MapperProperty[Any]:
2040 descriptor_props = util.preloaded.orm_descriptor_props
2041 self._log(
2042 "_configure_property(%s, %s)", key, prop_arg.__class__.__name__
2043 )
2044
2045 # early setup mode - don't assign any props, only
2046 # ensure a Column is turned into a ColumnProperty.
2047 # see #12858
2048 early_setup = not hasattr(self, "_props")
2049
2050 if not isinstance(prop_arg, MapperProperty):
2051 prop: MapperProperty[Any] = self._property_from_column(
2052 key, prop_arg, early_setup
2053 )
2054 else:
2055 prop = prop_arg
2056
2057 if early_setup:
2058 return prop
2059
2060 if isinstance(prop, properties.ColumnProperty):
2061 col = self.persist_selectable.corresponding_column(prop.columns[0])
2062
2063 # if the column is not present in the mapped table,
2064 # test if a column has been added after the fact to the
2065 # parent table (or their parent, etc.) [ticket:1570]
2066 if col is None and self.inherits:
2067 path = [self]
2068 for m in self.inherits.iterate_to_root():
2069 col = m.local_table.corresponding_column(prop.columns[0])
2070 if col is not None:
2071 for m2 in path:
2072 m2.persist_selectable._refresh_for_new_column(col)
2073 col = self.persist_selectable.corresponding_column(
2074 prop.columns[0]
2075 )
2076 break
2077 path.append(m)
2078
2079 # subquery expression, column not present in the mapped
2080 # selectable.
2081 if col is None:
2082 col = prop.columns[0]
2083
2084 # column is coming in after _readonly_props was
2085 # initialized; check for 'readonly'
2086 if hasattr(self, "_readonly_props") and (
2087 not hasattr(col, "table")
2088 or col.table not in self._cols_by_table
2089 ):
2090 self._readonly_props.add(prop)
2091
2092 else:
2093 # if column is coming in after _cols_by_table was
2094 # initialized, ensure the col is in the right set
2095 if (
2096 hasattr(self, "_cols_by_table")
2097 and col.table in self._cols_by_table
2098 and col not in self._cols_by_table[col.table]
2099 ):
2100 self._cols_by_table[col.table].add(col)
2101
2102 # if this properties.ColumnProperty represents the "polymorphic
2103 # discriminator" column, mark it. We'll need this when rendering
2104 # columns in SELECT statements.
2105 if not hasattr(prop, "_is_polymorphic_discriminator"):
2106 prop._is_polymorphic_discriminator = (
2107 col is self.polymorphic_on
2108 or prop.columns[0] is self.polymorphic_on
2109 )
2110
2111 if isinstance(col, expression.Label):
2112 # new in 1.4, get column property against expressions
2113 # to be addressable in subqueries
2114 col.key = col._tq_key_label = key
2115
2116 self._columns.add(col, key)
2117
2118 for col in prop.columns:
2119 for proxy_col in col.proxy_set:
2120 self._columntoproperty[proxy_col] = prop
2121
2122 if getattr(prop, "key", key) != key:
2123 util.warn(
2124 f"ORM mapped property {self.class_.__name__}.{prop.key} being "
2125 "assigned to attribute "
2126 f"{key!r} is already associated with "
2127 f"attribute {prop.key!r}. The attribute will be de-associated "
2128 f"from {prop.key!r}."
2129 )
2130
2131 prop.key = key
2132
2133 if setparent:
2134 prop.set_parent(self, init)
2135
2136 if key in self._props and getattr(
2137 self._props[key], "_mapped_by_synonym", False
2138 ):
2139 syn = self._props[key]._mapped_by_synonym
2140 raise sa_exc.ArgumentError(
2141 "Can't call map_column=True for synonym %r=%r, "
2142 "a ColumnProperty already exists keyed to the name "
2143 "%r for column %r" % (syn, key, key, syn)
2144 )
2145
2146 # replacement cases
2147
2148 # case one: prop is replacing a prop that we have mapped. this is
2149 # independent of whatever might be in the actual class dictionary
2150 if (
2151 key in self._props
2152 and not isinstance(
2153 self._props[key], descriptor_props.ConcreteInheritedProperty
2154 )
2155 and not isinstance(prop, descriptor_props.SynonymProperty)
2156 ):
2157 if warn_for_existing:
2158 util.warn_deprecated(
2159 f"User-placed attribute {self.class_.__name__}.{key} on "
2160 f"{self} is replacing an existing ORM-mapped attribute. "
2161 "Behavior is not fully defined in this case. This "
2162 "use is deprecated and will raise an error in a future "
2163 "release",
2164 "2.0",
2165 )
2166 oldprop = self._props[key]
2167 self._path_registry.pop(oldprop, None)
2168
2169 # case two: prop is replacing an attribute on the class of some kind.
2170 # we have to be more careful here since it's normal when using
2171 # Declarative that all the "declared attributes" on the class
2172 # get replaced.
2173 elif (
2174 warn_for_existing
2175 and self.class_.__dict__.get(key, None) is not None
2176 and not isinstance(prop, descriptor_props.SynonymProperty)
2177 and not isinstance(
2178 self._props.get(key, None),
2179 descriptor_props.ConcreteInheritedProperty,
2180 )
2181 ):
2182 util.warn_deprecated(
2183 f"User-placed attribute {self.class_.__name__}.{key} on "
2184 f"{self} is replacing an existing class-bound "
2185 "attribute of the same name. "
2186 "Behavior is not fully defined in this case. This "
2187 "use is deprecated and will raise an error in a future "
2188 "release",
2189 "2.0",
2190 )
2191
2192 self._props[key] = prop
2193
2194 prop.instrument_class(self)
2195
2196 for mapper in self._inheriting_mappers:
2197 mapper._adapt_inherited_property(key, prop, init)
2198
2199 if init:
2200 prop.init()
2201 prop.post_instrument_class(self)
2202
2203 if self.configured:
2204 self._expire_memoizations()
2205
2206 return prop
2207
2208 def _make_prop_from_column(
2209 self,
2210 key: str,
2211 column: Union[
2212 Sequence[KeyedColumnElement[Any]], KeyedColumnElement[Any]
2213 ],
2214 ) -> ColumnProperty[Any]:
2215 columns = util.to_list(column)
2216 mapped_column = []
2217 for c in columns:
2218 mc = self.persist_selectable.corresponding_column(c)
2219 if mc is None:
2220 mc = self.local_table.corresponding_column(c)
2221 if mc is not None:
2222 # if the column is in the local table but not the
2223 # mapped table, this corresponds to adding a
2224 # column after the fact to the local table.
2225 # [ticket:1523]
2226 self.persist_selectable._refresh_for_new_column(mc)
2227 mc = self.persist_selectable.corresponding_column(c)
2228 if mc is None:
2229 raise sa_exc.ArgumentError(
2230 "When configuring property '%s' on %s, "
2231 "column '%s' is not represented in the mapper's "
2232 "table. Use the `column_property()` function to "
2233 "force this column to be mapped as a read-only "
2234 "attribute." % (key, self, c)
2235 )
2236 mapped_column.append(mc)
2237 return properties.ColumnProperty(*mapped_column)
2238
2239 def _reconcile_prop_with_incoming_columns(
2240 self,
2241 key: str,
2242 existing_prop: MapperProperty[Any],
2243 warn_only: bool,
2244 incoming_prop: Optional[ColumnProperty[Any]] = None,
2245 single_column: Optional[KeyedColumnElement[Any]] = None,
2246 ) -> ColumnProperty[Any]:
2247 if incoming_prop and (
2248 self.concrete
2249 or not isinstance(existing_prop, properties.ColumnProperty)
2250 ):
2251 return incoming_prop
2252
2253 existing_column = existing_prop.columns[0]
2254
2255 if incoming_prop and existing_column in incoming_prop.columns:
2256 return incoming_prop
2257
2258 if incoming_prop is None:
2259 assert single_column is not None
2260 incoming_column = single_column
2261 equated_pair_key = (existing_prop.columns[0], incoming_column)
2262 else:
2263 assert single_column is None
2264 incoming_column = incoming_prop.columns[0]
2265 equated_pair_key = (incoming_column, existing_prop.columns[0])
2266
2267 if (
2268 (
2269 not self._inherits_equated_pairs
2270 or (equated_pair_key not in self._inherits_equated_pairs)
2271 )
2272 and not existing_column.shares_lineage(incoming_column)
2273 and existing_column is not self.version_id_col
2274 and incoming_column is not self.version_id_col
2275 ):
2276 msg = (
2277 "Implicitly combining column %s with column "
2278 "%s under attribute '%s'. Please configure one "
2279 "or more attributes for these same-named columns "
2280 "explicitly."
2281 % (
2282 existing_prop.columns[-1],
2283 incoming_column,
2284 key,
2285 )
2286 )
2287 if warn_only:
2288 util.warn(msg)
2289 else:
2290 raise sa_exc.InvalidRequestError(msg)
2291
2292 # existing properties.ColumnProperty from an inheriting
2293 # mapper. make a copy and append our column to it
2294 new_prop = existing_prop.copy()
2295
2296 new_prop.columns.insert(0, incoming_column)
2297 self._log(
2298 "inserting column to existing list "
2299 "in properties.ColumnProperty %s",
2300 key,
2301 )
2302 return new_prop # type: ignore[no-any-return]
2303
2304 @util.preload_module("sqlalchemy.orm.descriptor_props")
2305 def _property_from_column(
2306 self, key: str, column: KeyedColumnElement[Any], early_setup: bool
2307 ) -> ColumnProperty[Any]:
2308 """generate/update a :class:`.ColumnProperty` given a
2309 :class:`_schema.Column` or other SQL expression object."""
2310
2311 descriptor_props = util.preloaded.orm_descriptor_props
2312
2313 if early_setup:
2314 prop = None
2315 else:
2316 prop = self._props.get(key)
2317
2318 if isinstance(prop, properties.ColumnProperty):
2319 return self._reconcile_prop_with_incoming_columns(
2320 key,
2321 prop,
2322 single_column=column,
2323 warn_only=prop.parent is not self,
2324 )
2325 elif prop is None or isinstance(
2326 prop, descriptor_props.ConcreteInheritedProperty
2327 ):
2328 return self._make_prop_from_column(key, column)
2329 else:
2330 raise sa_exc.ArgumentError(
2331 "WARNING: when configuring property '%s' on %s, "
2332 "column '%s' conflicts with property '%r'. "
2333 "To resolve this, map the column to the class under a "
2334 "different name in the 'properties' dictionary. Or, "
2335 "to remove all awareness of the column entirely "
2336 "(including its availability as a foreign key), "
2337 "use the 'include_properties' or 'exclude_properties' "
2338 "mapper arguments to control specifically which table "
2339 "columns get mapped." % (key, self, column.key, prop)
2340 )
2341
2342 @util.langhelpers.tag_method_for_warnings(
2343 "This warning originated from the `configure_mappers()` process, "
2344 "which was invoked automatically in response to a user-initiated "
2345 "operation.",
2346 sa_exc.SAWarning,
2347 )
2348 def _check_configure(self) -> None:
2349 if self.registry._new_mappers:
2350 _configure_registries({self.registry}, cascade=True)
2351
2352 def _post_configure_properties(self) -> None:
2353 """Call the ``init()`` method on all ``MapperProperties``
2354 attached to this mapper.
2355
2356 This is a deferred configuration step which is intended
2357 to execute once all mappers have been constructed.
2358
2359 """
2360
2361 self._log("_post_configure_properties() started")
2362 l = [(key, prop) for key, prop in self._props.items()]
2363 for key, prop in l:
2364 self._log("initialize prop %s", key)
2365
2366 if prop.parent is self and not prop._configure_started:
2367 prop.init()
2368
2369 if prop._configure_finished:
2370 prop.post_instrument_class(self)
2371
2372 self._log("_post_configure_properties() complete")
2373 self.configured = True
2374
2375 def add_properties(self, dict_of_properties):
2376 """Add the given dictionary of properties to this mapper,
2377 using `add_property`.
2378
2379 """
2380 for key, value in dict_of_properties.items():
2381 self.add_property(key, value)
2382
2383 def add_property(
2384 self, key: str, prop: Union[Column[Any], MapperProperty[Any]]
2385 ) -> None:
2386 """Add an individual MapperProperty to this mapper.
2387
2388 If the mapper has not been configured yet, just adds the
2389 property to the initial properties dictionary sent to the
2390 constructor. If this Mapper has already been configured, then
2391 the given MapperProperty is configured immediately.
2392
2393 """
2394 prop = self._configure_property(
2395 key, prop, init=self.configured, warn_for_existing=True
2396 )
2397 assert isinstance(prop, MapperProperty)
2398 self._init_properties[key] = prop
2399
2400 def _expire_memoizations(self) -> None:
2401 for mapper in self.iterate_to_root():
2402 mapper._reset_memoizations()
2403
2404 @property
2405 def _log_desc(self) -> str:
2406 return (
2407 "("
2408 + self.class_.__name__
2409 + "|"
2410 + (
2411 self.local_table is not None
2412 and self.local_table.description
2413 or str(self.local_table)
2414 )
2415 + ")"
2416 )
2417
2418 def _log(self, msg: str, *args: Any) -> None:
2419 self.logger.info("%s " + msg, *((self._log_desc,) + args))
2420
2421 def _log_debug(self, msg: str, *args: Any) -> None:
2422 self.logger.debug("%s " + msg, *((self._log_desc,) + args))
2423
2424 def __repr__(self) -> str:
2425 return "<Mapper at 0x%x; %s>" % (id(self), self.class_.__name__)
2426
2427 def __str__(self) -> str:
2428 return "Mapper[%s(%s)]" % (
2429 self.class_.__name__,
2430 (
2431 self.local_table.description
2432 if self.local_table is not None
2433 else self.persist_selectable.description
2434 ),
2435 )
2436
2437 def path_string(self) -> str:
2438 """Return a user-facing name for this :class:`_orm.Mapper`,
2439 for use in a :class:`_orm.PathRegistry` string representation.
2440
2441 """
2442 return self.class_.__name__
2443
2444 def _is_orphan(self, state: InstanceState[_O]) -> bool:
2445 orphan_possible = False
2446 for mapper in self.iterate_to_root():
2447 for key, cls in mapper._delete_orphans:
2448 orphan_possible = True
2449
2450 has_parent = attributes.manager_of_class(cls).has_parent(
2451 state, key, optimistic=state.has_identity
2452 )
2453
2454 if self.legacy_is_orphan and has_parent:
2455 return False
2456 elif not self.legacy_is_orphan and not has_parent:
2457 return True
2458
2459 if self.legacy_is_orphan:
2460 return orphan_possible
2461 else:
2462 return False
2463
2464 def has_property(self, key: str) -> bool:
2465 return key in self._props
2466
2467 def get_property(
2468 self, key: str, _configure_mappers: bool = False
2469 ) -> MapperProperty[Any]:
2470 """return a MapperProperty associated with the given key."""
2471
2472 if _configure_mappers:
2473 self._check_configure()
2474
2475 try:
2476 return self._props[key]
2477 except KeyError as err:
2478 raise sa_exc.InvalidRequestError(
2479 f"Mapper '{self}' has no property '{key}'. If this property "
2480 "was indicated from other mappers or configure events, ensure "
2481 "registry.configure() has been called."
2482 ) from err
2483
2484 def get_property_by_column(
2485 self, column: ColumnElement[_T]
2486 ) -> MapperProperty[_T]:
2487 """Given a :class:`_schema.Column` object, return the
2488 :class:`.MapperProperty` which maps this column."""
2489
2490 return self._columntoproperty[column]
2491
2492 @HasMemoized.memoized_attribute
2493 def columns(self) -> ReadOnlyColumnCollection[str, Column[Any]]:
2494 """A collection of :class:`_schema.Column` or other scalar expression
2495 objects maintained by this :class:`_orm.Mapper`.
2496
2497 The collection behaves the same as that of the ``c`` attribute on any
2498 :class:`_schema.Table` object, except that only those columns included
2499 in this mapping are present, and are keyed based on the attribute name
2500 defined in the mapping, not necessarily the ``key`` attribute of the
2501 :class:`_schema.Column` itself. Additionally, scalar expressions
2502 mapped by :func:`.column_property` are also present here.
2503
2504 This is a *read only* attribute determined during mapper construction.
2505 Behavior is undefined if directly modified.
2506
2507 """
2508 return self._columns.as_readonly()
2509
2510 @HasMemoized.memoized_attribute
2511 def c(self) -> ReadOnlyColumnCollection[str, Column[Any]]:
2512 """A synonym for :attr:`_orm.Mapper.columns`."""
2513 return self._columns.as_readonly()
2514
2515 @property
2516 def iterate_properties(self):
2517 """return an iterator of all MapperProperty objects."""
2518
2519 return iter(self._props.values())
2520
2521 def _mappers_from_spec(
2522 self, spec: Any, selectable: Optional[FromClause]
2523 ) -> Sequence[Mapper[Any]]:
2524 """given a with_polymorphic() argument, return the set of mappers it
2525 represents.
2526
2527 Trims the list of mappers to just those represented within the given
2528 selectable, if present. This helps some more legacy-ish mappings.
2529
2530 """
2531 if spec == "*":
2532 mappers = list(self.self_and_descendants)
2533 elif spec:
2534 mapper_set: Set[Mapper[Any]] = set()
2535 for m in util.to_list(spec):
2536 m = _class_to_mapper(m)
2537 if not m.isa(self):
2538 raise sa_exc.InvalidRequestError(
2539 "%r does not inherit from %r" % (m, self)
2540 )
2541
2542 if selectable is None:
2543 mapper_set.update(m.iterate_to_root())
2544 else:
2545 mapper_set.add(m)
2546 mappers = [m for m in self.self_and_descendants if m in mapper_set]
2547 else:
2548 mappers = []
2549
2550 if selectable is not None:
2551 tables = set(
2552 sql_util.find_tables(selectable, include_aliases=True)
2553 )
2554 mappers = [m for m in mappers if m.local_table in tables]
2555 return mappers
2556
2557 def _selectable_from_mappers(
2558 self, mappers: Iterable[Mapper[Any]], innerjoin: bool
2559 ) -> FromClause:
2560 """given a list of mappers (assumed to be within this mapper's
2561 inheritance hierarchy), construct an outerjoin amongst those mapper's
2562 mapped tables.
2563
2564 """
2565 from_obj = self.persist_selectable
2566 for m in mappers:
2567 if m is self:
2568 continue
2569 if m.concrete:
2570 raise sa_exc.InvalidRequestError(
2571 "'with_polymorphic()' requires 'selectable' argument "
2572 "when concrete-inheriting mappers are used."
2573 )
2574 elif not m.single:
2575 if innerjoin:
2576 from_obj = from_obj.join(
2577 m.local_table, m.inherit_condition
2578 )
2579 else:
2580 from_obj = from_obj.outerjoin(
2581 m.local_table, m.inherit_condition
2582 )
2583
2584 return from_obj
2585
2586 @HasMemoized.memoized_attribute
2587 def _version_id_has_server_side_value(self) -> bool:
2588 vid_col = self.version_id_col
2589
2590 if vid_col is None:
2591 return False
2592
2593 elif not isinstance(vid_col, Column):
2594 return True
2595 else:
2596 return vid_col.server_default is not None or (
2597 vid_col.default is not None
2598 and (
2599 not vid_col.default.is_scalar
2600 and not vid_col.default.is_callable
2601 )
2602 )
2603
2604 @HasMemoized.memoized_attribute
2605 def _single_table_criteria_component(self):
2606 if self.single and self.inherits and self.polymorphic_on is not None:
2607
2608 hierarchy = tuple(
2609 m.polymorphic_identity
2610 for m in self.self_and_descendants
2611 if not m.polymorphic_abstract
2612 )
2613
2614 return (
2615 self.polymorphic_on._annotate(
2616 {"parententity": self, "parentmapper": self}
2617 ),
2618 hierarchy,
2619 )
2620 else:
2621 return None
2622
2623 @HasMemoized.memoized_attribute
2624 def _single_table_criterion(self):
2625 component = self._single_table_criteria_component
2626 if component is not None:
2627 return component[0].in_(component[1])
2628 else:
2629 return None
2630
2631 @HasMemoized.memoized_attribute
2632 def _has_aliased_polymorphic_fromclause(self):
2633 """return True if with_polymorphic[1] is an aliased fromclause,
2634 like a subquery.
2635
2636 As of #8168, polymorphic adaption with ORMAdapter is used only
2637 if this is present.
2638
2639 """
2640 return self.with_polymorphic and isinstance(
2641 self.with_polymorphic[1],
2642 expression.AliasedReturnsRows,
2643 )
2644
2645 @HasMemoized.memoized_attribute
2646 def _should_select_with_poly_adapter(self):
2647 """determine if _MapperEntity or _ORMColumnEntity will need to use
2648 polymorphic adaption when setting up a SELECT as well as fetching
2649 rows for mapped classes and subclasses against this Mapper.
2650
2651 moved here from context.py for #8456 to generalize the ruleset
2652 for this condition.
2653
2654 """
2655
2656 # this has been simplified as of #8456.
2657 # rule is: if we have a with_polymorphic or a concrete-style
2658 # polymorphic selectable, *or* if the base mapper has either of those,
2659 # we turn on the adaption thing. if not, we do *no* adaption.
2660 #
2661 # (UPDATE for #8168: the above comment was not accurate, as we were
2662 # still saying "do polymorphic" if we were using an auto-generated
2663 # flattened JOIN for with_polymorphic.)
2664 #
2665 # this splits the behavior among the "regular" joined inheritance
2666 # and single inheritance mappers, vs. the "weird / difficult"
2667 # concrete and joined inh mappings that use a with_polymorphic of
2668 # some kind or polymorphic_union.
2669 #
2670 # note we have some tests in test_polymorphic_rel that query against
2671 # a subclass, then refer to the superclass that has a with_polymorphic
2672 # on it (such as test_join_from_polymorphic_explicit_aliased_three).
2673 # these tests actually adapt the polymorphic selectable (like, the
2674 # UNION or the SELECT subquery with JOIN in it) to be just the simple
2675 # subclass table. Hence even if we are a "plain" inheriting mapper
2676 # but our base has a wpoly on it, we turn on adaption. This is a
2677 # legacy case we should probably disable.
2678 #
2679 #
2680 # UPDATE: simplified way more as of #8168. polymorphic adaption
2681 # is turned off even if with_polymorphic is set, as long as there
2682 # is no user-defined aliased selectable / subquery configured.
2683 # this scales back the use of polymorphic adaption in practice
2684 # to basically no cases except for concrete inheritance with a
2685 # polymorphic base class.
2686 #
2687 return (
2688 self._has_aliased_polymorphic_fromclause
2689 or self._requires_row_aliasing
2690 or (self.base_mapper._has_aliased_polymorphic_fromclause)
2691 or self.base_mapper._requires_row_aliasing
2692 )
2693
2694 @HasMemoized.memoized_attribute
2695 def _with_polymorphic_mappers(self) -> Sequence[Mapper[Any]]:
2696 self._check_configure()
2697
2698 if not self.with_polymorphic:
2699 return []
2700 return self._mappers_from_spec(*self.with_polymorphic)
2701
2702 @HasMemoized.memoized_attribute
2703 def _post_inspect(self):
2704 """This hook is invoked by attribute inspection.
2705
2706 E.g. when Query calls:
2707
2708 coercions.expect(roles.ColumnsClauseRole, ent, keep_inspect=True)
2709
2710 This allows the inspection process run a configure mappers hook.
2711
2712 """
2713 self._check_configure()
2714
2715 @HasMemoized_ro_memoized_attribute
2716 def _with_polymorphic_selectable(self) -> FromClause:
2717 if not self.with_polymorphic:
2718 return self.persist_selectable
2719
2720 spec, selectable = self.with_polymorphic
2721 if selectable is not None:
2722 return selectable
2723 else:
2724 return self._selectable_from_mappers(
2725 self._mappers_from_spec(spec, selectable), False
2726 )
2727
2728 with_polymorphic_mappers = _with_polymorphic_mappers
2729 """The list of :class:`_orm.Mapper` objects included in the
2730 default "polymorphic" query.
2731
2732 """
2733
2734 @HasMemoized_ro_memoized_attribute
2735 def _insert_cols_evaluating_none(self):
2736 return {
2737 table: frozenset(
2738 col for col in columns if col.type.should_evaluate_none
2739 )
2740 for table, columns in self._cols_by_table.items()
2741 }
2742
2743 @HasMemoized.memoized_attribute
2744 def _insert_cols_as_none(self):
2745 return {
2746 table: frozenset(
2747 col.key
2748 for col in columns
2749 if not col.primary_key
2750 and not col.server_default
2751 and not col.default
2752 and not col.type.should_evaluate_none
2753 )
2754 for table, columns in self._cols_by_table.items()
2755 }
2756
2757 @HasMemoized.memoized_attribute
2758 def _propkey_to_col(self):
2759 return {
2760 table: {self._columntoproperty[col].key: col for col in columns}
2761 for table, columns in self._cols_by_table.items()
2762 }
2763
2764 @HasMemoized.memoized_attribute
2765 def _pk_keys_by_table(self):
2766 return {
2767 table: frozenset([col.key for col in pks])
2768 for table, pks in self._pks_by_table.items()
2769 }
2770
2771 @HasMemoized.memoized_attribute
2772 def _pk_attr_keys_by_table(self):
2773 return {
2774 table: frozenset([self._columntoproperty[col].key for col in pks])
2775 for table, pks in self._pks_by_table.items()
2776 }
2777
2778 @HasMemoized.memoized_attribute
2779 def _server_default_cols(
2780 self,
2781 ) -> Mapping[FromClause, FrozenSet[Column[Any]]]:
2782 return {
2783 table: frozenset(
2784 [
2785 col
2786 for col in cast("Iterable[Column[Any]]", columns)
2787 if col.server_default is not None
2788 or (
2789 col.default is not None
2790 and col.default.is_clause_element
2791 )
2792 ]
2793 )
2794 for table, columns in self._cols_by_table.items()
2795 }
2796
2797 @HasMemoized.memoized_attribute
2798 def _server_onupdate_default_cols(
2799 self,
2800 ) -> Mapping[FromClause, FrozenSet[Column[Any]]]:
2801 return {
2802 table: frozenset(
2803 [
2804 col
2805 for col in cast("Iterable[Column[Any]]", columns)
2806 if col.server_onupdate is not None
2807 or (
2808 col.onupdate is not None
2809 and col.onupdate.is_clause_element
2810 )
2811 ]
2812 )
2813 for table, columns in self._cols_by_table.items()
2814 }
2815
2816 @HasMemoized.memoized_attribute
2817 def _server_default_col_keys(self) -> Mapping[FromClause, FrozenSet[str]]:
2818 return {
2819 table: frozenset(col.key for col in cols if col.key is not None)
2820 for table, cols in self._server_default_cols.items()
2821 }
2822
2823 @HasMemoized.memoized_attribute
2824 def _server_onupdate_default_col_keys(
2825 self,
2826 ) -> Mapping[FromClause, FrozenSet[str]]:
2827 return {
2828 table: frozenset(col.key for col in cols if col.key is not None)
2829 for table, cols in self._server_onupdate_default_cols.items()
2830 }
2831
2832 @HasMemoized.memoized_attribute
2833 def _server_default_plus_onupdate_propkeys(self) -> Set[str]:
2834 result: Set[str] = set()
2835
2836 col_to_property = self._columntoproperty
2837 for table, columns in self._server_default_cols.items():
2838 result.update(
2839 col_to_property[col].key
2840 for col in columns.intersection(col_to_property)
2841 )
2842 for table, columns in self._server_onupdate_default_cols.items():
2843 result.update(
2844 col_to_property[col].key
2845 for col in columns.intersection(col_to_property)
2846 )
2847 return result
2848
2849 @HasMemoized.memoized_instancemethod
2850 def __clause_element__(self):
2851 annotations: Dict[str, Any] = {
2852 "entity_namespace": self,
2853 "parententity": self,
2854 "parentmapper": self,
2855 }
2856 if self.persist_selectable is not self.local_table:
2857 # joined table inheritance, with polymorphic selectable,
2858 # etc.
2859 annotations["dml_table"] = self.local_table._annotate(
2860 {
2861 "entity_namespace": self,
2862 "parententity": self,
2863 "parentmapper": self,
2864 }
2865 )._set_propagate_attrs(
2866 {"compile_state_plugin": "orm", "plugin_subject": self}
2867 )
2868
2869 return self.selectable._annotate(annotations)._set_propagate_attrs(
2870 {"compile_state_plugin": "orm", "plugin_subject": self}
2871 )
2872
2873 @util.memoized_property
2874 def select_identity_token(self):
2875 return (
2876 expression.null()
2877 ._annotate(
2878 {
2879 "entity_namespace": self,
2880 "parententity": self,
2881 "parentmapper": self,
2882 "identity_token": True,
2883 }
2884 )
2885 ._set_propagate_attrs(
2886 {"compile_state_plugin": "orm", "plugin_subject": self}
2887 )
2888 )
2889
2890 @property
2891 def selectable(self) -> FromClause:
2892 """The :class:`_schema.FromClause` construct this
2893 :class:`_orm.Mapper` selects from by default.
2894
2895 Normally, this is equivalent to :attr:`.persist_selectable`, unless
2896 the ``with_polymorphic`` feature is in use, in which case the
2897 full "polymorphic" selectable is returned.
2898
2899 """
2900 return self._with_polymorphic_selectable
2901
2902 def _with_polymorphic_args(
2903 self,
2904 spec: Any = None,
2905 selectable: Union[Literal[False, None], FromClause] = False,
2906 innerjoin: bool = False,
2907 ) -> Tuple[Sequence[Mapper[Any]], FromClause]:
2908 if selectable not in (None, False):
2909 selectable = coercions.expect(
2910 roles.FromClauseRole,
2911 selectable,
2912 )
2913
2914 if self.with_polymorphic:
2915 if not spec:
2916 spec = self.with_polymorphic[0]
2917 if selectable is False:
2918 selectable = self.with_polymorphic[1]
2919 elif selectable is False:
2920 selectable = None
2921 mappers = self._mappers_from_spec(spec, selectable)
2922 if selectable is not None:
2923 return mappers, selectable
2924 else:
2925 return mappers, self._selectable_from_mappers(mappers, innerjoin)
2926
2927 @HasMemoized.memoized_attribute
2928 def _polymorphic_properties(self):
2929 return list(
2930 self._iterate_polymorphic_properties(
2931 self._with_polymorphic_mappers
2932 )
2933 )
2934
2935 @property
2936 def _all_column_expressions(self):
2937 poly_properties = self._polymorphic_properties
2938 adapter = self._polymorphic_adapter
2939
2940 return [
2941 adapter.columns[c] if adapter else c
2942 for prop in poly_properties
2943 if isinstance(prop, properties.ColumnProperty)
2944 and prop._renders_in_subqueries
2945 for c in prop.columns
2946 ]
2947
2948 def _columns_plus_keys(self, polymorphic_mappers=()):
2949 if polymorphic_mappers:
2950 poly_properties = self._iterate_polymorphic_properties(
2951 polymorphic_mappers
2952 )
2953 else:
2954 poly_properties = self._polymorphic_properties
2955
2956 return [
2957 (prop.key, prop.columns[0])
2958 for prop in poly_properties
2959 if isinstance(prop, properties.ColumnProperty)
2960 ]
2961
2962 @HasMemoized.memoized_attribute
2963 def _polymorphic_adapter(self) -> Optional[orm_util.ORMAdapter]:
2964 if self._has_aliased_polymorphic_fromclause:
2965 return orm_util.ORMAdapter(
2966 orm_util._TraceAdaptRole.MAPPER_POLYMORPHIC_ADAPTER,
2967 self,
2968 selectable=self.selectable,
2969 equivalents=self._equivalent_columns,
2970 limit_on_entity=False,
2971 )
2972 else:
2973 return None
2974
2975 def _iterate_polymorphic_properties(self, mappers=None):
2976 """Return an iterator of MapperProperty objects which will render into
2977 a SELECT."""
2978 if mappers is None:
2979 mappers = self._with_polymorphic_mappers
2980
2981 if not mappers:
2982 for c in self.iterate_properties:
2983 yield c
2984 else:
2985 # in the polymorphic case, filter out discriminator columns
2986 # from other mappers, as these are sometimes dependent on that
2987 # mapper's polymorphic selectable (which we don't want rendered)
2988 for c in util.unique_list(
2989 chain(
2990 *[
2991 list(mapper.iterate_properties)
2992 for mapper in [self] + mappers
2993 ]
2994 )
2995 ):
2996 if getattr(c, "_is_polymorphic_discriminator", False) and (
2997 self.polymorphic_on is None
2998 or c.columns[0] is not self.polymorphic_on
2999 ):
3000 continue
3001 yield c
3002
3003 @HasMemoized.memoized_attribute
3004 def attrs(self) -> util.ReadOnlyProperties[MapperProperty[Any]]:
3005 """A namespace of all :class:`.MapperProperty` objects
3006 associated this mapper.
3007
3008 This is an object that provides each property based on
3009 its key name. For instance, the mapper for a
3010 ``User`` class which has ``User.name`` attribute would
3011 provide ``mapper.attrs.name``, which would be the
3012 :class:`.ColumnProperty` representing the ``name``
3013 column. The namespace object can also be iterated,
3014 which would yield each :class:`.MapperProperty`.
3015
3016 :class:`_orm.Mapper` has several pre-filtered views
3017 of this attribute which limit the types of properties
3018 returned, including :attr:`.synonyms`, :attr:`.column_attrs`,
3019 :attr:`.relationships`, and :attr:`.composites`.
3020
3021 .. warning::
3022
3023 The :attr:`_orm.Mapper.attrs` accessor namespace is an
3024 instance of :class:`.OrderedProperties`. This is
3025 a dictionary-like object which includes a small number of
3026 named methods such as :meth:`.OrderedProperties.items`
3027 and :meth:`.OrderedProperties.values`. When
3028 accessing attributes dynamically, favor using the dict-access
3029 scheme, e.g. ``mapper.attrs[somename]`` over
3030 ``getattr(mapper.attrs, somename)`` to avoid name collisions.
3031
3032 .. seealso::
3033
3034 :attr:`_orm.Mapper.all_orm_descriptors`
3035
3036 """
3037
3038 self._check_configure()
3039 return util.ReadOnlyProperties(self._props)
3040
3041 @HasMemoized.memoized_attribute
3042 def all_orm_descriptors(self) -> util.ReadOnlyProperties[InspectionAttr]:
3043 """A namespace of all :class:`.InspectionAttr` attributes associated
3044 with the mapped class.
3045
3046 These attributes are in all cases Python :term:`descriptors`
3047 associated with the mapped class or its superclasses.
3048
3049 This namespace includes attributes that are mapped to the class
3050 as well as attributes declared by extension modules.
3051 It includes any Python descriptor type that inherits from
3052 :class:`.InspectionAttr`. This includes
3053 :class:`.QueryableAttribute`, as well as extension types such as
3054 :class:`.hybrid_property`, :class:`.hybrid_method` and
3055 :class:`.AssociationProxy`.
3056
3057 To distinguish between mapped attributes and extension attributes,
3058 the attribute :attr:`.InspectionAttr.extension_type` will refer
3059 to a constant that distinguishes between different extension types.
3060
3061 The sorting of the attributes is based on the following rules:
3062
3063 1. Iterate through the class and its superclasses in order from
3064 subclass to superclass (i.e. iterate through ``cls.__mro__``)
3065
3066 2. For each class, yield the attributes in the order in which they
3067 appear in ``__dict__``, with the exception of those in step
3068 3 below. The order will be the
3069 same as that of the class' construction, with the exception
3070 of attributes that were added after the fact by the application
3071 or the mapper.
3072
3073 3. If a certain attribute key is also in the superclass ``__dict__``,
3074 then it's included in the iteration for that class, and not the
3075 class in which it first appeared.
3076
3077 The above process produces an ordering that is deterministic in terms
3078 of the order in which attributes were assigned to the class.
3079
3080 When dealing with a :class:`.QueryableAttribute`, the
3081 :attr:`.QueryableAttribute.property` attribute refers to the
3082 :class:`.MapperProperty` property, which is what you get when
3083 referring to the collection of mapped properties via
3084 :attr:`_orm.Mapper.attrs`.
3085
3086 .. warning::
3087
3088 The :attr:`_orm.Mapper.all_orm_descriptors`
3089 accessor namespace is an
3090 instance of :class:`.OrderedProperties`. This is
3091 a dictionary-like object which includes a small number of
3092 named methods such as :meth:`.OrderedProperties.items`
3093 and :meth:`.OrderedProperties.values`. When
3094 accessing attributes dynamically, favor using the dict-access
3095 scheme, e.g. ``mapper.all_orm_descriptors[somename]`` over
3096 ``getattr(mapper.all_orm_descriptors, somename)`` to avoid name
3097 collisions.
3098
3099 .. seealso::
3100
3101 :attr:`_orm.Mapper.attrs`
3102
3103 """
3104 return util.ReadOnlyProperties(
3105 dict(self.class_manager._all_sqla_attributes())
3106 )
3107
3108 @HasMemoized.memoized_attribute
3109 @util.preload_module("sqlalchemy.orm.descriptor_props")
3110 def _pk_synonyms(self) -> Dict[str, str]:
3111 """return a dictionary of {syn_attribute_name: pk_attr_name} for
3112 all synonyms that refer to primary key columns
3113
3114 """
3115 descriptor_props = util.preloaded.orm_descriptor_props
3116
3117 pk_keys = {prop.key for prop in self._identity_key_props}
3118
3119 return {
3120 syn.key: syn.name
3121 for k, syn in self._props.items()
3122 if isinstance(syn, descriptor_props.SynonymProperty)
3123 and syn.name in pk_keys
3124 }
3125
3126 @HasMemoized.memoized_attribute
3127 @util.preload_module("sqlalchemy.orm.descriptor_props")
3128 def synonyms(self) -> util.ReadOnlyProperties[SynonymProperty[Any]]:
3129 """Return a namespace of all :class:`.Synonym`
3130 properties maintained by this :class:`_orm.Mapper`.
3131
3132 .. seealso::
3133
3134 :attr:`_orm.Mapper.attrs` - namespace of all
3135 :class:`.MapperProperty`
3136 objects.
3137
3138 """
3139 descriptor_props = util.preloaded.orm_descriptor_props
3140
3141 return self._filter_properties(descriptor_props.SynonymProperty)
3142
3143 @util.ro_non_memoized_property
3144 def entity_namespace(self) -> _EntityNamespace:
3145 return self.class_ # type: ignore[return-value]
3146
3147 @HasMemoized.memoized_attribute
3148 def column_attrs(self) -> util.ReadOnlyProperties[ColumnProperty[Any]]:
3149 """Return a namespace of all :class:`.ColumnProperty`
3150 properties maintained by this :class:`_orm.Mapper`.
3151
3152 .. seealso::
3153
3154 :attr:`_orm.Mapper.attrs` - namespace of all
3155 :class:`.MapperProperty`
3156 objects.
3157
3158 """
3159 return self._filter_properties(properties.ColumnProperty)
3160
3161 @HasMemoized.memoized_attribute
3162 @util.preload_module("sqlalchemy.orm.relationships")
3163 def relationships(
3164 self,
3165 ) -> util.ReadOnlyProperties[RelationshipProperty[Any]]:
3166 """A namespace of all :class:`.Relationship` properties
3167 maintained by this :class:`_orm.Mapper`.
3168
3169 .. warning::
3170
3171 the :attr:`_orm.Mapper.relationships` accessor namespace is an
3172 instance of :class:`.OrderedProperties`. This is
3173 a dictionary-like object which includes a small number of
3174 named methods such as :meth:`.OrderedProperties.items`
3175 and :meth:`.OrderedProperties.values`. When
3176 accessing attributes dynamically, favor using the dict-access
3177 scheme, e.g. ``mapper.relationships[somename]`` over
3178 ``getattr(mapper.relationships, somename)`` to avoid name
3179 collisions.
3180
3181 .. seealso::
3182
3183 :attr:`_orm.Mapper.attrs` - namespace of all
3184 :class:`.MapperProperty`
3185 objects.
3186
3187 """
3188 return self._filter_properties(
3189 util.preloaded.orm_relationships.RelationshipProperty
3190 )
3191
3192 @HasMemoized.memoized_attribute
3193 @util.preload_module("sqlalchemy.orm.descriptor_props")
3194 def composites(self) -> util.ReadOnlyProperties[CompositeProperty[Any]]:
3195 """Return a namespace of all :class:`.Composite`
3196 properties maintained by this :class:`_orm.Mapper`.
3197
3198 .. seealso::
3199
3200 :attr:`_orm.Mapper.attrs` - namespace of all
3201 :class:`.MapperProperty`
3202 objects.
3203
3204 """
3205 return self._filter_properties(
3206 util.preloaded.orm_descriptor_props.CompositeProperty
3207 )
3208
3209 def _filter_properties(
3210 self, type_: Type[_MP]
3211 ) -> util.ReadOnlyProperties[_MP]:
3212 self._check_configure()
3213 return util.ReadOnlyProperties(
3214 util.OrderedDict(
3215 (k, v) for k, v in self._props.items() if isinstance(v, type_)
3216 )
3217 )
3218
3219 @HasMemoized.memoized_attribute
3220 def _get_clause(self):
3221 """create a "get clause" based on the primary key. this is used
3222 by query.get() and many-to-one lazyloads to load this item
3223 by primary key.
3224
3225 """
3226 params = [
3227 (
3228 primary_key,
3229 sql.bindparam("pk_%d" % idx, type_=primary_key.type),
3230 )
3231 for idx, primary_key in enumerate(self.primary_key, 1)
3232 ]
3233 return (
3234 sql.and_(*[k == v for (k, v) in params]),
3235 util.column_dict(params),
3236 )
3237
3238 @HasMemoized.memoized_attribute
3239 def _equivalent_columns(self) -> _EquivalentColumnMap:
3240 """Create a map of all equivalent columns, based on
3241 the determination of column pairs that are equated to
3242 one another based on inherit condition. This is designed
3243 to work with the queries that util.polymorphic_union
3244 comes up with, which often don't include the columns from
3245 the base table directly (including the subclass table columns
3246 only).
3247
3248 The resulting structure is a dictionary of columns mapped
3249 to lists of equivalent columns, e.g.::
3250
3251 {tablea.col1: {tableb.col1, tablec.col1}, tablea.col2: {tabled.col2}}
3252
3253 """ # noqa: E501
3254 result: _EquivalentColumnMap = {}
3255
3256 def visit_binary(binary):
3257 if binary.operator == operators.eq:
3258 if binary.left in result:
3259 result[binary.left].add(binary.right)
3260 else:
3261 result[binary.left] = {binary.right}
3262 if binary.right in result:
3263 result[binary.right].add(binary.left)
3264 else:
3265 result[binary.right] = {binary.left}
3266
3267 for mapper in self.base_mapper.self_and_descendants:
3268 if mapper.inherit_condition is not None:
3269 visitors.traverse(
3270 mapper.inherit_condition, {}, {"binary": visit_binary}
3271 )
3272
3273 return result
3274
3275 def _is_userland_descriptor(self, assigned_name: str, obj: Any) -> bool:
3276 if isinstance(
3277 obj,
3278 (
3279 _MappedAttribute,
3280 instrumentation.ClassManager,
3281 expression.ColumnElement,
3282 ),
3283 ):
3284 return False
3285 else:
3286 return assigned_name not in self._dataclass_fields
3287
3288 @HasMemoized.memoized_attribute
3289 def _dataclass_fields(self):
3290 return [f.name for f in util.dataclass_fields(self.class_)]
3291
3292 def _should_exclude(self, name, assigned_name, local, column):
3293 """determine whether a particular property should be implicitly
3294 present on the class.
3295
3296 This occurs when properties are propagated from an inherited class, or
3297 are applied from the columns present in the mapped table.
3298
3299 """
3300
3301 if column is not None and sql_base._never_select_column(column):
3302 return True
3303
3304 # check for class-bound attributes and/or descriptors,
3305 # either local or from an inherited class
3306 # ignore dataclass field default values
3307 if local:
3308 if self.class_.__dict__.get(
3309 assigned_name, None
3310 ) is not None and self._is_userland_descriptor(
3311 assigned_name, self.class_.__dict__[assigned_name]
3312 ):
3313 return True
3314 else:
3315 attr = self.class_manager._get_class_attr_mro(assigned_name, None)
3316 if attr is not None and self._is_userland_descriptor(
3317 assigned_name, attr
3318 ):
3319 return True
3320
3321 columns = [column] if column is not None else []
3322 if self._excluded_by_include_exclude(name, columns):
3323 self._warn_for_excluded_declared_attr(name, columns)
3324 return True
3325
3326 return False
3327
3328 def _excluded_by_include_exclude(
3329 self, name: str, columns: Sequence[KeyedColumnElement[Any]]
3330 ) -> bool:
3331 if (
3332 self.include_properties is not None
3333 and name not in self.include_properties
3334 and self.include_properties.isdisjoint(columns)
3335 ):
3336 self._log("not including property %s" % (name))
3337 return True
3338
3339 if self.exclude_properties is not None and (
3340 name in self.exclude_properties
3341 or not self.exclude_properties.isdisjoint(columns)
3342 ):
3343 self._log("excluding property %s" % (name))
3344 return True
3345
3346 return False
3347
3348 def _warn_for_excluded_declared_attr(
3349 self, name: str, columns: Sequence[KeyedColumnElement[Any]]
3350 ) -> None:
3351 """warn for include_properties / exclude_properties that exclude
3352 an attribute declared on a Declarative class, #13650.
3353
3354 Columns that are present only in the Table, e.g. from ``__table__``,
3355 ``__table_args__`` or reflection, may be excluded without warning.
3356
3357 """
3358 decl_scan_ref = self.class_manager.declarative_scan
3359 mapper_config = decl_scan_ref() if decl_scan_ref is not None else None
3360 if mapper_config is None or not mapper_config.is_declarative:
3361 return
3362 decl_scan = cast("_DeclarativeMapperConfig", mapper_config)
3363
3364 if any(
3365 col in decl_scan.declared_columns
3366 and col not in decl_scan.unmapped_columns
3367 for col in columns
3368 ):
3369 util.warn_deprecated(
3370 f"Attribute {name!r} declared on class {self.class_} is "
3371 "excluded from the mapping by the exclude_properties / "
3372 "include_properties mapper arguments; excluding attributes "
3373 "that are declared on the class is deprecated, and does not "
3374 "reliably remove them from the mapping. To declare a column "
3375 "that is part of the Table but is not mapped, use the "
3376 "Unmapped[] annotation with unmapped_column().",
3377 "2.1.5",
3378 )
3379
3380 def common_parent(self, other: Mapper[Any]) -> bool:
3381 """Return true if the given mapper shares a
3382 common inherited parent as this mapper."""
3383
3384 return self.base_mapper is other.base_mapper
3385
3386 def is_sibling(self, other: Mapper[Any]) -> bool:
3387 """return true if the other mapper is an inheriting sibling to this
3388 one. common parent but different branch
3389
3390 """
3391 return (
3392 self.base_mapper is other.base_mapper
3393 and not self.isa(other)
3394 and not other.isa(self)
3395 )
3396
3397 def _canload(
3398 self, state: InstanceState[Any], allow_subtypes: bool
3399 ) -> bool:
3400 s = self.primary_mapper()
3401 if self.polymorphic_on is not None or allow_subtypes:
3402 return _state_mapper(state).isa(s)
3403 else:
3404 return _state_mapper(state) is s
3405
3406 def isa(self, other: Mapper[Any]) -> bool:
3407 """Return True if the this mapper inherits from the given mapper."""
3408
3409 m: Optional[Mapper[Any]] = self
3410 while m and m is not other:
3411 m = m.inherits
3412 return bool(m)
3413
3414 def iterate_to_root(self) -> Iterator[Mapper[Any]]:
3415 m: Optional[Mapper[Any]] = self
3416 while m:
3417 yield m
3418 m = m.inherits
3419
3420 @HasMemoized.memoized_attribute
3421 def self_and_descendants(self) -> Sequence[Mapper[Any]]:
3422 """The collection including this mapper and all descendant mappers.
3423
3424 This includes not just the immediately inheriting mappers but
3425 all their inheriting mappers as well.
3426
3427 """
3428 descendants = []
3429 stack = deque([self])
3430 while stack:
3431 item = stack.popleft()
3432 descendants.append(item)
3433 stack.extend(item._inheriting_mappers)
3434 return util.WeakSequence(descendants)
3435
3436 def polymorphic_iterator(self) -> Iterator[Mapper[Any]]:
3437 """Iterate through the collection including this mapper and
3438 all descendant mappers.
3439
3440 This includes not just the immediately inheriting mappers but
3441 all their inheriting mappers as well.
3442
3443 To iterate through an entire hierarchy, use
3444 ``mapper.base_mapper.polymorphic_iterator()``.
3445
3446 """
3447 return iter(self.self_and_descendants)
3448
3449 def primary_mapper(self) -> Mapper[Any]:
3450 """Return the primary mapper corresponding to this mapper's class key
3451 (class)."""
3452
3453 return self.class_manager.mapper
3454
3455 @property
3456 def primary_base_mapper(self) -> Mapper[Any]:
3457 return self.class_manager.mapper.base_mapper
3458
3459 def _result_has_identity_key(self, result, adapter=None):
3460 pk_cols: Sequence[ColumnElement[Any]]
3461 if adapter is not None:
3462 pk_cols = [adapter.columns[c] for c in self.primary_key]
3463 else:
3464 pk_cols = self.primary_key
3465 rk = result.keys()
3466 for col in pk_cols:
3467 if col not in rk:
3468 return False
3469 else:
3470 return True
3471
3472 def identity_key_from_row(
3473 self,
3474 row: Union[Row[Unpack[TupleAny]], RowMapping],
3475 identity_token: Optional[Any] = None,
3476 adapter: Optional[ORMAdapter] = None,
3477 ) -> _IdentityKeyType[_O]:
3478 """Return an identity-map key for use in storing/retrieving an
3479 item from the identity map.
3480
3481 :param row: A :class:`.Row` or :class:`.RowMapping` produced from a
3482 result set that selected from the ORM mapped primary key columns.
3483
3484 .. versionchanged:: 2.0
3485 :class:`.Row` or :class:`.RowMapping` are accepted
3486 for the "row" argument
3487
3488 """
3489 pk_cols: Sequence[ColumnElement[Any]]
3490 if adapter is not None:
3491 pk_cols = [adapter.columns[c] for c in self.primary_key]
3492 else:
3493 pk_cols = self.primary_key
3494
3495 mapping: RowMapping
3496 if hasattr(row, "_mapping"):
3497 mapping = row._mapping
3498 else:
3499 mapping = row # type: ignore[assignment]
3500
3501 return (
3502 self._identity_class,
3503 tuple(mapping[column] for column in pk_cols),
3504 identity_token,
3505 )
3506
3507 def identity_key_from_primary_key(
3508 self,
3509 primary_key: Tuple[Any, ...],
3510 identity_token: Optional[Any] = None,
3511 ) -> _IdentityKeyType[_O]:
3512 """Return an identity-map key for use in storing/retrieving an
3513 item from an identity map.
3514
3515 :param primary_key: A list of values indicating the identifier.
3516
3517 """
3518 return (
3519 self._identity_class,
3520 tuple(primary_key),
3521 identity_token,
3522 )
3523
3524 def identity_key_from_instance(self, instance: _O) -> _IdentityKeyType[_O]:
3525 """Return the identity key for the given instance, based on
3526 its primary key attributes.
3527
3528 If the instance's state is expired, calling this method
3529 will result in a database check to see if the object has been deleted.
3530 If the row no longer exists,
3531 :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised.
3532
3533 This value is typically also found on the instance state under the
3534 attribute name `key`.
3535
3536 """
3537 state = attributes.instance_state(instance)
3538 return self._identity_key_from_state(state, PassiveFlag.PASSIVE_OFF)
3539
3540 def _identity_key_from_state(
3541 self,
3542 state: InstanceState[_O],
3543 passive: PassiveFlag = PassiveFlag.PASSIVE_RETURN_NO_VALUE,
3544 ) -> _IdentityKeyType[_O]:
3545 dict_ = state.dict
3546 manager = state.manager
3547 return (
3548 self._identity_class,
3549 tuple(
3550 [
3551 manager[prop.key].impl.get(state, dict_, passive)
3552 for prop in self._identity_key_props
3553 ]
3554 ),
3555 state.identity_token,
3556 )
3557
3558 def primary_key_from_instance(self, instance: _O) -> Tuple[Any, ...]:
3559 """Return the list of primary key values for the given
3560 instance.
3561
3562 If the instance's state is expired, calling this method
3563 will result in a database check to see if the object has been deleted.
3564 If the row no longer exists,
3565 :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised.
3566
3567 """
3568 state = attributes.instance_state(instance)
3569 identity_key = self._identity_key_from_state(
3570 state, PassiveFlag.PASSIVE_OFF
3571 )
3572 return identity_key[1]
3573
3574 @HasMemoized.memoized_attribute
3575 def _persistent_sortkey_fn(self):
3576 key_fns = [col.type.sort_key_function for col in self.primary_key]
3577
3578 if set(key_fns).difference([None]):
3579
3580 def key(state):
3581 return tuple(
3582 key_fn(val) if key_fn is not None else val
3583 for key_fn, val in zip(key_fns, state.key[1])
3584 )
3585
3586 else:
3587
3588 def key(state):
3589 return state.key[1]
3590
3591 return key
3592
3593 @HasMemoized.memoized_attribute
3594 def _identity_key_props(self):
3595 return [self._columntoproperty[col] for col in self.primary_key]
3596
3597 @HasMemoized.memoized_attribute
3598 def _all_pk_cols(self):
3599 collection: Set[ColumnClause[Any]] = set()
3600 for table in self.tables:
3601 collection.update(self._pks_by_table[table])
3602 return collection
3603
3604 @HasMemoized.memoized_attribute
3605 def _should_undefer_in_wildcard(self):
3606 cols: Set[ColumnElement[Any]] = set(self.primary_key)
3607 if self.polymorphic_on is not None:
3608 cols.add(self.polymorphic_on)
3609 return cols
3610
3611 @HasMemoized.memoized_attribute
3612 def _primary_key_propkeys(self):
3613 return {self._columntoproperty[col].key for col in self._all_pk_cols}
3614
3615 def _get_state_attr_by_column(
3616 self,
3617 state: InstanceState[_O],
3618 dict_: _InstanceDict,
3619 column: ColumnElement[Any],
3620 passive: PassiveFlag = PassiveFlag.PASSIVE_RETURN_NO_VALUE,
3621 ) -> Any:
3622 prop = self._columntoproperty[column]
3623 return state.manager[prop.key].impl.get(state, dict_, passive=passive)
3624
3625 def _state_ident_getter(self, columns, passive):
3626 lookup_keys = [self._columntoproperty[col].key for col in columns]
3627 missing = object()
3628
3629 def get_ident(state, state_dict):
3630 return tuple(
3631 (
3632 v
3633 if (v := state_dict.get(lk, missing)) is not missing
3634 else self._get_state_attr_by_column(
3635 state, state_dict, col, passive=passive
3636 )
3637 )
3638 for lk, col in zip(lookup_keys, columns)
3639 )
3640
3641 return get_ident
3642
3643 def _set_committed_state_attr_by_column(self, state, dict_, column, value):
3644 prop = self._columntoproperty[column]
3645 state.manager[prop.key].impl.set_committed_value(state, dict_, value)
3646
3647 def _set_state_attr_by_column(self, state, dict_, column, value):
3648 prop = self._columntoproperty[column]
3649 state.manager[prop.key].impl.set(state, dict_, value, None)
3650
3651 def _get_committed_attr_by_column(self, obj, column):
3652 state = attributes.instance_state(obj)
3653 dict_ = attributes.instance_dict(obj)
3654 return self._get_committed_state_attr_by_column(
3655 state, dict_, column, passive=PassiveFlag.PASSIVE_OFF
3656 )
3657
3658 def _get_committed_state_attr_by_column(
3659 self, state, dict_, column, passive=PassiveFlag.PASSIVE_RETURN_NO_VALUE
3660 ):
3661 prop = self._columntoproperty[column]
3662 return state.manager[prop.key].impl.get_committed_value(
3663 state, dict_, passive=passive
3664 )
3665
3666 def _optimized_get_statement(self, state, attribute_names):
3667 """assemble a WHERE clause which retrieves a given state by primary
3668 key, using a minimized set of tables.
3669
3670 Applies to a joined-table inheritance mapper where the
3671 requested attribute names are only present on joined tables,
3672 not the base table. The WHERE clause attempts to include
3673 only those tables to minimize joins.
3674
3675 """
3676 props = self._props
3677
3678 col_attribute_names = set(attribute_names).intersection(
3679 state.mapper.column_attrs.keys()
3680 )
3681 tables: Set[FromClause] = set(
3682 chain(
3683 *[
3684 sql_util.find_tables(c, check_columns=True)
3685 for key in col_attribute_names
3686 for c in props[key].columns
3687 ]
3688 )
3689 )
3690
3691 if self.base_mapper.local_table in tables:
3692 return None
3693
3694 def visit_binary(binary):
3695 leftcol = binary.left
3696 rightcol = binary.right
3697 if leftcol is None or rightcol is None:
3698 return
3699
3700 if leftcol.table not in tables:
3701 leftval = self._get_committed_state_attr_by_column(
3702 state,
3703 state.dict,
3704 leftcol,
3705 passive=PassiveFlag.PASSIVE_NO_INITIALIZE,
3706 )
3707 if leftval in orm_util._none_set:
3708 raise _OptGetColumnsNotAvailable()
3709 binary.left = sql.bindparam(
3710 None, leftval, type_=binary.right.type
3711 )
3712 elif rightcol.table not in tables:
3713 rightval = self._get_committed_state_attr_by_column(
3714 state,
3715 state.dict,
3716 rightcol,
3717 passive=PassiveFlag.PASSIVE_NO_INITIALIZE,
3718 )
3719 if rightval in orm_util._none_set:
3720 raise _OptGetColumnsNotAvailable()
3721 binary.right = sql.bindparam(
3722 None, rightval, type_=binary.right.type
3723 )
3724
3725 allconds: List[ColumnElement[bool]] = []
3726
3727 start = False
3728
3729 # as of #7507, from the lowest base table on upwards,
3730 # we include all intermediary tables.
3731
3732 for mapper in reversed(list(self.iterate_to_root())):
3733 if mapper.local_table in tables:
3734 start = True
3735 elif not isinstance(mapper.local_table, expression.TableClause):
3736 return None
3737 if start and not mapper.single:
3738 assert mapper.inherits
3739 assert not mapper.concrete
3740 assert mapper.inherit_condition is not None
3741 allconds.append(mapper.inherit_condition)
3742 tables.add(mapper.local_table)
3743
3744 # only the bottom table needs its criteria to be altered to fit
3745 # the primary key ident - the rest of the tables upwards to the
3746 # descendant-most class should all be present and joined to each
3747 # other.
3748 try:
3749 _traversed = visitors.cloned_traverse(
3750 allconds[0], {}, {"binary": visit_binary}
3751 )
3752 except _OptGetColumnsNotAvailable:
3753 return None
3754 else:
3755 allconds[0] = _traversed
3756
3757 cond = sql.and_(*allconds)
3758
3759 cols = []
3760 for key in col_attribute_names:
3761 cols.extend(props[key].columns)
3762 return (
3763 sql.select(*cols)
3764 .where(cond)
3765 .set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL)
3766 )
3767
3768 def _iterate_to_target_viawpoly(self, mapper):
3769 if self.isa(mapper):
3770 prev = self
3771 for m in self.iterate_to_root():
3772 yield m
3773
3774 if m is not prev and prev not in m._with_polymorphic_mappers:
3775 break
3776
3777 prev = m
3778 if m is mapper:
3779 break
3780
3781 @HasMemoized.memoized_attribute
3782 def _would_selectinload_combinations_cache(self):
3783 return {}
3784
3785 def _would_selectin_load_only_from_given_mapper(self, super_mapper):
3786 """return True if this mapper would "selectin" polymorphic load based
3787 on the given super mapper, and not from a setting from a subclass.
3788
3789 given::
3790
3791 class A: ...
3792
3793
3794 class B(A):
3795 __mapper_args__ = {"polymorphic_load": "selectin"}
3796
3797
3798 class C(B): ...
3799
3800
3801 class D(B):
3802 __mapper_args__ = {"polymorphic_load": "selectin"}
3803
3804 ``inspect(C)._would_selectin_load_only_from_given_mapper(inspect(B))``
3805 returns True, because C does selectin loading because of B's setting.
3806
3807 OTOH, ``inspect(D)
3808 ._would_selectin_load_only_from_given_mapper(inspect(B))``
3809 returns False, because D does selectin loading because of its own
3810 setting; when we are doing a selectin poly load from B, we want to
3811 filter out D because it would already have its own selectin poly load
3812 set up separately.
3813
3814 Added as part of #9373.
3815
3816 """
3817 cache = self._would_selectinload_combinations_cache
3818
3819 try:
3820 return cache[super_mapper]
3821 except KeyError:
3822 pass
3823
3824 # assert that given object is a supermapper, meaning we already
3825 # strong reference it directly or indirectly. this allows us
3826 # to not worry that we are creating new strongrefs to unrelated
3827 # mappers or other objects.
3828 assert self.isa(super_mapper)
3829
3830 mapper = super_mapper
3831 for m in self._iterate_to_target_viawpoly(mapper):
3832 if m.polymorphic_load == "selectin":
3833 retval = m is super_mapper
3834 break
3835 else:
3836 retval = False
3837
3838 cache[super_mapper] = retval
3839 return retval
3840
3841 def _should_selectin_load(self, enabled_via_opt, polymorphic_from):
3842 if not enabled_via_opt:
3843 # common case, takes place for all polymorphic loads
3844 mapper = polymorphic_from
3845 for m in self._iterate_to_target_viawpoly(mapper):
3846 if m.polymorphic_load == "selectin":
3847 return m
3848 else:
3849 # uncommon case, selectin load options were used
3850 enabled_via_opt = set(enabled_via_opt)
3851 enabled_via_opt_mappers = {e.mapper: e for e in enabled_via_opt}
3852 for entity in enabled_via_opt.union([polymorphic_from]):
3853 mapper = entity.mapper
3854 for m in self._iterate_to_target_viawpoly(mapper):
3855 if (
3856 m.polymorphic_load == "selectin"
3857 or m in enabled_via_opt_mappers
3858 ):
3859 return enabled_via_opt_mappers.get(m, m)
3860
3861 return None
3862
3863 @util.preload_module("sqlalchemy.orm.strategy_options")
3864 def _subclass_load_via_in(self, entity, polymorphic_from):
3865 """Assemble a that can load the columns local to
3866 this subclass as a SELECT with IN.
3867
3868 """
3869
3870 strategy_options = util.preloaded.orm_strategy_options
3871
3872 assert self.inherits
3873
3874 if self.polymorphic_on is not None:
3875 polymorphic_prop = self._columntoproperty[self.polymorphic_on]
3876 keep_props = set([polymorphic_prop] + self._identity_key_props)
3877 else:
3878 keep_props = set(self._identity_key_props)
3879
3880 disable_opt = strategy_options.Load(entity)
3881 enable_opt = strategy_options.Load(entity)
3882
3883 classes_to_include = {self}
3884 m: Optional[Mapper[Any]] = self.inherits
3885 while (
3886 m is not None
3887 and m is not polymorphic_from
3888 and m.polymorphic_load == "selectin"
3889 ):
3890 classes_to_include.add(m)
3891 m = m.inherits
3892
3893 for prop in self.column_attrs + self.relationships:
3894 # skip prop keys that are not instrumented on the mapped class.
3895 # this is primarily the "_sa_polymorphic_on" property that gets
3896 # created for an ad-hoc polymorphic_on SQL expression, issue #8704
3897 if prop.key not in self.class_manager:
3898 continue
3899
3900 if prop.parent in classes_to_include or prop in keep_props:
3901 # "enable" options, to turn on the properties that we want to
3902 # load by default (subject to options from the query)
3903 if not isinstance(prop, StrategizedProperty):
3904 continue
3905
3906 enable_opt = enable_opt._set_generic_strategy(
3907 # convert string name to an attribute before passing
3908 # to loader strategy. note this must be in terms
3909 # of given entity, such as AliasedClass, etc.
3910 (getattr(entity.entity_namespace, prop.key),),
3911 dict(prop.strategy_key),
3912 _reconcile_to_other=True,
3913 )
3914 else:
3915 # "disable" options, to turn off the properties from the
3916 # superclass that we *don't* want to load, applied after
3917 # the options from the query to override them
3918 disable_opt = disable_opt._set_generic_strategy(
3919 # convert string name to an attribute before passing
3920 # to loader strategy. note this must be in terms
3921 # of given entity, such as AliasedClass, etc.
3922 (getattr(entity.entity_namespace, prop.key),),
3923 {"do_nothing": True},
3924 _reconcile_to_other=False,
3925 )
3926
3927 primary_key = list(self.primary_key)
3928
3929 in_expr: ColumnElement[Any]
3930
3931 if len(primary_key) > 1:
3932 in_expr = sql.tuple_(*primary_key)
3933 else:
3934 in_expr = primary_key[0]
3935
3936 if entity.is_aliased_class:
3937 assert entity.mapper is self
3938
3939 q = sql.select(entity).set_label_style(
3940 LABEL_STYLE_TABLENAME_PLUS_COL
3941 )
3942
3943 in_expr = entity._adapter.traverse(in_expr)
3944 q = q.where(
3945 in_expr.in_(sql.bindparam("primary_keys", expanding=True))
3946 )
3947 else:
3948 q = sql.select(self).set_label_style(
3949 LABEL_STYLE_TABLENAME_PLUS_COL
3950 )
3951 q = q.where(
3952 in_expr.in_(sql.bindparam("primary_keys", expanding=True))
3953 )
3954
3955 return q, enable_opt, disable_opt
3956
3957 @HasMemoized.memoized_attribute
3958 def _subclass_load_via_in_mapper(self):
3959 # the default is loading this mapper against the basemost mapper
3960 return self._subclass_load_via_in(self, self.base_mapper)
3961
3962 def cascade_iterator(
3963 self,
3964 type_: str,
3965 state: InstanceState[_O],
3966 halt_on: Optional[Callable[[InstanceState[Any]], bool]] = None,
3967 ) -> Iterator[
3968 Tuple[object, Mapper[Any], InstanceState[Any], _InstanceDict]
3969 ]:
3970 r"""Iterate each element and its mapper in an object graph,
3971 for all relationships that meet the given cascade rule.
3972
3973 :param type\_:
3974 The name of the cascade rule (i.e. ``"save-update"``, ``"delete"``,
3975 etc.).
3976
3977 .. note:: the ``"all"`` cascade is not accepted here. For a generic
3978 object traversal function, see :ref:`faq_walk_objects`.
3979
3980 :param state:
3981 The lead InstanceState. child items will be processed per
3982 the relationships defined for this object's mapper.
3983
3984 :return: the method yields individual object instances.
3985
3986 .. seealso::
3987
3988 :ref:`unitofwork_cascades`
3989
3990 :ref:`faq_walk_objects` - illustrates a generic function to
3991 traverse all objects without relying on cascades.
3992
3993 """
3994 visited_states: Set[InstanceState[Any]] = set()
3995 prp, mpp = object(), object()
3996
3997 assert state.mapper.isa(self)
3998
3999 # this is actually a recursive structure, fully typing it seems
4000 # a little too difficult for what it's worth here
4001 visitables: Deque[
4002 Tuple[
4003 Deque[Any],
4004 object,
4005 Optional[InstanceState[Any]],
4006 Optional[_InstanceDict],
4007 ]
4008 ]
4009
4010 visitables = deque(
4011 [(deque(state.mapper._props.values()), prp, state, state.dict)]
4012 )
4013
4014 while visitables:
4015 iterator, item_type, parent_state, parent_dict = visitables[-1]
4016 if not iterator:
4017 visitables.pop()
4018 continue
4019
4020 if item_type is prp:
4021 prop = iterator.popleft()
4022 if not prop.cascade or type_ not in prop.cascade:
4023 continue
4024 assert parent_state is not None
4025 assert parent_dict is not None
4026 queue = deque(
4027 prop.cascade_iterator(
4028 type_,
4029 parent_state,
4030 parent_dict,
4031 visited_states,
4032 halt_on,
4033 )
4034 )
4035 if queue:
4036 visitables.append((queue, mpp, None, None))
4037 elif item_type is mpp:
4038 (
4039 instance,
4040 instance_mapper,
4041 corresponding_state,
4042 corresponding_dict,
4043 ) = iterator.popleft()
4044 yield (
4045 instance,
4046 instance_mapper,
4047 corresponding_state,
4048 corresponding_dict,
4049 )
4050 visitables.append(
4051 (
4052 deque(instance_mapper._props.values()),
4053 prp,
4054 corresponding_state,
4055 corresponding_dict,
4056 )
4057 )
4058
4059 @HasMemoized.memoized_attribute
4060 def _compiled_cache(self):
4061 return util.LRUCache(self._compiled_cache_size)
4062
4063 @HasMemoized.memoized_attribute
4064 def _multiple_persistence_tables(self):
4065 return len(self.tables) > 1
4066
4067 @HasMemoized.memoized_attribute
4068 def _sorted_tables(self):
4069 table_to_mapper: Dict[TableClause, Mapper[Any]] = {}
4070
4071 for mapper in self.base_mapper.self_and_descendants:
4072 for t in mapper.tables:
4073 table_to_mapper.setdefault(t, mapper)
4074
4075 extra_dependencies = []
4076 for table, mapper in table_to_mapper.items():
4077 super_ = mapper.inherits
4078 if super_:
4079 extra_dependencies.extend(
4080 [(super_table, table) for super_table in super_.tables]
4081 )
4082
4083 def skip(fk):
4084 # attempt to skip dependencies that are not
4085 # significant to the inheritance chain
4086 # for two tables that are related by inheritance.
4087 # while that dependency may be important, it's technically
4088 # not what we mean to sort on here.
4089 parent = table_to_mapper.get(fk.parent.table)
4090 dep = table_to_mapper.get(fk.column.table)
4091 if (
4092 parent is not None
4093 and dep is not None
4094 and dep is not parent
4095 and dep.inherit_condition is not None
4096 ):
4097 cols = set(sql_util._find_columns(dep.inherit_condition))
4098 if parent.inherit_condition is not None:
4099 cols = cols.union(
4100 sql_util._find_columns(parent.inherit_condition)
4101 )
4102 return fk.parent not in cols and fk.column not in cols
4103 else:
4104 return fk.parent not in cols
4105 return False
4106
4107 sorted_ = sql_util.sort_tables(
4108 table_to_mapper,
4109 skip_fn=skip,
4110 extra_dependencies=extra_dependencies,
4111 )
4112
4113 ret = util.OrderedDict()
4114 for t in sorted_:
4115 ret[t] = table_to_mapper[t]
4116 return ret
4117
4118 def _memo(self, key: Any, callable_: Callable[[], _T]) -> _T:
4119 if key in self._memoized_values:
4120 return cast(_T, self._memoized_values[key])
4121 else:
4122 self._memoized_values[key] = value = callable_()
4123 return value
4124
4125 @util.memoized_property
4126 def _table_to_equated(self):
4127 """memoized map of tables to collections of columns to be
4128 synchronized upwards to the base mapper."""
4129
4130 result: util.defaultdict[
4131 Table,
4132 List[
4133 Tuple[
4134 Mapper[Any],
4135 List[Tuple[ColumnElement[Any], ColumnElement[Any]]],
4136 ]
4137 ],
4138 ] = util.defaultdict(list)
4139
4140 def set_union(x, y):
4141 return x.union(y)
4142
4143 for table in self._sorted_tables:
4144 cols = set(table.c)
4145
4146 for m in self.iterate_to_root():
4147 if m._inherits_equated_pairs and cols.intersection(
4148 reduce(
4149 set_union,
4150 [l.proxy_set for l, r in m._inherits_equated_pairs],
4151 )
4152 ):
4153 result[table].append((m, m._inherits_equated_pairs))
4154
4155 return result
4156
4157
4158class _OptGetColumnsNotAvailable(Exception):
4159 pass
4160
4161
4162def configure_mappers() -> None:
4163 """Initialize the inter-mapper relationships of all mappers that
4164 have been constructed thus far across all :class:`_orm.registry`
4165 collections.
4166
4167 The configure step is used to reconcile and initialize the
4168 :func:`_orm.relationship` linkages between mapped classes, as well as to
4169 invoke configuration events such as the
4170 :meth:`_orm.MapperEvents.before_configured` and
4171 :meth:`_orm.MapperEvents.after_configured`, which may be used by ORM
4172 extensions or user-defined extension hooks.
4173
4174 Mapper configuration is normally invoked automatically, the first time
4175 mappings from a particular :class:`_orm.registry` are used, as well as
4176 whenever mappings are used and additional not-yet-configured mappers have
4177 been constructed. The automatic configuration process however is local only
4178 to the :class:`_orm.registry` involving the target mapper and any related
4179 :class:`_orm.registry` objects which it may depend on; this is
4180 equivalent to invoking the :meth:`_orm.registry.configure` method
4181 on a particular :class:`_orm.registry`.
4182
4183 By contrast, the :func:`_orm.configure_mappers` function will invoke the
4184 configuration process on all :class:`_orm.registry` objects that
4185 exist in memory, and may be useful for scenarios where many individual
4186 :class:`_orm.registry` objects that are nonetheless interrelated are
4187 in use.
4188
4189 .. versionchanged:: 1.4
4190
4191 As of SQLAlchemy 1.4.0b2, this function works on a
4192 per-:class:`_orm.registry` basis, locating all :class:`_orm.registry`
4193 objects present and invoking the :meth:`_orm.registry.configure` method
4194 on each. The :meth:`_orm.registry.configure` method may be preferred to
4195 limit the configuration of mappers to those local to a particular
4196 :class:`_orm.registry` and/or declarative base class.
4197
4198 Points at which automatic configuration is invoked include when a mapped
4199 class is instantiated into an instance, as well as when ORM queries
4200 are emitted using :meth:`.Session.query` or :meth:`_orm.Session.execute`
4201 with an ORM-enabled statement.
4202
4203 The mapper configure process, whether invoked by
4204 :func:`_orm.configure_mappers` or from :meth:`_orm.registry.configure`,
4205 provides several event hooks that can be used to augment the mapper
4206 configuration step. These hooks include:
4207
4208 * :meth:`.MapperEvents.before_configured` - called once before
4209 :func:`.configure_mappers` or :meth:`_orm.registry.configure` does any
4210 work; this can be used to establish additional options, properties, or
4211 related mappings before the operation proceeds.
4212
4213 * :meth:`.RegistryEvents.before_configured` - Like
4214 :meth:`.MapperEvents.before_configured`, but local to a specific
4215 :class:`_orm.registry`.
4216
4217 .. versionadded:: 2.1 - added :meth:`.RegistryEvents.before_configured`
4218
4219 * :meth:`.MapperEvents.mapper_configured` - called as each individual
4220 :class:`_orm.Mapper` is configured within the process; will include all
4221 mapper state except for backrefs set up by other mappers that are still
4222 to be configured.
4223
4224 * :meth:`.MapperEvents.after_configured` - called once after
4225 :func:`.configure_mappers` or :meth:`_orm.registry.configure` is
4226 complete; at this stage, all :class:`_orm.Mapper` objects that fall
4227 within the scope of the configuration operation will be fully configured.
4228 Note that the calling application may still have other mappings that
4229 haven't been produced yet, such as if they are in modules as yet
4230 unimported, and may also have mappings that are still to be configured,
4231 if they are in other :class:`_orm.registry` collections not part of the
4232 current scope of configuration.
4233
4234 * :meth:`.RegistryEvents.after_configured` - Like
4235 :meth:`.MapperEvents.after_configured`, but local to a specific
4236 :class:`_orm.registry`.
4237
4238 .. versionadded:: 2.1 - added :meth:`.RegistryEvents.after_configured`
4239
4240 """
4241
4242 _configure_registries(_all_registries(), cascade=True)
4243
4244
4245def _configure_registries(
4246 registries: Set[_RegistryType], cascade: bool
4247) -> None:
4248 for reg in registries:
4249 if reg._new_mappers:
4250 break
4251 else:
4252 return
4253
4254 with _CONFIGURE_MUTEX:
4255 global _already_compiling
4256 if _already_compiling:
4257 return
4258 _already_compiling = True
4259 try:
4260 # double-check inside mutex
4261 for reg in registries:
4262 if reg._new_mappers:
4263 break
4264 else:
4265 return
4266
4267 Mapper.dispatch._for_class(Mapper).before_configured() # type: ignore[arg-type, call-arg, misc] # noqa: E501
4268
4269 # initialize properties on all mappers
4270 # note that _mapper_registry is unordered, which
4271 # may randomly conceal/reveal issues related to
4272 # the order of mapper compilation
4273
4274 registries_configured = list(
4275 _do_configure_registries(registries, cascade)
4276 )
4277
4278 finally:
4279 _already_compiling = False
4280 for reg in registries_configured:
4281 reg.dispatch.after_configured(reg)
4282 Mapper.dispatch._for_class(Mapper).after_configured() # type: ignore[arg-type, call-arg, misc] # noqa: E501
4283
4284
4285@util.preload_module("sqlalchemy.orm.decl_api")
4286def _do_configure_registries(
4287 registries: Set[_RegistryType], cascade: bool
4288) -> Iterator[registry]:
4289 registry = util.preloaded.orm_decl_api.registry
4290
4291 orig = set(registries)
4292
4293 for reg in registry._recurse_with_dependencies(registries):
4294 if reg._new_mappers:
4295 reg.dispatch.before_configured(reg)
4296
4297 has_skip = False
4298
4299 for mapper in reg._mappers_to_configure():
4300 run_configure = None
4301
4302 for fn in mapper.dispatch.before_mapper_configured:
4303 run_configure = fn(mapper, mapper.class_)
4304 if run_configure is EXT_SKIP:
4305 has_skip = True
4306 break
4307 if run_configure is EXT_SKIP:
4308 continue
4309
4310 if getattr(mapper, "_configure_failed", False):
4311 e = sa_exc.InvalidRequestError(
4312 "One or more mappers failed to initialize - "
4313 "can't proceed with initialization of other "
4314 "mappers. Triggering mapper: '%s'. "
4315 "Original exception was: %s"
4316 % (mapper, mapper._configure_failed)
4317 )
4318 e._configure_failed = mapper._configure_failed # type: ignore[attr-defined] # noqa: E501
4319 raise e
4320
4321 if not mapper.configured:
4322 try:
4323 mapper._post_configure_properties()
4324 mapper._expire_memoizations()
4325 mapper.dispatch.mapper_configured(mapper, mapper.class_)
4326 except Exception:
4327 exc = sys.exc_info()[1]
4328 if not hasattr(exc, "_configure_failed"):
4329 mapper._configure_failed = exc
4330 raise
4331
4332 if reg._new_mappers:
4333 yield reg
4334 if not has_skip:
4335 reg._new_mappers = False
4336
4337 if not cascade and reg._dependencies.difference(orig):
4338 raise sa_exc.InvalidRequestError(
4339 "configure was called with cascade=False but "
4340 "additional registries remain"
4341 )
4342
4343
4344@util.preload_module("sqlalchemy.orm.decl_api")
4345def _dispose_registries(registries: Set[_RegistryType], cascade: bool) -> None:
4346 registry = util.preloaded.orm_decl_api.registry
4347
4348 orig = set(registries)
4349
4350 for reg in registry._recurse_with_dependents(registries):
4351 if not cascade and reg._dependents.difference(orig):
4352 raise sa_exc.InvalidRequestError(
4353 "Registry has dependent registries that are not disposed; "
4354 "pass cascade=True to clear these also"
4355 )
4356
4357 while reg._managers:
4358 try:
4359 manager, _ = reg._managers.popitem()
4360 except KeyError:
4361 # guard against race between while and popitem
4362 pass
4363 else:
4364 reg._dispose_manager_and_mapper(manager)
4365
4366 reg._dependents.clear()
4367 for dep in reg._dependencies:
4368 dep._dependents.discard(reg)
4369 reg._dependencies.clear()
4370 # this wasn't done in the 1.3 clear_mappers() and in fact it
4371 # was a bug, as it could cause configure_mappers() to invoke
4372 # the "before_configured" event even though mappers had all been
4373 # disposed.
4374 reg._new_mappers = False
4375
4376
4377def reconstructor(fn: _Fn) -> _Fn:
4378 """Decorate a method as the 'reconstructor' hook.
4379
4380 Designates a single method as the "reconstructor", an ``__init__``-like
4381 method that will be called by the ORM after the instance has been
4382 loaded from the database or otherwise reconstituted.
4383
4384 .. tip::
4385
4386 The :func:`_orm.reconstructor` decorator makes use of the
4387 :meth:`_orm.InstanceEvents.load` event hook, which can be
4388 used directly.
4389
4390 The reconstructor will be invoked with no arguments. Scalar
4391 (non-collection) database-mapped attributes of the instance will
4392 be available for use within the function. Eagerly-loaded
4393 collections are generally not yet available and will usually only
4394 contain the first element. ORM state changes made to objects at
4395 this stage will not be recorded for the next flush() operation, so
4396 the activity within a reconstructor should be conservative.
4397
4398 .. seealso::
4399
4400 :meth:`.InstanceEvents.load`
4401
4402 """
4403 fn.__sa_reconstructor__ = True # type: ignore[attr-defined]
4404 return fn
4405
4406
4407def validates(
4408 *names: str, include_removes: bool = False, include_backrefs: bool = True
4409) -> Callable[[_Fn], _Fn]:
4410 r"""Decorate a method as a 'validator' for one or more named properties.
4411
4412 Designates a method as a validator, a method which receives the
4413 name of the attribute as well as a value to be assigned, or in the
4414 case of a collection, the value to be added to the collection.
4415 The function can then raise validation exceptions to halt the
4416 process from continuing (where Python's built-in ``ValueError``
4417 and ``AssertionError`` exceptions are reasonable choices), or can
4418 modify or replace the value before proceeding. The function should
4419 otherwise return the given value.
4420
4421 When a subclass overrides a validator for the same attribute using
4422 the same method name, only the subclass validator is invoked. The
4423 subclass validator may call ``super()`` to also invoke the parent
4424 class validator.
4425
4426 A subclass that overrides a validator using the same method name
4427 as the parent now replaces the parent validator entirely, rather
4428 than the parent validator being invoked unconditionally. The
4429 subclass validator may opt in to the parent's behavior by calling
4430 ``super()``. See :ref:`validators_subclass_override` for
4431 background on this change.
4432
4433 .. versionchanged:: 2.1 Added support for overriding of validators
4434 on subclasses.
4435
4436 Note that a validator for a collection **cannot** issue a load of that
4437 collection within the validation routine - this usage raises
4438 an assertion to avoid recursion overflows. This is a reentrant
4439 condition which is not supported.
4440
4441 :param \*names: list of attribute names to be validated.
4442 :param include_removes: if True, "remove" events will be
4443 sent as well - the validation function must accept an additional
4444 argument "is_remove" which will be a boolean.
4445
4446 :param include_backrefs: defaults to ``True``; if ``False``, the
4447 validation function will not emit if the originator is an attribute
4448 event related via a backref. This can be used for bi-directional
4449 :func:`.validates` usage where only one validator should emit per
4450 attribute operation.
4451
4452 .. versionchanged:: 2.0.16 This parameter inadvertently defaulted to
4453 ``False`` for releases 2.0.0 through 2.0.15. Its correct default
4454 of ``True`` is restored in 2.0.16.
4455
4456 .. seealso::
4457
4458 :ref:`simple_validators` - usage examples for :func:`.validates`
4459
4460 :ref:`validators_subclass_override` - overriding validators in
4461 subclasses
4462
4463 """
4464
4465 def wrap(fn: _Fn) -> _Fn:
4466 fn.__sa_validators__ = names # type: ignore[attr-defined]
4467 fn.__sa_validation_opts__ = { # type: ignore[attr-defined]
4468 "include_removes": include_removes,
4469 "include_backrefs": include_backrefs,
4470 }
4471 return fn
4472
4473 return wrap
4474
4475
4476def _event_on_load(state, ctx):
4477 instrumenting_mapper = state.manager.mapper
4478
4479 if instrumenting_mapper._reconstructor:
4480 instrumenting_mapper._reconstructor(state.obj())
4481
4482
4483def _event_on_init(state, args, kwargs):
4484 """Run init_instance hooks.
4485
4486 This also includes mapper compilation, normally not needed
4487 here but helps with some piecemeal configuration
4488 scenarios (such as in the ORM tutorial).
4489
4490 """
4491
4492 instrumenting_mapper = state.manager.mapper
4493 if instrumenting_mapper:
4494 instrumenting_mapper._check_configure()
4495 if instrumenting_mapper._set_polymorphic_identity:
4496 instrumenting_mapper._set_polymorphic_identity(state)
4497
4498
4499class _ColumnMapping(Dict["ColumnElement[Any]", "MapperProperty[Any]"]):
4500 """Error reporting helper for mapper._columntoproperty."""
4501
4502 __slots__ = ("mapper",)
4503
4504 def __init__(self, mapper):
4505 # TODO: weakref would be a good idea here
4506 self.mapper = mapper
4507
4508 def __missing__(self, column):
4509 prop = self.mapper._props.get(column)
4510 if prop:
4511 raise orm_exc.UnmappedColumnError(
4512 "Column '%s.%s' is not available, due to "
4513 "conflicting property '%s':%r"
4514 % (column.table.name, column.name, column.key, prop)
4515 )
4516 raise orm_exc.UnmappedColumnError(
4517 "No column %s is configured on mapper %s..."
4518 % (column, self.mapper)
4519 )