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