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