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