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