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