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