Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/mapper.py: 38%
1186 statements
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
1# orm/mapper.py
2# Copyright (C) 2005-2022 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
8"""Logic to map Python classes to and from selectables.
10Defines the :class:`~sqlalchemy.orm.mapper.Mapper` class, the central
11configurational unit which associates a class with a database table.
13This is a semi-private module; the main configurational API of the ORM is
14available in :class:`~sqlalchemy.orm.`.
16"""
17from __future__ import absolute_import
19from collections import deque
20from itertools import chain
21import sys
22import weakref
24from . import attributes
25from . import exc as orm_exc
26from . import instrumentation
27from . import loading
28from . import properties
29from . import util as orm_util
30from .base import _class_to_mapper
31from .base import _state_mapper
32from .base import class_mapper
33from .base import state_str
34from .interfaces import _MappedAttribute
35from .interfaces import EXT_SKIP
36from .interfaces import InspectionAttr
37from .interfaces import MapperProperty
38from .interfaces import ORMEntityColumnsClauseRole
39from .interfaces import ORMFromClauseRole
40from .interfaces import StrategizedProperty
41from .path_registry import PathRegistry
42from .. import event
43from .. import exc as sa_exc
44from .. import inspection
45from .. import log
46from .. import schema
47from .. import sql
48from .. import util
49from ..sql import base as sql_base
50from ..sql import coercions
51from ..sql import expression
52from ..sql import operators
53from ..sql import roles
54from ..sql import util as sql_util
55from ..sql import visitors
56from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
57from ..util import HasMemoized
59_mapper_registries = weakref.WeakKeyDictionary()
61_legacy_registry = None
64def _all_registries():
65 with _CONFIGURE_MUTEX:
66 return set(_mapper_registries)
69def _unconfigured_mappers():
70 for reg in _all_registries():
71 for mapper in reg._mappers_to_configure():
72 yield mapper
75_already_compiling = False
78# a constant returned by _get_attr_by_column to indicate
79# this mapper is not handling an attribute for a particular
80# column
81NO_ATTRIBUTE = util.symbol("NO_ATTRIBUTE")
83# lock used to synchronize the "mapper configure" step
84_CONFIGURE_MUTEX = util.threading.RLock()
87@inspection._self_inspects
88@log.class_logger
89class Mapper(
90 ORMFromClauseRole,
91 ORMEntityColumnsClauseRole,
92 sql_base.MemoizedHasCacheKey,
93 InspectionAttr,
94):
95 """Defines an association between a Python class and a database table or
96 other relational structure, so that ORM operations against the class may
97 proceed.
99 The :class:`_orm.Mapper` object is instantiated using mapping methods
100 present on the :class:`_orm.registry` object. For information
101 about instantiating new :class:`_orm.Mapper` objects, see
102 :ref:`orm_mapping_classes_toplevel`.
104 """
106 _dispose_called = False
107 _ready_for_configure = False
109 @util.deprecated_params(
110 non_primary=(
111 "1.3",
112 "The :paramref:`.mapper.non_primary` parameter is deprecated, "
113 "and will be removed in a future release. The functionality "
114 "of non primary mappers is now better suited using the "
115 ":class:`.AliasedClass` construct, which can also be used "
116 "as the target of a :func:`_orm.relationship` in 1.3.",
117 ),
118 )
119 def __init__(
120 self,
121 class_,
122 local_table=None,
123 properties=None,
124 primary_key=None,
125 non_primary=False,
126 inherits=None,
127 inherit_condition=None,
128 inherit_foreign_keys=None,
129 always_refresh=False,
130 version_id_col=None,
131 version_id_generator=None,
132 polymorphic_on=None,
133 _polymorphic_map=None,
134 polymorphic_identity=None,
135 concrete=False,
136 with_polymorphic=None,
137 polymorphic_load=None,
138 allow_partial_pks=True,
139 batch=True,
140 column_prefix=None,
141 include_properties=None,
142 exclude_properties=None,
143 passive_updates=True,
144 passive_deletes=False,
145 confirm_deleted_rows=True,
146 eager_defaults=False,
147 legacy_is_orphan=False,
148 _compiled_cache_size=100,
149 ):
150 r"""Direct constructor for a new :class:`_orm.Mapper` object.
152 The :func:`_orm.mapper` function is normally invoked through the
153 use of the :class:`_orm.registry` object through either the
154 :ref:`Declarative <orm_declarative_mapping>` or
155 :ref:`Imperative <orm_imperative_mapping>` mapping styles.
157 .. versionchanged:: 1.4 The :func:`_orm.mapper` function should not
158 be called directly for classical mapping; for a classical mapping
159 configuration, use the :meth:`_orm.registry.map_imperatively`
160 method. The :func:`_orm.mapper` function may become private in a
161 future release.
163 Parameters documented below may be passed to either the
164 :meth:`_orm.registry.map_imperatively` method, or may be passed in the
165 ``__mapper_args__`` declarative class attribute described at
166 :ref:`orm_declarative_mapper_options`.
168 :param class\_: The class to be mapped. When using Declarative,
169 this argument is automatically passed as the declared class
170 itself.
172 :param local_table: The :class:`_schema.Table` or other selectable
173 to which the class is mapped. May be ``None`` if
174 this mapper inherits from another mapper using single-table
175 inheritance. When using Declarative, this argument is
176 automatically passed by the extension, based on what
177 is configured via the ``__table__`` argument or via the
178 :class:`_schema.Table`
179 produced as a result of the ``__tablename__``
180 and :class:`_schema.Column` arguments present.
182 :param always_refresh: If True, all query operations for this mapped
183 class will overwrite all data within object instances that already
184 exist within the session, erasing any in-memory changes with
185 whatever information was loaded from the database. Usage of this
186 flag is highly discouraged; as an alternative, see the method
187 :meth:`_query.Query.populate_existing`.
189 :param allow_partial_pks: Defaults to True. Indicates that a
190 composite primary key with some NULL values should be considered as
191 possibly existing within the database. This affects whether a
192 mapper will assign an incoming row to an existing identity, as well
193 as if :meth:`.Session.merge` will check the database first for a
194 particular primary key value. A "partial primary key" can occur if
195 one has mapped to an OUTER JOIN, for example.
197 :param batch: Defaults to ``True``, indicating that save operations
198 of multiple entities can be batched together for efficiency.
199 Setting to False indicates
200 that an instance will be fully saved before saving the next
201 instance. This is used in the extremely rare case that a
202 :class:`.MapperEvents` listener requires being called
203 in between individual row persistence operations.
205 :param column_prefix: A string which will be prepended
206 to the mapped attribute name when :class:`_schema.Column`
207 objects are automatically assigned as attributes to the
208 mapped class. Does not affect :class:`.Column` objects that
209 are mapped explicitly in the :paramref:`.mapper.properties`
210 dictionary.
212 This parameter is typically useful with imperative mappings
213 that keep the :class:`.Table` object separate. Below, assuming
214 the ``user_table`` :class:`.Table` object has columns named
215 ``user_id``, ``user_name``, and ``password``::
217 class User(Base):
218 __table__ = user_table
219 __mapper_args__ = {'column_prefix':'_'}
221 The above mapping will assign the ``user_id``, ``user_name``, and
222 ``password`` columns to attributes named ``_user_id``,
223 ``_user_name``, and ``_password`` on the mapped ``User`` class.
225 The :paramref:`.mapper.column_prefix` parameter is uncommon in
226 modern use. For dealing with reflected tables, a more flexible
227 approach to automating a naming scheme is to intercept the
228 :class:`.Column` objects as they are reflected; see the section
229 :ref:`mapper_automated_reflection_schemes` for notes on this usage
230 pattern.
232 :param concrete: If True, indicates this mapper should use concrete
233 table inheritance with its parent mapper.
235 See the section :ref:`concrete_inheritance` for an example.
237 :param confirm_deleted_rows: defaults to True; when a DELETE occurs
238 of one more rows based on specific primary keys, a warning is
239 emitted when the number of rows matched does not equal the number
240 of rows expected. This parameter may be set to False to handle the
241 case where database ON DELETE CASCADE rules may be deleting some of
242 those rows automatically. The warning may be changed to an
243 exception in a future release.
245 .. versionadded:: 0.9.4 - added
246 :paramref:`.mapper.confirm_deleted_rows` as well as conditional
247 matched row checking on delete.
249 :param eager_defaults: if True, the ORM will immediately fetch the
250 value of server-generated default values after an INSERT or UPDATE,
251 rather than leaving them as expired to be fetched on next access.
252 This can be used for event schemes where the server-generated values
253 are needed immediately before the flush completes. By default,
254 this scheme will emit an individual ``SELECT`` statement per row
255 inserted or updated, which note can add significant performance
256 overhead. However, if the
257 target database supports :term:`RETURNING`, the default values will
258 be returned inline with the INSERT or UPDATE statement, which can
259 greatly enhance performance for an application that needs frequent
260 access to just-generated server defaults.
262 .. seealso::
264 :ref:`orm_server_defaults`
266 .. versionchanged:: 0.9.0 The ``eager_defaults`` option can now
267 make use of :term:`RETURNING` for backends which support it.
269 :param exclude_properties: A list or set of string column names to
270 be excluded from mapping.
272 See :ref:`include_exclude_cols` for an example.
274 :param include_properties: An inclusive list or set of string column
275 names to map.
277 See :ref:`include_exclude_cols` for an example.
279 :param inherits: A mapped class or the corresponding
280 :class:`_orm.Mapper`
281 of one indicating a superclass to which this :class:`_orm.Mapper`
282 should *inherit* from. The mapped class here must be a subclass
283 of the other mapper's class. When using Declarative, this argument
284 is passed automatically as a result of the natural class
285 hierarchy of the declared classes.
287 .. seealso::
289 :ref:`inheritance_toplevel`
291 :param inherit_condition: For joined table inheritance, a SQL
292 expression which will
293 define how the two tables are joined; defaults to a natural join
294 between the two tables.
296 :param inherit_foreign_keys: When ``inherit_condition`` is used and
297 the columns present are missing a :class:`_schema.ForeignKey`
298 configuration, this parameter can be used to specify which columns
299 are "foreign". In most cases can be left as ``None``.
301 :param legacy_is_orphan: Boolean, defaults to ``False``.
302 When ``True``, specifies that "legacy" orphan consideration
303 is to be applied to objects mapped by this mapper, which means
304 that a pending (that is, not persistent) object is auto-expunged
305 from an owning :class:`.Session` only when it is de-associated
306 from *all* parents that specify a ``delete-orphan`` cascade towards
307 this mapper. The new default behavior is that the object is
308 auto-expunged when it is de-associated with *any* of its parents
309 that specify ``delete-orphan`` cascade. This behavior is more
310 consistent with that of a persistent object, and allows behavior to
311 be consistent in more scenarios independently of whether or not an
312 orphan object has been flushed yet or not.
314 See the change note and example at :ref:`legacy_is_orphan_addition`
315 for more detail on this change.
317 :param non_primary: Specify that this :class:`_orm.Mapper`
318 is in addition
319 to the "primary" mapper, that is, the one used for persistence.
320 The :class:`_orm.Mapper` created here may be used for ad-hoc
321 mapping of the class to an alternate selectable, for loading
322 only.
324 .. seealso::
326 :ref:`relationship_aliased_class` - the new pattern that removes
327 the need for the :paramref:`_orm.Mapper.non_primary` flag.
329 :param passive_deletes: Indicates DELETE behavior of foreign key
330 columns when a joined-table inheritance entity is being deleted.
331 Defaults to ``False`` for a base mapper; for an inheriting mapper,
332 defaults to ``False`` unless the value is set to ``True``
333 on the superclass mapper.
335 When ``True``, it is assumed that ON DELETE CASCADE is configured
336 on the foreign key relationships that link this mapper's table
337 to its superclass table, so that when the unit of work attempts
338 to delete the entity, it need only emit a DELETE statement for the
339 superclass table, and not this table.
341 When ``False``, a DELETE statement is emitted for this mapper's
342 table individually. If the primary key attributes local to this
343 table are unloaded, then a SELECT must be emitted in order to
344 validate these attributes; note that the primary key columns
345 of a joined-table subclass are not part of the "primary key" of
346 the object as a whole.
348 Note that a value of ``True`` is **always** forced onto the
349 subclass mappers; that is, it's not possible for a superclass
350 to specify passive_deletes without this taking effect for
351 all subclass mappers.
353 .. versionadded:: 1.1
355 .. seealso::
357 :ref:`passive_deletes` - description of similar feature as
358 used with :func:`_orm.relationship`
360 :paramref:`.mapper.passive_updates` - supporting ON UPDATE
361 CASCADE for joined-table inheritance mappers
363 :param passive_updates: Indicates UPDATE behavior of foreign key
364 columns when a primary key column changes on a joined-table
365 inheritance mapping. Defaults to ``True``.
367 When True, it is assumed that ON UPDATE CASCADE is configured on
368 the foreign key in the database, and that the database will handle
369 propagation of an UPDATE from a source column to dependent columns
370 on joined-table rows.
372 When False, it is assumed that the database does not enforce
373 referential integrity and will not be issuing its own CASCADE
374 operation for an update. The unit of work process will
375 emit an UPDATE statement for the dependent columns during a
376 primary key change.
378 .. seealso::
380 :ref:`passive_updates` - description of a similar feature as
381 used with :func:`_orm.relationship`
383 :paramref:`.mapper.passive_deletes` - supporting ON DELETE
384 CASCADE for joined-table inheritance mappers
386 :param polymorphic_load: Specifies "polymorphic loading" behavior
387 for a subclass in an inheritance hierarchy (joined and single
388 table inheritance only). Valid values are:
390 * "'inline'" - specifies this class should be part of the
391 "with_polymorphic" mappers, e.g. its columns will be included
392 in a SELECT query against the base.
394 * "'selectin'" - specifies that when instances of this class
395 are loaded, an additional SELECT will be emitted to retrieve
396 the columns specific to this subclass. The SELECT uses
397 IN to fetch multiple subclasses at once.
399 .. versionadded:: 1.2
401 .. seealso::
403 :ref:`with_polymorphic_mapper_config`
405 :ref:`polymorphic_selectin`
407 :param polymorphic_on: Specifies the column, attribute, or
408 SQL expression used to determine the target class for an
409 incoming row, when inheriting classes are present.
411 This value is commonly a :class:`_schema.Column` object that's
412 present in the mapped :class:`_schema.Table`::
414 class Employee(Base):
415 __tablename__ = 'employee'
417 id = Column(Integer, primary_key=True)
418 discriminator = Column(String(50))
420 __mapper_args__ = {
421 "polymorphic_on":discriminator,
422 "polymorphic_identity":"employee"
423 }
425 It may also be specified
426 as a SQL expression, as in this example where we
427 use the :func:`.case` construct to provide a conditional
428 approach::
430 class Employee(Base):
431 __tablename__ = 'employee'
433 id = Column(Integer, primary_key=True)
434 discriminator = Column(String(50))
436 __mapper_args__ = {
437 "polymorphic_on":case([
438 (discriminator == "EN", "engineer"),
439 (discriminator == "MA", "manager"),
440 ], else_="employee"),
441 "polymorphic_identity":"employee"
442 }
444 It may also refer to any attribute
445 configured with :func:`.column_property`, or to the
446 string name of one::
448 class Employee(Base):
449 __tablename__ = 'employee'
451 id = Column(Integer, primary_key=True)
452 discriminator = Column(String(50))
453 employee_type = column_property(
454 case([
455 (discriminator == "EN", "engineer"),
456 (discriminator == "MA", "manager"),
457 ], else_="employee")
458 )
460 __mapper_args__ = {
461 "polymorphic_on":employee_type,
462 "polymorphic_identity":"employee"
463 }
465 When setting ``polymorphic_on`` to reference an
466 attribute or expression that's not present in the
467 locally mapped :class:`_schema.Table`, yet the value
468 of the discriminator should be persisted to the database,
469 the value of the
470 discriminator is not automatically set on new
471 instances; this must be handled by the user,
472 either through manual means or via event listeners.
473 A typical approach to establishing such a listener
474 looks like::
476 from sqlalchemy import event
477 from sqlalchemy.orm import object_mapper
479 @event.listens_for(Employee, "init", propagate=True)
480 def set_identity(instance, *arg, **kw):
481 mapper = object_mapper(instance)
482 instance.discriminator = mapper.polymorphic_identity
484 Where above, we assign the value of ``polymorphic_identity``
485 for the mapped class to the ``discriminator`` attribute,
486 thus persisting the value to the ``discriminator`` column
487 in the database.
489 .. warning::
491 Currently, **only one discriminator column may be set**, typically
492 on the base-most class in the hierarchy. "Cascading" polymorphic
493 columns are not yet supported.
495 .. seealso::
497 :ref:`inheritance_toplevel`
499 :param polymorphic_identity: Specifies the value which
500 identifies this particular class as returned by the
501 column expression referred to by the ``polymorphic_on``
502 setting. As rows are received, the value corresponding
503 to the ``polymorphic_on`` column expression is compared
504 to this value, indicating which subclass should
505 be used for the newly reconstructed object.
507 :param properties: A dictionary mapping the string names of object
508 attributes to :class:`.MapperProperty` instances, which define the
509 persistence behavior of that attribute. Note that
510 :class:`_schema.Column`
511 objects present in
512 the mapped :class:`_schema.Table` are automatically placed into
513 ``ColumnProperty`` instances upon mapping, unless overridden.
514 When using Declarative, this argument is passed automatically,
515 based on all those :class:`.MapperProperty` instances declared
516 in the declared class body.
518 .. seealso::
520 :ref:`orm_mapping_properties` - in the
521 :ref:`orm_mapping_classes_toplevel`
523 :param primary_key: A list of :class:`_schema.Column`
524 objects which define
525 the primary key to be used against this mapper's selectable unit.
526 This is normally simply the primary key of the ``local_table``, but
527 can be overridden here.
529 .. seealso::
531 :ref:`mapper_primary_key` - background and example use
533 :param version_id_col: A :class:`_schema.Column`
534 that will be used to keep a running version id of rows
535 in the table. This is used to detect concurrent updates or
536 the presence of stale data in a flush. The methodology is to
537 detect if an UPDATE statement does not match the last known
538 version id, a
539 :class:`~sqlalchemy.orm.exc.StaleDataError` exception is
540 thrown.
541 By default, the column must be of :class:`.Integer` type,
542 unless ``version_id_generator`` specifies an alternative version
543 generator.
545 .. seealso::
547 :ref:`mapper_version_counter` - discussion of version counting
548 and rationale.
550 :param version_id_generator: Define how new version ids should
551 be generated. Defaults to ``None``, which indicates that
552 a simple integer counting scheme be employed. To provide a custom
553 versioning scheme, provide a callable function of the form::
555 def generate_version(version):
556 return next_version
558 Alternatively, server-side versioning functions such as triggers,
559 or programmatic versioning schemes outside of the version id
560 generator may be used, by specifying the value ``False``.
561 Please see :ref:`server_side_version_counter` for a discussion
562 of important points when using this option.
564 .. versionadded:: 0.9.0 ``version_id_generator`` supports
565 server-side version number generation.
567 .. seealso::
569 :ref:`custom_version_counter`
571 :ref:`server_side_version_counter`
574 :param with_polymorphic: A tuple in the form ``(<classes>,
575 <selectable>)`` indicating the default style of "polymorphic"
576 loading, that is, which tables are queried at once. <classes> is
577 any single or list of mappers and/or classes indicating the
578 inherited classes that should be loaded at once. The special value
579 ``'*'`` may be used to indicate all descending classes should be
580 loaded immediately. The second tuple argument <selectable>
581 indicates a selectable that will be used to query for multiple
582 classes.
584 .. seealso::
586 :ref:`with_polymorphic` - discussion of polymorphic querying
587 techniques.
589 """
590 self.class_ = util.assert_arg_type(class_, type, "class_")
591 self._sort_key = "%s.%s" % (
592 self.class_.__module__,
593 self.class_.__name__,
594 )
596 self.class_manager = None
598 self._primary_key_argument = util.to_list(primary_key)
599 self.non_primary = non_primary
601 self.always_refresh = always_refresh
603 if isinstance(version_id_col, MapperProperty):
604 self.version_id_prop = version_id_col
605 self.version_id_col = None
606 else:
607 self.version_id_col = version_id_col
608 if version_id_generator is False:
609 self.version_id_generator = False
610 elif version_id_generator is None:
611 self.version_id_generator = lambda x: (x or 0) + 1
612 else:
613 self.version_id_generator = version_id_generator
615 self.concrete = concrete
616 self.single = False
617 self.inherits = inherits
618 if local_table is not None:
619 self.local_table = coercions.expect(
620 roles.StrictFromClauseRole, local_table
621 )
622 else:
623 self.local_table = None
625 self.inherit_condition = inherit_condition
626 self.inherit_foreign_keys = inherit_foreign_keys
627 self._init_properties = properties or {}
628 self._delete_orphans = []
629 self.batch = batch
630 self.eager_defaults = eager_defaults
631 self.column_prefix = column_prefix
632 self.polymorphic_on = (
633 coercions.expect(
634 roles.ColumnArgumentOrKeyRole,
635 polymorphic_on,
636 argname="polymorphic_on",
637 )
638 if polymorphic_on is not None
639 else None
640 )
641 self._dependency_processors = []
642 self.validators = util.EMPTY_DICT
643 self.passive_updates = passive_updates
644 self.passive_deletes = passive_deletes
645 self.legacy_is_orphan = legacy_is_orphan
646 self._clause_adapter = None
647 self._requires_row_aliasing = False
648 self._inherits_equated_pairs = None
649 self._memoized_values = {}
650 self._compiled_cache_size = _compiled_cache_size
651 self._reconstructor = None
652 self.allow_partial_pks = allow_partial_pks
654 if self.inherits and not self.concrete:
655 self.confirm_deleted_rows = False
656 else:
657 self.confirm_deleted_rows = confirm_deleted_rows
659 self._set_with_polymorphic(with_polymorphic)
660 self.polymorphic_load = polymorphic_load
662 # our 'polymorphic identity', a string name that when located in a
663 # result set row indicates this Mapper should be used to construct
664 # the object instance for that row.
665 self.polymorphic_identity = polymorphic_identity
667 # a dictionary of 'polymorphic identity' names, associating those
668 # names with Mappers that will be used to construct object instances
669 # upon a select operation.
670 if _polymorphic_map is None:
671 self.polymorphic_map = {}
672 else:
673 self.polymorphic_map = _polymorphic_map
675 if include_properties is not None:
676 self.include_properties = util.to_set(include_properties)
677 else:
678 self.include_properties = None
679 if exclude_properties:
680 self.exclude_properties = util.to_set(exclude_properties)
681 else:
682 self.exclude_properties = None
684 # prevent this mapper from being constructed
685 # while a configure_mappers() is occurring (and defer a
686 # configure_mappers() until construction succeeds)
687 with _CONFIGURE_MUTEX:
688 self.dispatch._events._new_mapper_instance(class_, self)
689 self._configure_inheritance()
690 self._configure_class_instrumentation()
691 self._configure_properties()
692 self._configure_polymorphic_setter()
693 self._configure_pks()
694 self.registry._flag_new_mapper(self)
695 self._log("constructed")
696 self._expire_memoizations()
698 # major attributes initialized at the classlevel so that
699 # they can be Sphinx-documented.
701 is_mapper = True
702 """Part of the inspection API."""
704 represents_outer_join = False
706 @property
707 def mapper(self):
708 """Part of the inspection API.
710 Returns self.
712 """
713 return self
715 def _gen_cache_key(self, anon_map, bindparams):
716 return (self,)
718 @property
719 def entity(self):
720 r"""Part of the inspection API.
722 Returns self.class\_.
724 """
725 return self.class_
727 local_table = None
728 """The :class:`_expression.Selectable` which this :class:`_orm.Mapper`
729 manages.
731 Typically is an instance of :class:`_schema.Table` or
732 :class:`_expression.Alias`.
733 May also be ``None``.
735 The "local" table is the
736 selectable that the :class:`_orm.Mapper` is directly responsible for
737 managing from an attribute access and flush perspective. For
738 non-inheriting mappers, the local table is the same as the
739 "mapped" table. For joined-table inheritance mappers, local_table
740 will be the particular sub-table of the overall "join" which
741 this :class:`_orm.Mapper` represents. If this mapper is a
742 single-table inheriting mapper, local_table will be ``None``.
744 .. seealso::
746 :attr:`_orm.Mapper.persist_selectable`.
748 """
750 persist_selectable = None
751 """The :class:`_expression.Selectable` to which this :class:`_orm.Mapper`
752 is mapped.
754 Typically an instance of :class:`_schema.Table`,
755 :class:`_expression.Join`, or :class:`_expression.Alias`.
757 The :attr:`_orm.Mapper.persist_selectable` is separate from
758 :attr:`_orm.Mapper.selectable` in that the former represents columns
759 that are mapped on this class or its superclasses, whereas the
760 latter may be a "polymorphic" selectable that contains additional columns
761 which are in fact mapped on subclasses only.
763 "persist selectable" is the "thing the mapper writes to" and
764 "selectable" is the "thing the mapper selects from".
766 :attr:`_orm.Mapper.persist_selectable` is also separate from
767 :attr:`_orm.Mapper.local_table`, which represents the set of columns that
768 are locally mapped on this class directly.
771 .. seealso::
773 :attr:`_orm.Mapper.selectable`.
775 :attr:`_orm.Mapper.local_table`.
777 """
779 inherits = None
780 """References the :class:`_orm.Mapper` which this :class:`_orm.Mapper`
781 inherits from, if any.
783 This is a *read only* attribute determined during mapper construction.
784 Behavior is undefined if directly modified.
786 """
788 configured = False
789 """Represent ``True`` if this :class:`_orm.Mapper` has been configured.
791 This is a *read only* attribute determined during mapper construction.
792 Behavior is undefined if directly modified.
794 .. seealso::
796 :func:`.configure_mappers`.
798 """
800 concrete = None
801 """Represent ``True`` if this :class:`_orm.Mapper` is a concrete
802 inheritance mapper.
804 This is a *read only* attribute determined during mapper construction.
805 Behavior is undefined if directly modified.
807 """
809 tables = None
810 """An iterable containing the collection of :class:`_schema.Table` objects
811 which this :class:`_orm.Mapper` is aware of.
813 If the mapper is mapped to a :class:`_expression.Join`, or an
814 :class:`_expression.Alias`
815 representing a :class:`_expression.Select`, the individual
816 :class:`_schema.Table`
817 objects that comprise the full construct will be represented here.
819 This is a *read only* attribute determined during mapper construction.
820 Behavior is undefined if directly modified.
822 """
824 primary_key = None
825 """An iterable containing the collection of :class:`_schema.Column`
826 objects
827 which comprise the 'primary key' of the mapped table, from the
828 perspective of this :class:`_orm.Mapper`.
830 This list is against the selectable in
831 :attr:`_orm.Mapper.persist_selectable`.
832 In the case of inheriting mappers, some columns may be managed by a
833 superclass mapper. For example, in the case of a
834 :class:`_expression.Join`, the
835 primary key is determined by all of the primary key columns across all
836 tables referenced by the :class:`_expression.Join`.
838 The list is also not necessarily the same as the primary key column
839 collection associated with the underlying tables; the :class:`_orm.Mapper`
840 features a ``primary_key`` argument that can override what the
841 :class:`_orm.Mapper` considers as primary key columns.
843 This is a *read only* attribute determined during mapper construction.
844 Behavior is undefined if directly modified.
846 """
848 class_ = None
849 """The Python class which this :class:`_orm.Mapper` maps.
851 This is a *read only* attribute determined during mapper construction.
852 Behavior is undefined if directly modified.
854 """
856 class_manager = None
857 """The :class:`.ClassManager` which maintains event listeners
858 and class-bound descriptors for this :class:`_orm.Mapper`.
860 This is a *read only* attribute determined during mapper construction.
861 Behavior is undefined if directly modified.
863 """
865 single = None
866 """Represent ``True`` if this :class:`_orm.Mapper` is a single table
867 inheritance mapper.
869 :attr:`_orm.Mapper.local_table` will be ``None`` if this flag is set.
871 This is a *read only* attribute determined during mapper construction.
872 Behavior is undefined if directly modified.
874 """
876 non_primary = None
877 """Represent ``True`` if this :class:`_orm.Mapper` is a "non-primary"
878 mapper, e.g. a mapper that is used only to select rows but not for
879 persistence management.
881 This is a *read only* attribute determined during mapper construction.
882 Behavior is undefined if directly modified.
884 """
886 polymorphic_on = None
887 """The :class:`_schema.Column` or SQL expression specified as the
888 ``polymorphic_on`` argument
889 for this :class:`_orm.Mapper`, within an inheritance scenario.
891 This attribute is normally a :class:`_schema.Column` instance but
892 may also be an expression, such as one derived from
893 :func:`.cast`.
895 This is a *read only* attribute determined during mapper construction.
896 Behavior is undefined if directly modified.
898 """
900 polymorphic_map = None
901 """A mapping of "polymorphic identity" identifiers mapped to
902 :class:`_orm.Mapper` instances, within an inheritance scenario.
904 The identifiers can be of any type which is comparable to the
905 type of column represented by :attr:`_orm.Mapper.polymorphic_on`.
907 An inheritance chain of mappers will all reference the same
908 polymorphic map object. The object is used to correlate incoming
909 result rows to target mappers.
911 This is a *read only* attribute determined during mapper construction.
912 Behavior is undefined if directly modified.
914 """
916 polymorphic_identity = None
917 """Represent an identifier which is matched against the
918 :attr:`_orm.Mapper.polymorphic_on` column during result row loading.
920 Used only with inheritance, this object can be of any type which is
921 comparable to the type of column represented by
922 :attr:`_orm.Mapper.polymorphic_on`.
924 This is a *read only* attribute determined during mapper construction.
925 Behavior is undefined if directly modified.
927 """
929 base_mapper = None
930 """The base-most :class:`_orm.Mapper` in an inheritance chain.
932 In a non-inheriting scenario, this attribute will always be this
933 :class:`_orm.Mapper`. In an inheritance scenario, it references
934 the :class:`_orm.Mapper` which is parent to all other :class:`_orm.Mapper`
935 objects in the inheritance chain.
937 This is a *read only* attribute determined during mapper construction.
938 Behavior is undefined if directly modified.
940 """
942 columns = None
943 """A collection of :class:`_schema.Column` or other scalar expression
944 objects maintained by this :class:`_orm.Mapper`.
946 The collection behaves the same as that of the ``c`` attribute on
947 any :class:`_schema.Table` object,
948 except that only those columns included in
949 this mapping are present, and are keyed based on the attribute name
950 defined in the mapping, not necessarily the ``key`` attribute of the
951 :class:`_schema.Column` itself. Additionally, scalar expressions mapped
952 by :func:`.column_property` are also present here.
954 This is a *read only* attribute determined during mapper construction.
955 Behavior is undefined if directly modified.
957 """
959 validators = None
960 """An immutable dictionary of attributes which have been decorated
961 using the :func:`_orm.validates` decorator.
963 The dictionary contains string attribute names as keys
964 mapped to the actual validation method.
966 """
968 c = None
969 """A synonym for :attr:`_orm.Mapper.columns`."""
971 @property
972 @util.deprecated("1.3", "Use .persist_selectable")
973 def mapped_table(self):
974 return self.persist_selectable
976 @util.memoized_property
977 def _path_registry(self):
978 return PathRegistry.per_mapper(self)
980 def _configure_inheritance(self):
981 """Configure settings related to inheriting and/or inherited mappers
982 being present."""
984 # a set of all mappers which inherit from this one.
985 self._inheriting_mappers = util.WeakSequence()
987 if self.inherits:
988 if isinstance(self.inherits, type):
989 self.inherits = class_mapper(self.inherits, configure=False)
990 if not issubclass(self.class_, self.inherits.class_):
991 raise sa_exc.ArgumentError(
992 "Class '%s' does not inherit from '%s'"
993 % (self.class_.__name__, self.inherits.class_.__name__)
994 )
996 self.dispatch._update(self.inherits.dispatch)
998 if self.non_primary != self.inherits.non_primary:
999 np = not self.non_primary and "primary" or "non-primary"
1000 raise sa_exc.ArgumentError(
1001 "Inheritance of %s mapper for class '%s' is "
1002 "only allowed from a %s mapper"
1003 % (np, self.class_.__name__, np)
1004 )
1005 # inherit_condition is optional.
1006 if self.local_table is None:
1007 self.local_table = self.inherits.local_table
1008 self.persist_selectable = self.inherits.persist_selectable
1009 self.single = True
1010 elif self.local_table is not self.inherits.local_table:
1011 if self.concrete:
1012 self.persist_selectable = self.local_table
1013 for mapper in self.iterate_to_root():
1014 if mapper.polymorphic_on is not None:
1015 mapper._requires_row_aliasing = True
1016 else:
1017 if self.inherit_condition is None:
1018 # figure out inherit condition from our table to the
1019 # immediate table of the inherited mapper, not its
1020 # full table which could pull in other stuff we don't
1021 # want (allows test/inheritance.InheritTest4 to pass)
1022 try:
1023 self.inherit_condition = sql_util.join_condition(
1024 self.inherits.local_table, self.local_table
1025 )
1026 except sa_exc.NoForeignKeysError as nfe:
1027 assert self.inherits.local_table is not None
1028 assert self.local_table is not None
1029 util.raise_(
1030 sa_exc.NoForeignKeysError(
1031 "Can't determine the inherit condition "
1032 "between inherited table '%s' and "
1033 "inheriting "
1034 "table '%s'; tables have no "
1035 "foreign key relationships established. "
1036 "Please ensure the inheriting table has "
1037 "a foreign key relationship to the "
1038 "inherited "
1039 "table, or provide an "
1040 "'on clause' using "
1041 "the 'inherit_condition' mapper argument."
1042 % (
1043 self.inherits.local_table.description,
1044 self.local_table.description,
1045 )
1046 ),
1047 replace_context=nfe,
1048 )
1049 except sa_exc.AmbiguousForeignKeysError as afe:
1050 assert self.inherits.local_table is not None
1051 assert self.local_table is not None
1052 util.raise_(
1053 sa_exc.AmbiguousForeignKeysError(
1054 "Can't determine the inherit condition "
1055 "between inherited table '%s' and "
1056 "inheriting "
1057 "table '%s'; tables have more than one "
1058 "foreign key relationship established. "
1059 "Please specify the 'on clause' using "
1060 "the 'inherit_condition' mapper argument."
1061 % (
1062 self.inherits.local_table.description,
1063 self.local_table.description,
1064 )
1065 ),
1066 replace_context=afe,
1067 )
1068 self.persist_selectable = sql.join(
1069 self.inherits.persist_selectable,
1070 self.local_table,
1071 self.inherit_condition,
1072 )
1074 fks = util.to_set(self.inherit_foreign_keys)
1075 self._inherits_equated_pairs = sql_util.criterion_as_pairs(
1076 self.persist_selectable.onclause,
1077 consider_as_foreign_keys=fks,
1078 )
1079 else:
1080 self.persist_selectable = self.local_table
1082 if self.polymorphic_identity is None:
1083 self._identity_class = self.class_
1085 if self.inherits.base_mapper.polymorphic_on is not None:
1086 util.warn(
1087 "Mapper %s does not indicate a polymorphic_identity, "
1088 "yet is part of an inheritance hierarchy that has a "
1089 "polymorphic_on column of '%s'. Objects of this type "
1090 "cannot be loaded polymorphically which can lead to "
1091 "degraded or incorrect loading behavior in some "
1092 "scenarios. Please establish a polmorphic_identity "
1093 "for this class, or leave it un-mapped. "
1094 "To omit mapping an intermediary class when using "
1095 "declarative, set the '__abstract__ = True' "
1096 "attribute on that class."
1097 % (self, self.inherits.base_mapper.polymorphic_on)
1098 )
1099 elif self.concrete:
1100 self._identity_class = self.class_
1101 else:
1102 self._identity_class = self.inherits._identity_class
1104 if self.version_id_col is None:
1105 self.version_id_col = self.inherits.version_id_col
1106 self.version_id_generator = self.inherits.version_id_generator
1107 elif (
1108 self.inherits.version_id_col is not None
1109 and self.version_id_col is not self.inherits.version_id_col
1110 ):
1111 util.warn(
1112 "Inheriting version_id_col '%s' does not match inherited "
1113 "version_id_col '%s' and will not automatically populate "
1114 "the inherited versioning column. "
1115 "version_id_col should only be specified on "
1116 "the base-most mapper that includes versioning."
1117 % (
1118 self.version_id_col.description,
1119 self.inherits.version_id_col.description,
1120 )
1121 )
1123 self.polymorphic_map = self.inherits.polymorphic_map
1124 self.batch = self.inherits.batch
1125 self.inherits._inheriting_mappers.append(self)
1126 self.base_mapper = self.inherits.base_mapper
1127 self.passive_updates = self.inherits.passive_updates
1128 self.passive_deletes = (
1129 self.inherits.passive_deletes or self.passive_deletes
1130 )
1131 self._all_tables = self.inherits._all_tables
1133 if self.polymorphic_identity is not None:
1134 if self.polymorphic_identity in self.polymorphic_map:
1135 util.warn(
1136 "Reassigning polymorphic association for identity %r "
1137 "from %r to %r: Check for duplicate use of %r as "
1138 "value for polymorphic_identity."
1139 % (
1140 self.polymorphic_identity,
1141 self.polymorphic_map[self.polymorphic_identity],
1142 self,
1143 self.polymorphic_identity,
1144 )
1145 )
1146 self.polymorphic_map[self.polymorphic_identity] = self
1148 if self.polymorphic_load and self.concrete:
1149 raise sa_exc.ArgumentError(
1150 "polymorphic_load is not currently supported "
1151 "with concrete table inheritance"
1152 )
1153 if self.polymorphic_load == "inline":
1154 self.inherits._add_with_polymorphic_subclass(self)
1155 elif self.polymorphic_load == "selectin":
1156 pass
1157 elif self.polymorphic_load is not None:
1158 raise sa_exc.ArgumentError(
1159 "unknown argument for polymorphic_load: %r"
1160 % self.polymorphic_load
1161 )
1163 else:
1164 self._all_tables = set()
1165 self.base_mapper = self
1166 self.persist_selectable = self.local_table
1167 if self.polymorphic_identity is not None:
1168 self.polymorphic_map[self.polymorphic_identity] = self
1169 self._identity_class = self.class_
1171 if self.persist_selectable is None:
1172 raise sa_exc.ArgumentError(
1173 "Mapper '%s' does not have a persist_selectable specified."
1174 % self
1175 )
1177 def _set_with_polymorphic(self, with_polymorphic):
1178 if with_polymorphic == "*":
1179 self.with_polymorphic = ("*", None)
1180 elif isinstance(with_polymorphic, (tuple, list)):
1181 if isinstance(
1182 with_polymorphic[0], util.string_types + (tuple, list)
1183 ):
1184 self.with_polymorphic = with_polymorphic
1185 else:
1186 self.with_polymorphic = (with_polymorphic, None)
1187 elif with_polymorphic is not None:
1188 raise sa_exc.ArgumentError("Invalid setting for with_polymorphic")
1189 else:
1190 self.with_polymorphic = None
1192 if self.with_polymorphic and self.with_polymorphic[1] is not None:
1193 self.with_polymorphic = (
1194 self.with_polymorphic[0],
1195 coercions.expect(
1196 roles.StrictFromClauseRole,
1197 self.with_polymorphic[1],
1198 allow_select=True,
1199 ),
1200 )
1202 if self.configured:
1203 self._expire_memoizations()
1205 def _add_with_polymorphic_subclass(self, mapper):
1206 subcl = mapper.class_
1207 if self.with_polymorphic is None:
1208 self._set_with_polymorphic((subcl,))
1209 elif self.with_polymorphic[0] != "*":
1210 self._set_with_polymorphic(
1211 (self.with_polymorphic[0] + (subcl,), self.with_polymorphic[1])
1212 )
1214 def _set_concrete_base(self, mapper):
1215 """Set the given :class:`_orm.Mapper` as the 'inherits' for this
1216 :class:`_orm.Mapper`, assuming this :class:`_orm.Mapper` is concrete
1217 and does not already have an inherits."""
1219 assert self.concrete
1220 assert not self.inherits
1221 assert isinstance(mapper, Mapper)
1222 self.inherits = mapper
1223 self.inherits.polymorphic_map.update(self.polymorphic_map)
1224 self.polymorphic_map = self.inherits.polymorphic_map
1225 for mapper in self.iterate_to_root():
1226 if mapper.polymorphic_on is not None:
1227 mapper._requires_row_aliasing = True
1228 self.batch = self.inherits.batch
1229 for mp in self.self_and_descendants:
1230 mp.base_mapper = self.inherits.base_mapper
1231 self.inherits._inheriting_mappers.append(self)
1232 self.passive_updates = self.inherits.passive_updates
1233 self._all_tables = self.inherits._all_tables
1235 for key, prop in mapper._props.items():
1236 if key not in self._props and not self._should_exclude(
1237 key, key, local=False, column=None
1238 ):
1239 self._adapt_inherited_property(key, prop, False)
1241 def _set_polymorphic_on(self, polymorphic_on):
1242 self.polymorphic_on = polymorphic_on
1243 self._configure_polymorphic_setter(True)
1245 def _configure_class_instrumentation(self):
1246 """If this mapper is to be a primary mapper (i.e. the
1247 non_primary flag is not set), associate this Mapper with the
1248 given class and entity name.
1250 Subsequent calls to ``class_mapper()`` for the ``class_`` / ``entity``
1251 name combination will return this mapper. Also decorate the
1252 `__init__` method on the mapped class to include optional
1253 auto-session attachment logic.
1255 """
1257 # we expect that declarative has applied the class manager
1258 # already and set up a registry. if this is None,
1259 # we will emit a deprecation warning below when we also see that
1260 # it has no registry.
1261 manager = attributes.manager_of_class(self.class_)
1263 if self.non_primary:
1264 if not manager or not manager.is_mapped:
1265 raise sa_exc.InvalidRequestError(
1266 "Class %s has no primary mapper configured. Configure "
1267 "a primary mapper first before setting up a non primary "
1268 "Mapper." % self.class_
1269 )
1270 self.class_manager = manager
1271 self.registry = manager.registry
1272 self._identity_class = manager.mapper._identity_class
1273 manager.registry._add_non_primary_mapper(self)
1274 return
1276 if manager is not None:
1277 assert manager.class_ is self.class_
1278 if manager.is_mapped:
1279 # changed in #7579:
1280 # this message is defined in two places as of this change,
1281 # also in decl_api -> _add_manager(). in 2.0, this codepath
1282 # is removed as any calls to mapper() / Mapper without
1283 # the registry setting up first will be rejected.
1284 raise sa_exc.ArgumentError(
1285 "Class '%s' already has a primary mapper defined. "
1286 % self.class_
1287 )
1288 # else:
1289 # a ClassManager may already exist as
1290 # ClassManager.instrument_attribute() creates
1291 # new managers for each subclass if they don't yet exist.
1293 self.dispatch.instrument_class(self, self.class_)
1295 # this invokes the class_instrument event and sets up
1296 # the __init__ method. documented behavior is that this must
1297 # occur after the instrument_class event above.
1298 # yes two events with the same two words reversed and different APIs.
1299 # :(
1301 manager = instrumentation.register_class(
1302 self.class_,
1303 mapper=self,
1304 expired_attribute_loader=util.partial(
1305 loading.load_scalar_attributes, self
1306 ),
1307 # finalize flag means instrument the __init__ method
1308 # and call the class_instrument event
1309 finalize=True,
1310 )
1312 if not manager.registry:
1313 util.warn_deprecated_20(
1314 "Calling the mapper() function directly outside of a "
1315 "declarative registry is deprecated."
1316 " Please use the sqlalchemy.orm.registry.map_imperatively() "
1317 "function for a classical mapping."
1318 )
1319 assert _legacy_registry is not None
1320 _legacy_registry._add_manager(manager)
1322 self.class_manager = manager
1323 self.registry = manager.registry
1325 # The remaining members can be added by any mapper,
1326 # e_name None or not.
1327 if manager.mapper is None:
1328 return
1330 event.listen(manager, "init", _event_on_init, raw=True)
1332 for key, method in util.iterate_attributes(self.class_):
1333 if key == "__init__" and hasattr(method, "_sa_original_init"):
1334 method = method._sa_original_init
1335 if hasattr(method, "__func__"):
1336 method = method.__func__
1337 if callable(method):
1338 if hasattr(method, "__sa_reconstructor__"):
1339 self._reconstructor = method
1340 event.listen(manager, "load", _event_on_load, raw=True)
1341 elif hasattr(method, "__sa_validators__"):
1342 validation_opts = method.__sa_validation_opts__
1343 for name in method.__sa_validators__:
1344 if name in self.validators:
1345 raise sa_exc.InvalidRequestError(
1346 "A validation function for mapped "
1347 "attribute %r on mapper %s already exists."
1348 % (name, self)
1349 )
1350 self.validators = self.validators.union(
1351 {name: (method, validation_opts)}
1352 )
1354 def _set_dispose_flags(self):
1355 self.configured = True
1356 self._ready_for_configure = True
1357 self._dispose_called = True
1359 self.__dict__.pop("_configure_failed", None)
1361 def _configure_pks(self):
1362 self.tables = sql_util.find_tables(self.persist_selectable)
1364 self._pks_by_table = {}
1365 self._cols_by_table = {}
1367 all_cols = util.column_set(
1368 chain(*[col.proxy_set for col in self._columntoproperty])
1369 )
1371 pk_cols = util.column_set(c for c in all_cols if c.primary_key)
1373 # identify primary key columns which are also mapped by this mapper.
1374 tables = set(self.tables + [self.persist_selectable])
1375 self._all_tables.update(tables)
1376 for t in tables:
1377 if t.primary_key and pk_cols.issuperset(t.primary_key):
1378 # ordering is important since it determines the ordering of
1379 # mapper.primary_key (and therefore query.get())
1380 self._pks_by_table[t] = util.ordered_column_set(
1381 t.primary_key
1382 ).intersection(pk_cols)
1383 self._cols_by_table[t] = util.ordered_column_set(t.c).intersection(
1384 all_cols
1385 )
1387 # if explicit PK argument sent, add those columns to the
1388 # primary key mappings
1389 if self._primary_key_argument:
1390 for k in self._primary_key_argument:
1391 if k.table not in self._pks_by_table:
1392 self._pks_by_table[k.table] = util.OrderedSet()
1393 self._pks_by_table[k.table].add(k)
1395 # otherwise, see that we got a full PK for the mapped table
1396 elif (
1397 self.persist_selectable not in self._pks_by_table
1398 or len(self._pks_by_table[self.persist_selectable]) == 0
1399 ):
1400 raise sa_exc.ArgumentError(
1401 "Mapper %s could not assemble any primary "
1402 "key columns for mapped table '%s'"
1403 % (self, self.persist_selectable.description)
1404 )
1405 elif self.local_table not in self._pks_by_table and isinstance(
1406 self.local_table, schema.Table
1407 ):
1408 util.warn(
1409 "Could not assemble any primary "
1410 "keys for locally mapped table '%s' - "
1411 "no rows will be persisted in this Table."
1412 % self.local_table.description
1413 )
1415 if (
1416 self.inherits
1417 and not self.concrete
1418 and not self._primary_key_argument
1419 ):
1420 # if inheriting, the "primary key" for this mapper is
1421 # that of the inheriting (unless concrete or explicit)
1422 self.primary_key = self.inherits.primary_key
1423 else:
1424 # determine primary key from argument or persist_selectable pks
1425 if self._primary_key_argument:
1426 primary_key = [
1427 self.persist_selectable.corresponding_column(c)
1428 for c in self._primary_key_argument
1429 ]
1430 else:
1431 # if heuristically determined PKs, reduce to the minimal set
1432 # of columns by eliminating FK->PK pairs for a multi-table
1433 # expression. May over-reduce for some kinds of UNIONs
1434 # / CTEs; use explicit PK argument for these special cases
1435 primary_key = sql_util.reduce_columns(
1436 self._pks_by_table[self.persist_selectable],
1437 ignore_nonexistent_tables=True,
1438 )
1440 if len(primary_key) == 0:
1441 raise sa_exc.ArgumentError(
1442 "Mapper %s could not assemble any primary "
1443 "key columns for mapped table '%s'"
1444 % (self, self.persist_selectable.description)
1445 )
1447 self.primary_key = tuple(primary_key)
1448 self._log("Identified primary key columns: %s", primary_key)
1450 # determine cols that aren't expressed within our tables; mark these
1451 # as "read only" properties which are refreshed upon INSERT/UPDATE
1452 self._readonly_props = set(
1453 self._columntoproperty[col]
1454 for col in self._columntoproperty
1455 if self._columntoproperty[col] not in self._identity_key_props
1456 and (
1457 not hasattr(col, "table")
1458 or col.table not in self._cols_by_table
1459 )
1460 )
1462 def _configure_properties(self):
1464 # TODO: consider using DedupeColumnCollection
1465 self.columns = self.c = sql_base.ColumnCollection()
1467 # object attribute names mapped to MapperProperty objects
1468 self._props = util.OrderedDict()
1470 # table columns mapped to MapperProperty
1471 self._columntoproperty = _ColumnMapping(self)
1473 # load custom properties
1474 if self._init_properties:
1475 for key, prop in self._init_properties.items():
1476 self._configure_property(key, prop, False)
1478 # pull properties from the inherited mapper if any.
1479 if self.inherits:
1480 for key, prop in self.inherits._props.items():
1481 if key not in self._props and not self._should_exclude(
1482 key, key, local=False, column=None
1483 ):
1484 self._adapt_inherited_property(key, prop, False)
1486 # create properties for each column in the mapped table,
1487 # for those columns which don't already map to a property
1488 for column in self.persist_selectable.columns:
1489 if column in self._columntoproperty:
1490 continue
1492 column_key = (self.column_prefix or "") + column.key
1494 if self._should_exclude(
1495 column.key,
1496 column_key,
1497 local=self.local_table.c.contains_column(column),
1498 column=column,
1499 ):
1500 continue
1502 # adjust the "key" used for this column to that
1503 # of the inheriting mapper
1504 for mapper in self.iterate_to_root():
1505 if column in mapper._columntoproperty:
1506 column_key = mapper._columntoproperty[column].key
1508 self._configure_property(
1509 column_key, column, init=False, setparent=True
1510 )
1512 def _configure_polymorphic_setter(self, init=False):
1513 """Configure an attribute on the mapper representing the
1514 'polymorphic_on' column, if applicable, and not
1515 already generated by _configure_properties (which is typical).
1517 Also create a setter function which will assign this
1518 attribute to the value of the 'polymorphic_identity'
1519 upon instance construction, also if applicable. This
1520 routine will run when an instance is created.
1522 """
1523 setter = False
1525 if self.polymorphic_on is not None:
1526 setter = True
1528 if isinstance(self.polymorphic_on, util.string_types):
1529 # polymorphic_on specified as a string - link
1530 # it to mapped ColumnProperty
1531 try:
1532 self.polymorphic_on = self._props[self.polymorphic_on]
1533 except KeyError as err:
1534 util.raise_(
1535 sa_exc.ArgumentError(
1536 "Can't determine polymorphic_on "
1537 "value '%s' - no attribute is "
1538 "mapped to this name." % self.polymorphic_on
1539 ),
1540 replace_context=err,
1541 )
1543 if self.polymorphic_on in self._columntoproperty:
1544 # polymorphic_on is a column that is already mapped
1545 # to a ColumnProperty
1546 prop = self._columntoproperty[self.polymorphic_on]
1547 elif isinstance(self.polymorphic_on, MapperProperty):
1548 # polymorphic_on is directly a MapperProperty,
1549 # ensure it's a ColumnProperty
1550 if not isinstance(
1551 self.polymorphic_on, properties.ColumnProperty
1552 ):
1553 raise sa_exc.ArgumentError(
1554 "Only direct column-mapped "
1555 "property or SQL expression "
1556 "can be passed for polymorphic_on"
1557 )
1558 prop = self.polymorphic_on
1559 else:
1560 # polymorphic_on is a Column or SQL expression and
1561 # doesn't appear to be mapped. this means it can be 1.
1562 # only present in the with_polymorphic selectable or
1563 # 2. a totally standalone SQL expression which we'd
1564 # hope is compatible with this mapper's persist_selectable
1565 col = self.persist_selectable.corresponding_column(
1566 self.polymorphic_on
1567 )
1568 if col is None:
1569 # polymorphic_on doesn't derive from any
1570 # column/expression isn't present in the mapped
1571 # table. we will make a "hidden" ColumnProperty
1572 # for it. Just check that if it's directly a
1573 # schema.Column and we have with_polymorphic, it's
1574 # likely a user error if the schema.Column isn't
1575 # represented somehow in either persist_selectable or
1576 # with_polymorphic. Otherwise as of 0.7.4 we
1577 # just go with it and assume the user wants it
1578 # that way (i.e. a CASE statement)
1579 setter = False
1580 instrument = False
1581 col = self.polymorphic_on
1582 if isinstance(col, schema.Column) and (
1583 self.with_polymorphic is None
1584 or self.with_polymorphic[1].corresponding_column(col)
1585 is None
1586 ):
1587 raise sa_exc.InvalidRequestError(
1588 "Could not map polymorphic_on column "
1589 "'%s' to the mapped table - polymorphic "
1590 "loads will not function properly"
1591 % col.description
1592 )
1593 else:
1594 # column/expression that polymorphic_on derives from
1595 # is present in our mapped table
1596 # and is probably mapped, but polymorphic_on itself
1597 # is not. This happens when
1598 # the polymorphic_on is only directly present in the
1599 # with_polymorphic selectable, as when use
1600 # polymorphic_union.
1601 # we'll make a separate ColumnProperty for it.
1602 instrument = True
1603 key = getattr(col, "key", None)
1604 if key:
1605 if self._should_exclude(col.key, col.key, False, col):
1606 raise sa_exc.InvalidRequestError(
1607 "Cannot exclude or override the "
1608 "discriminator column %r" % col.key
1609 )
1610 else:
1611 self.polymorphic_on = col = col.label("_sa_polymorphic_on")
1612 key = col.key
1614 prop = properties.ColumnProperty(col, _instrument=instrument)
1615 self._configure_property(key, prop, init=init, setparent=True)
1617 # the actual polymorphic_on should be the first public-facing
1618 # column in the property
1619 self.polymorphic_on = prop.columns[0]
1620 polymorphic_key = prop.key
1622 else:
1623 # no polymorphic_on was set.
1624 # check inheriting mappers for one.
1625 for mapper in self.iterate_to_root():
1626 # determine if polymorphic_on of the parent
1627 # should be propagated here. If the col
1628 # is present in our mapped table, or if our mapped
1629 # table is the same as the parent (i.e. single table
1630 # inheritance), we can use it
1631 if mapper.polymorphic_on is not None:
1632 if self.persist_selectable is mapper.persist_selectable:
1633 self.polymorphic_on = mapper.polymorphic_on
1634 else:
1635 self.polymorphic_on = (
1636 self.persist_selectable
1637 ).corresponding_column(mapper.polymorphic_on)
1638 # we can use the parent mapper's _set_polymorphic_identity
1639 # directly; it ensures the polymorphic_identity of the
1640 # instance's mapper is used so is portable to subclasses.
1641 if self.polymorphic_on is not None:
1642 self._set_polymorphic_identity = (
1643 mapper._set_polymorphic_identity
1644 )
1645 self._validate_polymorphic_identity = (
1646 mapper._validate_polymorphic_identity
1647 )
1648 else:
1649 self._set_polymorphic_identity = None
1650 return
1652 if setter:
1654 def _set_polymorphic_identity(state):
1655 dict_ = state.dict
1656 state.get_impl(polymorphic_key).set(
1657 state,
1658 dict_,
1659 state.manager.mapper.polymorphic_identity,
1660 None,
1661 )
1663 def _validate_polymorphic_identity(mapper, state, dict_):
1664 if (
1665 polymorphic_key in dict_
1666 and dict_[polymorphic_key]
1667 not in mapper._acceptable_polymorphic_identities
1668 ):
1669 util.warn_limited(
1670 "Flushing object %s with "
1671 "incompatible polymorphic identity %r; the "
1672 "object may not refresh and/or load correctly",
1673 (state_str(state), dict_[polymorphic_key]),
1674 )
1676 self._set_polymorphic_identity = _set_polymorphic_identity
1677 self._validate_polymorphic_identity = (
1678 _validate_polymorphic_identity
1679 )
1680 else:
1681 self._set_polymorphic_identity = None
1683 _validate_polymorphic_identity = None
1685 @HasMemoized.memoized_attribute
1686 def _version_id_prop(self):
1687 if self.version_id_col is not None:
1688 return self._columntoproperty[self.version_id_col]
1689 else:
1690 return None
1692 @HasMemoized.memoized_attribute
1693 def _acceptable_polymorphic_identities(self):
1694 identities = set()
1696 stack = deque([self])
1697 while stack:
1698 item = stack.popleft()
1699 if item.persist_selectable is self.persist_selectable:
1700 identities.add(item.polymorphic_identity)
1701 stack.extend(item._inheriting_mappers)
1703 return identities
1705 @HasMemoized.memoized_attribute
1706 def _prop_set(self):
1707 return frozenset(self._props.values())
1709 @util.preload_module("sqlalchemy.orm.descriptor_props")
1710 def _adapt_inherited_property(self, key, prop, init):
1711 descriptor_props = util.preloaded.orm_descriptor_props
1713 if not self.concrete:
1714 self._configure_property(key, prop, init=False, setparent=False)
1715 elif key not in self._props:
1716 # determine if the class implements this attribute; if not,
1717 # or if it is implemented by the attribute that is handling the
1718 # given superclass-mapped property, then we need to report that we
1719 # can't use this at the instance level since we are a concrete
1720 # mapper and we don't map this. don't trip user-defined
1721 # descriptors that might have side effects when invoked.
1722 implementing_attribute = self.class_manager._get_class_attr_mro(
1723 key, prop
1724 )
1725 if implementing_attribute is prop or (
1726 isinstance(
1727 implementing_attribute, attributes.InstrumentedAttribute
1728 )
1729 and implementing_attribute._parententity is prop.parent
1730 ):
1731 self._configure_property(
1732 key,
1733 descriptor_props.ConcreteInheritedProperty(),
1734 init=init,
1735 setparent=True,
1736 )
1738 @util.preload_module("sqlalchemy.orm.descriptor_props")
1739 def _configure_property(self, key, prop, init=True, setparent=True):
1740 descriptor_props = util.preloaded.orm_descriptor_props
1741 self._log("_configure_property(%s, %s)", key, prop.__class__.__name__)
1743 if not isinstance(prop, MapperProperty):
1744 prop = self._property_from_column(key, prop)
1746 if isinstance(prop, properties.ColumnProperty):
1747 col = self.persist_selectable.corresponding_column(prop.columns[0])
1749 # if the column is not present in the mapped table,
1750 # test if a column has been added after the fact to the
1751 # parent table (or their parent, etc.) [ticket:1570]
1752 if col is None and self.inherits:
1753 path = [self]
1754 for m in self.inherits.iterate_to_root():
1755 col = m.local_table.corresponding_column(prop.columns[0])
1756 if col is not None:
1757 for m2 in path:
1758 m2.persist_selectable._refresh_for_new_column(col)
1759 col = self.persist_selectable.corresponding_column(
1760 prop.columns[0]
1761 )
1762 break
1763 path.append(m)
1765 # subquery expression, column not present in the mapped
1766 # selectable.
1767 if col is None:
1768 col = prop.columns[0]
1770 # column is coming in after _readonly_props was
1771 # initialized; check for 'readonly'
1772 if hasattr(self, "_readonly_props") and (
1773 not hasattr(col, "table")
1774 or col.table not in self._cols_by_table
1775 ):
1776 self._readonly_props.add(prop)
1778 else:
1779 # if column is coming in after _cols_by_table was
1780 # initialized, ensure the col is in the right set
1781 if (
1782 hasattr(self, "_cols_by_table")
1783 and col.table in self._cols_by_table
1784 and col not in self._cols_by_table[col.table]
1785 ):
1786 self._cols_by_table[col.table].add(col)
1788 # if this properties.ColumnProperty represents the "polymorphic
1789 # discriminator" column, mark it. We'll need this when rendering
1790 # columns in SELECT statements.
1791 if not hasattr(prop, "_is_polymorphic_discriminator"):
1792 prop._is_polymorphic_discriminator = (
1793 col is self.polymorphic_on
1794 or prop.columns[0] is self.polymorphic_on
1795 )
1797 if isinstance(col, expression.Label):
1798 # new in 1.4, get column property against expressions
1799 # to be addressable in subqueries
1800 col.key = col._tq_key_label = key
1802 self.columns.add(col, key)
1803 for col in prop.columns:
1804 for col in col.proxy_set:
1805 self._columntoproperty[col] = prop
1807 prop.key = key
1809 if setparent:
1810 prop.set_parent(self, init)
1812 if key in self._props and getattr(
1813 self._props[key], "_mapped_by_synonym", False
1814 ):
1815 syn = self._props[key]._mapped_by_synonym
1816 raise sa_exc.ArgumentError(
1817 "Can't call map_column=True for synonym %r=%r, "
1818 "a ColumnProperty already exists keyed to the name "
1819 "%r for column %r" % (syn, key, key, syn)
1820 )
1822 if (
1823 key in self._props
1824 and not isinstance(prop, properties.ColumnProperty)
1825 and not isinstance(
1826 self._props[key],
1827 (
1828 properties.ColumnProperty,
1829 descriptor_props.ConcreteInheritedProperty,
1830 ),
1831 )
1832 ):
1833 util.warn(
1834 "Property %s on %s being replaced with new "
1835 "property %s; the old property will be discarded"
1836 % (self._props[key], self, prop)
1837 )
1838 oldprop = self._props[key]
1839 self._path_registry.pop(oldprop, None)
1841 self._props[key] = prop
1843 if not self.non_primary:
1844 prop.instrument_class(self)
1846 for mapper in self._inheriting_mappers:
1847 mapper._adapt_inherited_property(key, prop, init)
1849 if init:
1850 prop.init()
1851 prop.post_instrument_class(self)
1853 if self.configured:
1854 self._expire_memoizations()
1856 @util.preload_module("sqlalchemy.orm.descriptor_props")
1857 def _property_from_column(self, key, prop):
1858 """generate/update a :class:`.ColumnProperty` given a
1859 :class:`_schema.Column` object."""
1860 descriptor_props = util.preloaded.orm_descriptor_props
1861 # we were passed a Column or a list of Columns;
1862 # generate a properties.ColumnProperty
1863 columns = util.to_list(prop)
1864 column = columns[0]
1865 assert isinstance(column, expression.ColumnElement)
1867 prop = self._props.get(key, None)
1869 if isinstance(prop, properties.ColumnProperty):
1870 if (
1871 (
1872 not self._inherits_equated_pairs
1873 or (prop.columns[0], column)
1874 not in self._inherits_equated_pairs
1875 )
1876 and not prop.columns[0].shares_lineage(column)
1877 and prop.columns[0] is not self.version_id_col
1878 and column is not self.version_id_col
1879 ):
1880 warn_only = prop.parent is not self
1881 msg = (
1882 "Implicitly combining column %s with column "
1883 "%s under attribute '%s'. Please configure one "
1884 "or more attributes for these same-named columns "
1885 "explicitly." % (prop.columns[-1], column, key)
1886 )
1887 if warn_only:
1888 util.warn(msg)
1889 else:
1890 raise sa_exc.InvalidRequestError(msg)
1892 # existing properties.ColumnProperty from an inheriting
1893 # mapper. make a copy and append our column to it
1894 prop = prop.copy()
1895 prop.columns.insert(0, column)
1896 self._log(
1897 "inserting column to existing list "
1898 "in properties.ColumnProperty %s" % (key)
1899 )
1900 return prop
1901 elif prop is None or isinstance(
1902 prop, descriptor_props.ConcreteInheritedProperty
1903 ):
1904 mapped_column = []
1905 for c in columns:
1906 mc = self.persist_selectable.corresponding_column(c)
1907 if mc is None:
1908 mc = self.local_table.corresponding_column(c)
1909 if mc is not None:
1910 # if the column is in the local table but not the
1911 # mapped table, this corresponds to adding a
1912 # column after the fact to the local table.
1913 # [ticket:1523]
1914 self.persist_selectable._refresh_for_new_column(mc)
1915 mc = self.persist_selectable.corresponding_column(c)
1916 if mc is None:
1917 raise sa_exc.ArgumentError(
1918 "When configuring property '%s' on %s, "
1919 "column '%s' is not represented in the mapper's "
1920 "table. Use the `column_property()` function to "
1921 "force this column to be mapped as a read-only "
1922 "attribute." % (key, self, c)
1923 )
1924 mapped_column.append(mc)
1925 return properties.ColumnProperty(*mapped_column)
1926 else:
1927 raise sa_exc.ArgumentError(
1928 "WARNING: when configuring property '%s' on %s, "
1929 "column '%s' conflicts with property '%r'. "
1930 "To resolve this, map the column to the class under a "
1931 "different name in the 'properties' dictionary. Or, "
1932 "to remove all awareness of the column entirely "
1933 "(including its availability as a foreign key), "
1934 "use the 'include_properties' or 'exclude_properties' "
1935 "mapper arguments to control specifically which table "
1936 "columns get mapped." % (key, self, column.key, prop)
1937 )
1939 def _check_configure(self):
1940 if self.registry._new_mappers:
1941 _configure_registries({self.registry}, cascade=True)
1943 def _post_configure_properties(self):
1944 """Call the ``init()`` method on all ``MapperProperties``
1945 attached to this mapper.
1947 This is a deferred configuration step which is intended
1948 to execute once all mappers have been constructed.
1950 """
1952 self._log("_post_configure_properties() started")
1953 l = [(key, prop) for key, prop in self._props.items()]
1954 for key, prop in l:
1955 self._log("initialize prop %s", key)
1957 if prop.parent is self and not prop._configure_started:
1958 prop.init()
1960 if prop._configure_finished:
1961 prop.post_instrument_class(self)
1963 self._log("_post_configure_properties() complete")
1964 self.configured = True
1966 def add_properties(self, dict_of_properties):
1967 """Add the given dictionary of properties to this mapper,
1968 using `add_property`.
1970 """
1971 for key, value in dict_of_properties.items():
1972 self.add_property(key, value)
1974 def add_property(self, key, prop):
1975 """Add an individual MapperProperty to this mapper.
1977 If the mapper has not been configured yet, just adds the
1978 property to the initial properties dictionary sent to the
1979 constructor. If this Mapper has already been configured, then
1980 the given MapperProperty is configured immediately.
1982 """
1983 self._init_properties[key] = prop
1984 self._configure_property(key, prop, init=self.configured)
1986 def _expire_memoizations(self):
1987 for mapper in self.iterate_to_root():
1988 mapper._reset_memoizations()
1990 @property
1991 def _log_desc(self):
1992 return (
1993 "("
1994 + self.class_.__name__
1995 + "|"
1996 + (
1997 self.local_table is not None
1998 and self.local_table.description
1999 or str(self.local_table)
2000 )
2001 + (self.non_primary and "|non-primary" or "")
2002 + ")"
2003 )
2005 def _log(self, msg, *args):
2006 self.logger.info("%s " + msg, *((self._log_desc,) + args))
2008 def _log_debug(self, msg, *args):
2009 self.logger.debug("%s " + msg, *((self._log_desc,) + args))
2011 def __repr__(self):
2012 return "<Mapper at 0x%x; %s>" % (id(self), self.class_.__name__)
2014 def __str__(self):
2015 return "mapped class %s%s->%s" % (
2016 self.class_.__name__,
2017 self.non_primary and " (non-primary)" or "",
2018 self.local_table.description
2019 if self.local_table is not None
2020 else self.persist_selectable.description,
2021 )
2023 def _is_orphan(self, state):
2024 orphan_possible = False
2025 for mapper in self.iterate_to_root():
2026 for (key, cls) in mapper._delete_orphans:
2027 orphan_possible = True
2029 has_parent = attributes.manager_of_class(cls).has_parent(
2030 state, key, optimistic=state.has_identity
2031 )
2033 if self.legacy_is_orphan and has_parent:
2034 return False
2035 elif not self.legacy_is_orphan and not has_parent:
2036 return True
2038 if self.legacy_is_orphan:
2039 return orphan_possible
2040 else:
2041 return False
2043 def has_property(self, key):
2044 return key in self._props
2046 def get_property(self, key, _configure_mappers=True):
2047 """return a MapperProperty associated with the given key."""
2049 if _configure_mappers:
2050 self._check_configure()
2052 try:
2053 return self._props[key]
2054 except KeyError as err:
2055 util.raise_(
2056 sa_exc.InvalidRequestError(
2057 "Mapper '%s' has no property '%s'" % (self, key)
2058 ),
2059 replace_context=err,
2060 )
2062 def get_property_by_column(self, column):
2063 """Given a :class:`_schema.Column` object, return the
2064 :class:`.MapperProperty` which maps this column."""
2066 return self._columntoproperty[column]
2068 @property
2069 def iterate_properties(self):
2070 """return an iterator of all MapperProperty objects."""
2072 self._check_configure()
2073 return iter(self._props.values())
2075 def _mappers_from_spec(self, spec, selectable):
2076 """given a with_polymorphic() argument, return the set of mappers it
2077 represents.
2079 Trims the list of mappers to just those represented within the given
2080 selectable, if present. This helps some more legacy-ish mappings.
2082 """
2083 if spec == "*":
2084 mappers = list(self.self_and_descendants)
2085 elif spec:
2086 mappers = set()
2087 for m in util.to_list(spec):
2088 m = _class_to_mapper(m)
2089 if not m.isa(self):
2090 raise sa_exc.InvalidRequestError(
2091 "%r does not inherit from %r" % (m, self)
2092 )
2094 if selectable is None:
2095 mappers.update(m.iterate_to_root())
2096 else:
2097 mappers.add(m)
2098 mappers = [m for m in self.self_and_descendants if m in mappers]
2099 else:
2100 mappers = []
2102 if selectable is not None:
2103 tables = set(
2104 sql_util.find_tables(selectable, include_aliases=True)
2105 )
2106 mappers = [m for m in mappers if m.local_table in tables]
2107 return mappers
2109 def _selectable_from_mappers(self, mappers, innerjoin):
2110 """given a list of mappers (assumed to be within this mapper's
2111 inheritance hierarchy), construct an outerjoin amongst those mapper's
2112 mapped tables.
2114 """
2115 from_obj = self.persist_selectable
2116 for m in mappers:
2117 if m is self:
2118 continue
2119 if m.concrete:
2120 raise sa_exc.InvalidRequestError(
2121 "'with_polymorphic()' requires 'selectable' argument "
2122 "when concrete-inheriting mappers are used."
2123 )
2124 elif not m.single:
2125 if innerjoin:
2126 from_obj = from_obj.join(
2127 m.local_table, m.inherit_condition
2128 )
2129 else:
2130 from_obj = from_obj.outerjoin(
2131 m.local_table, m.inherit_condition
2132 )
2134 return from_obj
2136 @HasMemoized.memoized_attribute
2137 def _single_table_criterion(self):
2138 if self.single and self.inherits and self.polymorphic_on is not None:
2139 return self.polymorphic_on._annotate(
2140 {"parententity": self, "parentmapper": self}
2141 ).in_(m.polymorphic_identity for m in self.self_and_descendants)
2142 else:
2143 return None
2145 @HasMemoized.memoized_attribute
2146 def _should_select_with_poly_adapter(self):
2147 """determine if _MapperEntity or _ORMColumnEntity will need to use
2148 polymorphic adaption when setting up a SELECT as well as fetching
2149 rows for mapped classes and subclasses against this Mapper.
2151 moved here from context.py for #8456 to generalize the ruleset
2152 for this condition.
2154 """
2156 # this has been simplified as of #8456.
2157 # rule is: if we have a with_polymorphic or a concrete-style
2158 # polymorphic selectable, *or* if the base mapper has either of those,
2159 # we turn on the adaption thing. if not, we do *no* adaption.
2160 #
2161 # this splits the behavior among the "regular" joined inheritance
2162 # and single inheritance mappers, vs. the "weird / difficult"
2163 # concrete and joined inh mappings that use a with_polymorphic of
2164 # some kind or polymorphic_union.
2165 #
2166 # note we have some tests in test_polymorphic_rel that query against
2167 # a subclass, then refer to the superclass that has a with_polymorphic
2168 # on it (such as test_join_from_polymorphic_explicit_aliased_three).
2169 # these tests actually adapt the polymorphic selectable (like, the
2170 # UNION or the SELECT subquery with JOIN in it) to be just the simple
2171 # subclass table. Hence even if we are a "plain" inheriting mapper
2172 # but our base has a wpoly on it, we turn on adaption.
2173 return (
2174 self.with_polymorphic
2175 or self._requires_row_aliasing
2176 or self.base_mapper.with_polymorphic
2177 or self.base_mapper._requires_row_aliasing
2178 )
2180 @HasMemoized.memoized_attribute
2181 def _with_polymorphic_mappers(self):
2182 self._check_configure()
2184 if not self.with_polymorphic:
2185 return []
2186 return self._mappers_from_spec(*self.with_polymorphic)
2188 @HasMemoized.memoized_attribute
2189 def _post_inspect(self):
2190 """This hook is invoked by attribute inspection.
2192 E.g. when Query calls:
2194 coercions.expect(roles.ColumnsClauseRole, ent, keep_inspect=True)
2196 This allows the inspection process run a configure mappers hook.
2198 """
2199 self._check_configure()
2201 @HasMemoized.memoized_attribute
2202 def _with_polymorphic_selectable(self):
2203 if not self.with_polymorphic:
2204 return self.persist_selectable
2206 spec, selectable = self.with_polymorphic
2207 if selectable is not None:
2208 return selectable
2209 else:
2210 return self._selectable_from_mappers(
2211 self._mappers_from_spec(spec, selectable), False
2212 )
2214 with_polymorphic_mappers = _with_polymorphic_mappers
2215 """The list of :class:`_orm.Mapper` objects included in the
2216 default "polymorphic" query.
2218 """
2220 @HasMemoized.memoized_attribute
2221 def _insert_cols_evaluating_none(self):
2222 return dict(
2223 (
2224 table,
2225 frozenset(
2226 col for col in columns if col.type.should_evaluate_none
2227 ),
2228 )
2229 for table, columns in self._cols_by_table.items()
2230 )
2232 @HasMemoized.memoized_attribute
2233 def _insert_cols_as_none(self):
2234 return dict(
2235 (
2236 table,
2237 frozenset(
2238 col.key
2239 for col in columns
2240 if not col.primary_key
2241 and not col.server_default
2242 and not col.default
2243 and not col.type.should_evaluate_none
2244 ),
2245 )
2246 for table, columns in self._cols_by_table.items()
2247 )
2249 @HasMemoized.memoized_attribute
2250 def _propkey_to_col(self):
2251 return dict(
2252 (
2253 table,
2254 dict(
2255 (self._columntoproperty[col].key, col) for col in columns
2256 ),
2257 )
2258 for table, columns in self._cols_by_table.items()
2259 )
2261 @HasMemoized.memoized_attribute
2262 def _pk_keys_by_table(self):
2263 return dict(
2264 (table, frozenset([col.key for col in pks]))
2265 for table, pks in self._pks_by_table.items()
2266 )
2268 @HasMemoized.memoized_attribute
2269 def _pk_attr_keys_by_table(self):
2270 return dict(
2271 (
2272 table,
2273 frozenset([self._columntoproperty[col].key for col in pks]),
2274 )
2275 for table, pks in self._pks_by_table.items()
2276 )
2278 @HasMemoized.memoized_attribute
2279 def _server_default_cols(self):
2280 return dict(
2281 (
2282 table,
2283 frozenset(
2284 [
2285 col.key
2286 for col in columns
2287 if col.server_default is not None
2288 ]
2289 ),
2290 )
2291 for table, columns in self._cols_by_table.items()
2292 )
2294 @HasMemoized.memoized_attribute
2295 def _server_default_plus_onupdate_propkeys(self):
2296 result = set()
2298 for table, columns in self._cols_by_table.items():
2299 for col in columns:
2300 if (
2301 col.server_default is not None
2302 or col.server_onupdate is not None
2303 ) and col in self._columntoproperty:
2304 result.add(self._columntoproperty[col].key)
2306 return result
2308 @HasMemoized.memoized_attribute
2309 def _server_onupdate_default_cols(self):
2310 return dict(
2311 (
2312 table,
2313 frozenset(
2314 [
2315 col.key
2316 for col in columns
2317 if col.server_onupdate is not None
2318 ]
2319 ),
2320 )
2321 for table, columns in self._cols_by_table.items()
2322 )
2324 @HasMemoized.memoized_instancemethod
2325 def __clause_element__(self):
2327 annotations = {
2328 "entity_namespace": self,
2329 "parententity": self,
2330 "parentmapper": self,
2331 }
2332 if self.persist_selectable is not self.local_table:
2333 # joined table inheritance, with polymorphic selectable,
2334 # etc.
2335 annotations["dml_table"] = self.local_table._annotate(
2336 {
2337 "entity_namespace": self,
2338 "parententity": self,
2339 "parentmapper": self,
2340 }
2341 )._set_propagate_attrs(
2342 {"compile_state_plugin": "orm", "plugin_subject": self}
2343 )
2345 return self.selectable._annotate(annotations)._set_propagate_attrs(
2346 {"compile_state_plugin": "orm", "plugin_subject": self}
2347 )
2349 @util.memoized_property
2350 def select_identity_token(self):
2351 return (
2352 expression.null()
2353 ._annotate(
2354 {
2355 "entity_namespace": self,
2356 "parententity": self,
2357 "parentmapper": self,
2358 "identity_token": True,
2359 }
2360 )
2361 ._set_propagate_attrs(
2362 {"compile_state_plugin": "orm", "plugin_subject": self}
2363 )
2364 )
2366 @property
2367 def selectable(self):
2368 """The :class:`_schema.FromClause` construct this
2369 :class:`_orm.Mapper` selects from by default.
2371 Normally, this is equivalent to :attr:`.persist_selectable`, unless
2372 the ``with_polymorphic`` feature is in use, in which case the
2373 full "polymorphic" selectable is returned.
2375 """
2376 return self._with_polymorphic_selectable
2378 def _with_polymorphic_args(
2379 self, spec=None, selectable=False, innerjoin=False
2380 ):
2381 if selectable not in (None, False):
2382 selectable = coercions.expect(
2383 roles.StrictFromClauseRole, selectable, allow_select=True
2384 )
2386 if self.with_polymorphic:
2387 if not spec:
2388 spec = self.with_polymorphic[0]
2389 if selectable is False:
2390 selectable = self.with_polymorphic[1]
2391 elif selectable is False:
2392 selectable = None
2393 mappers = self._mappers_from_spec(spec, selectable)
2394 if selectable is not None:
2395 return mappers, selectable
2396 else:
2397 return mappers, self._selectable_from_mappers(mappers, innerjoin)
2399 @HasMemoized.memoized_attribute
2400 def _polymorphic_properties(self):
2401 return list(
2402 self._iterate_polymorphic_properties(
2403 self._with_polymorphic_mappers
2404 )
2405 )
2407 @property
2408 def _all_column_expressions(self):
2409 poly_properties = self._polymorphic_properties
2410 adapter = self._polymorphic_adapter
2412 return [
2413 adapter.columns[prop.columns[0]] if adapter else prop.columns[0]
2414 for prop in poly_properties
2415 if isinstance(prop, properties.ColumnProperty)
2416 and prop._renders_in_subqueries
2417 ]
2419 def _columns_plus_keys(self, polymorphic_mappers=()):
2420 if polymorphic_mappers:
2421 poly_properties = self._iterate_polymorphic_properties(
2422 polymorphic_mappers
2423 )
2424 else:
2425 poly_properties = self._polymorphic_properties
2427 return [
2428 (prop.key, prop.columns[0])
2429 for prop in poly_properties
2430 if isinstance(prop, properties.ColumnProperty)
2431 ]
2433 @HasMemoized.memoized_attribute
2434 def _polymorphic_adapter(self):
2435 if self.with_polymorphic:
2436 return sql_util.ColumnAdapter(
2437 self.selectable, equivalents=self._equivalent_columns
2438 )
2439 else:
2440 return None
2442 def _iterate_polymorphic_properties(self, mappers=None):
2443 """Return an iterator of MapperProperty objects which will render into
2444 a SELECT."""
2445 if mappers is None:
2446 mappers = self._with_polymorphic_mappers
2448 if not mappers:
2449 for c in self.iterate_properties:
2450 yield c
2451 else:
2452 # in the polymorphic case, filter out discriminator columns
2453 # from other mappers, as these are sometimes dependent on that
2454 # mapper's polymorphic selectable (which we don't want rendered)
2455 for c in util.unique_list(
2456 chain(
2457 *[
2458 list(mapper.iterate_properties)
2459 for mapper in [self] + mappers
2460 ]
2461 )
2462 ):
2463 if getattr(c, "_is_polymorphic_discriminator", False) and (
2464 self.polymorphic_on is None
2465 or c.columns[0] is not self.polymorphic_on
2466 ):
2467 continue
2468 yield c
2470 @HasMemoized.memoized_attribute
2471 def attrs(self):
2472 """A namespace of all :class:`.MapperProperty` objects
2473 associated this mapper.
2475 This is an object that provides each property based on
2476 its key name. For instance, the mapper for a
2477 ``User`` class which has ``User.name`` attribute would
2478 provide ``mapper.attrs.name``, which would be the
2479 :class:`.ColumnProperty` representing the ``name``
2480 column. The namespace object can also be iterated,
2481 which would yield each :class:`.MapperProperty`.
2483 :class:`_orm.Mapper` has several pre-filtered views
2484 of this attribute which limit the types of properties
2485 returned, including :attr:`.synonyms`, :attr:`.column_attrs`,
2486 :attr:`.relationships`, and :attr:`.composites`.
2488 .. warning::
2490 The :attr:`_orm.Mapper.attrs` accessor namespace is an
2491 instance of :class:`.OrderedProperties`. This is
2492 a dictionary-like object which includes a small number of
2493 named methods such as :meth:`.OrderedProperties.items`
2494 and :meth:`.OrderedProperties.values`. When
2495 accessing attributes dynamically, favor using the dict-access
2496 scheme, e.g. ``mapper.attrs[somename]`` over
2497 ``getattr(mapper.attrs, somename)`` to avoid name collisions.
2499 .. seealso::
2501 :attr:`_orm.Mapper.all_orm_descriptors`
2503 """
2505 self._check_configure()
2506 return util.ImmutableProperties(self._props)
2508 @HasMemoized.memoized_attribute
2509 def all_orm_descriptors(self):
2510 """A namespace of all :class:`.InspectionAttr` attributes associated
2511 with the mapped class.
2513 These attributes are in all cases Python :term:`descriptors`
2514 associated with the mapped class or its superclasses.
2516 This namespace includes attributes that are mapped to the class
2517 as well as attributes declared by extension modules.
2518 It includes any Python descriptor type that inherits from
2519 :class:`.InspectionAttr`. This includes
2520 :class:`.QueryableAttribute`, as well as extension types such as
2521 :class:`.hybrid_property`, :class:`.hybrid_method` and
2522 :class:`.AssociationProxy`.
2524 To distinguish between mapped attributes and extension attributes,
2525 the attribute :attr:`.InspectionAttr.extension_type` will refer
2526 to a constant that distinguishes between different extension types.
2528 The sorting of the attributes is based on the following rules:
2530 1. Iterate through the class and its superclasses in order from
2531 subclass to superclass (i.e. iterate through ``cls.__mro__``)
2533 2. For each class, yield the attributes in the order in which they
2534 appear in ``__dict__``, with the exception of those in step
2535 3 below. In Python 3.6 and above this ordering will be the
2536 same as that of the class' construction, with the exception
2537 of attributes that were added after the fact by the application
2538 or the mapper.
2540 3. If a certain attribute key is also in the superclass ``__dict__``,
2541 then it's included in the iteration for that class, and not the
2542 class in which it first appeared.
2544 The above process produces an ordering that is deterministic in terms
2545 of the order in which attributes were assigned to the class.
2547 .. versionchanged:: 1.3.19 ensured deterministic ordering for
2548 :meth:`_orm.Mapper.all_orm_descriptors`.
2550 When dealing with a :class:`.QueryableAttribute`, the
2551 :attr:`.QueryableAttribute.property` attribute refers to the
2552 :class:`.MapperProperty` property, which is what you get when
2553 referring to the collection of mapped properties via
2554 :attr:`_orm.Mapper.attrs`.
2556 .. warning::
2558 The :attr:`_orm.Mapper.all_orm_descriptors`
2559 accessor namespace is an
2560 instance of :class:`.OrderedProperties`. This is
2561 a dictionary-like object which includes a small number of
2562 named methods such as :meth:`.OrderedProperties.items`
2563 and :meth:`.OrderedProperties.values`. When
2564 accessing attributes dynamically, favor using the dict-access
2565 scheme, e.g. ``mapper.all_orm_descriptors[somename]`` over
2566 ``getattr(mapper.all_orm_descriptors, somename)`` to avoid name
2567 collisions.
2569 .. seealso::
2571 :attr:`_orm.Mapper.attrs`
2573 """
2574 return util.ImmutableProperties(
2575 dict(self.class_manager._all_sqla_attributes())
2576 )
2578 @HasMemoized.memoized_attribute
2579 @util.preload_module("sqlalchemy.orm.descriptor_props")
2580 def _pk_synonyms(self):
2581 """return a dictionary of {syn_attribute_name: pk_attr_name} for
2582 all synonyms that refer to primary key columns
2584 """
2585 descriptor_props = util.preloaded.orm_descriptor_props
2587 pk_keys = {prop.key for prop in self._identity_key_props}
2589 return {
2590 syn.key: syn.name
2591 for k, syn in self._props.items()
2592 if isinstance(syn, descriptor_props.SynonymProperty)
2593 and syn.name in pk_keys
2594 }
2596 @HasMemoized.memoized_attribute
2597 @util.preload_module("sqlalchemy.orm.descriptor_props")
2598 def synonyms(self):
2599 """Return a namespace of all :class:`.SynonymProperty`
2600 properties maintained by this :class:`_orm.Mapper`.
2602 .. seealso::
2604 :attr:`_orm.Mapper.attrs` - namespace of all
2605 :class:`.MapperProperty`
2606 objects.
2608 """
2609 descriptor_props = util.preloaded.orm_descriptor_props
2611 return self._filter_properties(descriptor_props.SynonymProperty)
2613 @property
2614 def entity_namespace(self):
2615 return self.class_
2617 @HasMemoized.memoized_attribute
2618 def column_attrs(self):
2619 """Return a namespace of all :class:`.ColumnProperty`
2620 properties maintained by this :class:`_orm.Mapper`.
2622 .. seealso::
2624 :attr:`_orm.Mapper.attrs` - namespace of all
2625 :class:`.MapperProperty`
2626 objects.
2628 """
2629 return self._filter_properties(properties.ColumnProperty)
2631 @util.preload_module("sqlalchemy.orm.relationships")
2632 @HasMemoized.memoized_attribute
2633 def relationships(self):
2634 """A namespace of all :class:`.RelationshipProperty` properties
2635 maintained by this :class:`_orm.Mapper`.
2637 .. warning::
2639 the :attr:`_orm.Mapper.relationships` accessor namespace is an
2640 instance of :class:`.OrderedProperties`. This is
2641 a dictionary-like object which includes a small number of
2642 named methods such as :meth:`.OrderedProperties.items`
2643 and :meth:`.OrderedProperties.values`. When
2644 accessing attributes dynamically, favor using the dict-access
2645 scheme, e.g. ``mapper.relationships[somename]`` over
2646 ``getattr(mapper.relationships, somename)`` to avoid name
2647 collisions.
2649 .. seealso::
2651 :attr:`_orm.Mapper.attrs` - namespace of all
2652 :class:`.MapperProperty`
2653 objects.
2655 """
2656 return self._filter_properties(
2657 util.preloaded.orm_relationships.RelationshipProperty
2658 )
2660 @HasMemoized.memoized_attribute
2661 @util.preload_module("sqlalchemy.orm.descriptor_props")
2662 def composites(self):
2663 """Return a namespace of all :class:`.CompositeProperty`
2664 properties maintained by this :class:`_orm.Mapper`.
2666 .. seealso::
2668 :attr:`_orm.Mapper.attrs` - namespace of all
2669 :class:`.MapperProperty`
2670 objects.
2672 """
2673 return self._filter_properties(
2674 util.preloaded.orm_descriptor_props.CompositeProperty
2675 )
2677 def _filter_properties(self, type_):
2678 self._check_configure()
2679 return util.ImmutableProperties(
2680 util.OrderedDict(
2681 (k, v) for k, v in self._props.items() if isinstance(v, type_)
2682 )
2683 )
2685 @HasMemoized.memoized_attribute
2686 def _get_clause(self):
2687 """create a "get clause" based on the primary key. this is used
2688 by query.get() and many-to-one lazyloads to load this item
2689 by primary key.
2691 """
2692 params = [
2693 (
2694 primary_key,
2695 sql.bindparam("pk_%d" % idx, type_=primary_key.type),
2696 )
2697 for idx, primary_key in enumerate(self.primary_key, 1)
2698 ]
2699 return (
2700 sql.and_(*[k == v for (k, v) in params]),
2701 util.column_dict(params),
2702 )
2704 @HasMemoized.memoized_attribute
2705 def _equivalent_columns(self):
2706 """Create a map of all equivalent columns, based on
2707 the determination of column pairs that are equated to
2708 one another based on inherit condition. This is designed
2709 to work with the queries that util.polymorphic_union
2710 comes up with, which often don't include the columns from
2711 the base table directly (including the subclass table columns
2712 only).
2714 The resulting structure is a dictionary of columns mapped
2715 to lists of equivalent columns, e.g.::
2717 {
2718 tablea.col1:
2719 {tableb.col1, tablec.col1},
2720 tablea.col2:
2721 {tabled.col2}
2722 }
2724 """
2725 result = util.column_dict()
2727 def visit_binary(binary):
2728 if binary.operator == operators.eq:
2729 if binary.left in result:
2730 result[binary.left].add(binary.right)
2731 else:
2732 result[binary.left] = util.column_set((binary.right,))
2733 if binary.right in result:
2734 result[binary.right].add(binary.left)
2735 else:
2736 result[binary.right] = util.column_set((binary.left,))
2738 for mapper in self.base_mapper.self_and_descendants:
2739 if mapper.inherit_condition is not None:
2740 visitors.traverse(
2741 mapper.inherit_condition, {}, {"binary": visit_binary}
2742 )
2744 return result
2746 def _is_userland_descriptor(self, assigned_name, obj):
2747 if isinstance(
2748 obj,
2749 (
2750 _MappedAttribute,
2751 instrumentation.ClassManager,
2752 expression.ColumnElement,
2753 ),
2754 ):
2755 return False
2756 else:
2757 return assigned_name not in self._dataclass_fields
2759 @HasMemoized.memoized_attribute
2760 def _dataclass_fields(self):
2761 return [f.name for f in util.dataclass_fields(self.class_)]
2763 def _should_exclude(self, name, assigned_name, local, column):
2764 """determine whether a particular property should be implicitly
2765 present on the class.
2767 This occurs when properties are propagated from an inherited class, or
2768 are applied from the columns present in the mapped table.
2770 """
2772 # check for class-bound attributes and/or descriptors,
2773 # either local or from an inherited class
2774 # ignore dataclass field default values
2775 if local:
2776 if self.class_.__dict__.get(
2777 assigned_name, None
2778 ) is not None and self._is_userland_descriptor(
2779 assigned_name, self.class_.__dict__[assigned_name]
2780 ):
2781 return True
2782 else:
2783 attr = self.class_manager._get_class_attr_mro(assigned_name, None)
2784 if attr is not None and self._is_userland_descriptor(
2785 assigned_name, attr
2786 ):
2787 return True
2789 if (
2790 self.include_properties is not None
2791 and name not in self.include_properties
2792 and (column is None or column not in self.include_properties)
2793 ):
2794 self._log("not including property %s" % (name))
2795 return True
2797 if self.exclude_properties is not None and (
2798 name in self.exclude_properties
2799 or (column is not None and column in self.exclude_properties)
2800 ):
2801 self._log("excluding property %s" % (name))
2802 return True
2804 return False
2806 def common_parent(self, other):
2807 """Return true if the given mapper shares a
2808 common inherited parent as this mapper."""
2810 return self.base_mapper is other.base_mapper
2812 def is_sibling(self, other):
2813 """return true if the other mapper is an inheriting sibling to this
2814 one. common parent but different branch
2816 """
2817 return (
2818 self.base_mapper is other.base_mapper
2819 and not self.isa(other)
2820 and not other.isa(self)
2821 )
2823 def _canload(self, state, allow_subtypes):
2824 s = self.primary_mapper()
2825 if self.polymorphic_on is not None or allow_subtypes:
2826 return _state_mapper(state).isa(s)
2827 else:
2828 return _state_mapper(state) is s
2830 def isa(self, other):
2831 """Return True if the this mapper inherits from the given mapper."""
2833 m = self
2834 while m and m is not other:
2835 m = m.inherits
2836 return bool(m)
2838 def iterate_to_root(self):
2839 m = self
2840 while m:
2841 yield m
2842 m = m.inherits
2844 @HasMemoized.memoized_attribute
2845 def self_and_descendants(self):
2846 """The collection including this mapper and all descendant mappers.
2848 This includes not just the immediately inheriting mappers but
2849 all their inheriting mappers as well.
2851 """
2852 descendants = []
2853 stack = deque([self])
2854 while stack:
2855 item = stack.popleft()
2856 descendants.append(item)
2857 stack.extend(item._inheriting_mappers)
2858 return util.WeakSequence(descendants)
2860 def polymorphic_iterator(self):
2861 """Iterate through the collection including this mapper and
2862 all descendant mappers.
2864 This includes not just the immediately inheriting mappers but
2865 all their inheriting mappers as well.
2867 To iterate through an entire hierarchy, use
2868 ``mapper.base_mapper.polymorphic_iterator()``.
2870 """
2871 return iter(self.self_and_descendants)
2873 def primary_mapper(self):
2874 """Return the primary mapper corresponding to this mapper's class key
2875 (class)."""
2877 return self.class_manager.mapper
2879 @property
2880 def primary_base_mapper(self):
2881 return self.class_manager.mapper.base_mapper
2883 def _result_has_identity_key(self, result, adapter=None):
2884 pk_cols = self.primary_key
2885 if adapter:
2886 pk_cols = [adapter.columns[c] for c in pk_cols]
2887 rk = result.keys()
2888 for col in pk_cols:
2889 if col not in rk:
2890 return False
2891 else:
2892 return True
2894 def identity_key_from_row(self, row, identity_token=None, adapter=None):
2895 """Return an identity-map key for use in storing/retrieving an
2896 item from the identity map.
2898 :param row: A :class:`.Row` instance. The columns which are
2899 mapped by this :class:`_orm.Mapper` should be locatable in the row,
2900 preferably via the :class:`_schema.Column`
2901 object directly (as is the case
2902 when a :func:`_expression.select` construct is executed), or
2903 via string names of the form ``<tablename>_<colname>``.
2905 """
2906 pk_cols = self.primary_key
2907 if adapter:
2908 pk_cols = [adapter.columns[c] for c in pk_cols]
2910 return (
2911 self._identity_class,
2912 tuple(row[column] for column in pk_cols),
2913 identity_token,
2914 )
2916 def identity_key_from_primary_key(self, primary_key, identity_token=None):
2917 """Return an identity-map key for use in storing/retrieving an
2918 item from an identity map.
2920 :param primary_key: A list of values indicating the identifier.
2922 """
2923 return self._identity_class, tuple(primary_key), identity_token
2925 def identity_key_from_instance(self, instance):
2926 """Return the identity key for the given instance, based on
2927 its primary key attributes.
2929 If the instance's state is expired, calling this method
2930 will result in a database check to see if the object has been deleted.
2931 If the row no longer exists,
2932 :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised.
2934 This value is typically also found on the instance state under the
2935 attribute name `key`.
2937 """
2938 state = attributes.instance_state(instance)
2939 return self._identity_key_from_state(state, attributes.PASSIVE_OFF)
2941 def _identity_key_from_state(
2942 self, state, passive=attributes.PASSIVE_RETURN_NO_VALUE
2943 ):
2944 dict_ = state.dict
2945 manager = state.manager
2946 return (
2947 self._identity_class,
2948 tuple(
2949 [
2950 manager[prop.key].impl.get(state, dict_, passive)
2951 for prop in self._identity_key_props
2952 ]
2953 ),
2954 state.identity_token,
2955 )
2957 def primary_key_from_instance(self, instance):
2958 """Return the list of primary key values for the given
2959 instance.
2961 If the instance's state is expired, calling this method
2962 will result in a database check to see if the object has been deleted.
2963 If the row no longer exists,
2964 :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised.
2966 """
2967 state = attributes.instance_state(instance)
2968 identity_key = self._identity_key_from_state(
2969 state, attributes.PASSIVE_OFF
2970 )
2971 return identity_key[1]
2973 @HasMemoized.memoized_attribute
2974 def _persistent_sortkey_fn(self):
2975 key_fns = [col.type.sort_key_function for col in self.primary_key]
2977 if set(key_fns).difference([None]):
2979 def key(state):
2980 return tuple(
2981 key_fn(val) if key_fn is not None else val
2982 for key_fn, val in zip(key_fns, state.key[1])
2983 )
2985 else:
2987 def key(state):
2988 return state.key[1]
2990 return key
2992 @HasMemoized.memoized_attribute
2993 def _identity_key_props(self):
2994 return [self._columntoproperty[col] for col in self.primary_key]
2996 @HasMemoized.memoized_attribute
2997 def _all_pk_cols(self):
2998 collection = set()
2999 for table in self.tables:
3000 collection.update(self._pks_by_table[table])
3001 return collection
3003 @HasMemoized.memoized_attribute
3004 def _should_undefer_in_wildcard(self):
3005 cols = set(self.primary_key)
3006 if self.polymorphic_on is not None:
3007 cols.add(self.polymorphic_on)
3008 return cols
3010 @HasMemoized.memoized_attribute
3011 def _primary_key_propkeys(self):
3012 return {self._columntoproperty[col].key for col in self._all_pk_cols}
3014 def _get_state_attr_by_column(
3015 self, state, dict_, column, passive=attributes.PASSIVE_RETURN_NO_VALUE
3016 ):
3017 prop = self._columntoproperty[column]
3018 return state.manager[prop.key].impl.get(state, dict_, passive=passive)
3020 def _set_committed_state_attr_by_column(self, state, dict_, column, value):
3021 prop = self._columntoproperty[column]
3022 state.manager[prop.key].impl.set_committed_value(state, dict_, value)
3024 def _set_state_attr_by_column(self, state, dict_, column, value):
3025 prop = self._columntoproperty[column]
3026 state.manager[prop.key].impl.set(state, dict_, value, None)
3028 def _get_committed_attr_by_column(self, obj, column):
3029 state = attributes.instance_state(obj)
3030 dict_ = attributes.instance_dict(obj)
3031 return self._get_committed_state_attr_by_column(
3032 state, dict_, column, passive=attributes.PASSIVE_OFF
3033 )
3035 def _get_committed_state_attr_by_column(
3036 self, state, dict_, column, passive=attributes.PASSIVE_RETURN_NO_VALUE
3037 ):
3039 prop = self._columntoproperty[column]
3040 return state.manager[prop.key].impl.get_committed_value(
3041 state, dict_, passive=passive
3042 )
3044 def _optimized_get_statement(self, state, attribute_names):
3045 """assemble a WHERE clause which retrieves a given state by primary
3046 key, using a minimized set of tables.
3048 Applies to a joined-table inheritance mapper where the
3049 requested attribute names are only present on joined tables,
3050 not the base table. The WHERE clause attempts to include
3051 only those tables to minimize joins.
3053 """
3054 props = self._props
3056 col_attribute_names = set(attribute_names).intersection(
3057 state.mapper.column_attrs.keys()
3058 )
3059 tables = set(
3060 chain(
3061 *[
3062 sql_util.find_tables(c, check_columns=True)
3063 for key in col_attribute_names
3064 for c in props[key].columns
3065 ]
3066 )
3067 )
3069 if self.base_mapper.local_table in tables:
3070 return None
3072 def visit_binary(binary):
3073 leftcol = binary.left
3074 rightcol = binary.right
3075 if leftcol is None or rightcol is None:
3076 return
3078 if leftcol.table not in tables:
3079 leftval = self._get_committed_state_attr_by_column(
3080 state,
3081 state.dict,
3082 leftcol,
3083 passive=attributes.PASSIVE_NO_INITIALIZE,
3084 )
3085 if leftval in orm_util._none_set:
3086 raise _OptGetColumnsNotAvailable()
3087 binary.left = sql.bindparam(
3088 None, leftval, type_=binary.right.type
3089 )
3090 elif rightcol.table not in tables:
3091 rightval = self._get_committed_state_attr_by_column(
3092 state,
3093 state.dict,
3094 rightcol,
3095 passive=attributes.PASSIVE_NO_INITIALIZE,
3096 )
3097 if rightval in orm_util._none_set:
3098 raise _OptGetColumnsNotAvailable()
3099 binary.right = sql.bindparam(
3100 None, rightval, type_=binary.right.type
3101 )
3103 allconds = []
3105 start = False
3107 # as of #7507, from the lowest base table on upwards,
3108 # we include all intermediary tables.
3110 for mapper in reversed(list(self.iterate_to_root())):
3111 if mapper.local_table in tables:
3112 start = True
3113 elif not isinstance(mapper.local_table, expression.TableClause):
3114 return None
3115 if start and not mapper.single:
3116 allconds.append(mapper.inherit_condition)
3117 tables.add(mapper.local_table)
3119 # only the bottom table needs its criteria to be altered to fit
3120 # the primary key ident - the rest of the tables upwards to the
3121 # descendant-most class should all be present and joined to each
3122 # other.
3123 try:
3124 allconds[0] = visitors.cloned_traverse(
3125 allconds[0], {}, {"binary": visit_binary}
3126 )
3127 except _OptGetColumnsNotAvailable:
3128 return None
3130 cond = sql.and_(*allconds)
3132 cols = []
3133 for key in col_attribute_names:
3134 cols.extend(props[key].columns)
3135 return (
3136 sql.select(*cols)
3137 .where(cond)
3138 .set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL)
3139 )
3141 def _iterate_to_target_viawpoly(self, mapper):
3142 if self.isa(mapper):
3143 prev = self
3144 for m in self.iterate_to_root():
3145 yield m
3147 if m is not prev and prev not in m._with_polymorphic_mappers:
3148 break
3150 prev = m
3151 if m is mapper:
3152 break
3154 def _should_selectin_load(self, enabled_via_opt, polymorphic_from):
3155 if not enabled_via_opt:
3156 # common case, takes place for all polymorphic loads
3157 mapper = polymorphic_from
3158 for m in self._iterate_to_target_viawpoly(mapper):
3159 if m.polymorphic_load == "selectin":
3160 return m
3161 else:
3162 # uncommon case, selectin load options were used
3163 enabled_via_opt = set(enabled_via_opt)
3164 enabled_via_opt_mappers = {e.mapper: e for e in enabled_via_opt}
3165 for entity in enabled_via_opt.union([polymorphic_from]):
3166 mapper = entity.mapper
3167 for m in self._iterate_to_target_viawpoly(mapper):
3168 if (
3169 m.polymorphic_load == "selectin"
3170 or m in enabled_via_opt_mappers
3171 ):
3172 return enabled_via_opt_mappers.get(m, m)
3174 return None
3176 @util.preload_module("sqlalchemy.orm.strategy_options")
3177 def _subclass_load_via_in(self, entity):
3178 """Assemble a that can load the columns local to
3179 this subclass as a SELECT with IN.
3181 """
3182 strategy_options = util.preloaded.orm_strategy_options
3184 assert self.inherits
3186 if self.polymorphic_on is not None:
3187 polymorphic_prop = self._columntoproperty[self.polymorphic_on]
3188 keep_props = set([polymorphic_prop] + self._identity_key_props)
3189 else:
3190 keep_props = set(self._identity_key_props)
3192 disable_opt = strategy_options.Load(entity)
3193 enable_opt = strategy_options.Load(entity)
3195 for prop in self.attrs:
3197 # skip prop keys that are not instrumented on the mapped class.
3198 # this is primarily the "_sa_polymorphic_on" property that gets
3199 # created for an ad-hoc polymorphic_on SQL expression, issue #8704
3200 if prop.key not in self.class_manager:
3201 continue
3203 if prop.parent is self or prop in keep_props:
3204 # "enable" options, to turn on the properties that we want to
3205 # load by default (subject to options from the query)
3206 if not isinstance(prop, StrategizedProperty):
3207 continue
3209 enable_opt.set_generic_strategy(
3210 # convert string name to an attribute before passing
3211 # to loader strategy. note this must be in terms
3212 # of given entity, such as AliasedClass, etc.
3213 (getattr(entity.entity_namespace, prop.key),),
3214 dict(prop.strategy_key),
3215 )
3216 else:
3217 # "disable" options, to turn off the properties from the
3218 # superclass that we *don't* want to load, applied after
3219 # the options from the query to override them
3220 disable_opt.set_generic_strategy(
3221 # convert string name to an attribute before passing
3222 # to loader strategy. note this must be in terms
3223 # of given entity, such as AliasedClass, etc.
3224 (getattr(entity.entity_namespace, prop.key),),
3225 {"do_nothing": True},
3226 )
3228 primary_key = [
3229 sql_util._deep_annotate(pk, {"_orm_adapt": True})
3230 for pk in self.primary_key
3231 ]
3233 if len(primary_key) > 1:
3234 in_expr = sql.tuple_(*primary_key)
3235 else:
3236 in_expr = primary_key[0]
3238 if entity.is_aliased_class:
3239 assert entity.mapper is self
3241 q = sql.select(entity).set_label_style(
3242 LABEL_STYLE_TABLENAME_PLUS_COL
3243 )
3245 in_expr = entity._adapter.traverse(in_expr)
3246 primary_key = [entity._adapter.traverse(k) for k in primary_key]
3247 q = q.where(
3248 in_expr.in_(sql.bindparam("primary_keys", expanding=True))
3249 ).order_by(*primary_key)
3250 else:
3252 q = sql.select(self).set_label_style(
3253 LABEL_STYLE_TABLENAME_PLUS_COL
3254 )
3255 q = q.where(
3256 in_expr.in_(sql.bindparam("primary_keys", expanding=True))
3257 ).order_by(*primary_key)
3259 return q, enable_opt, disable_opt
3261 @HasMemoized.memoized_attribute
3262 def _subclass_load_via_in_mapper(self):
3263 return self._subclass_load_via_in(self)
3265 def cascade_iterator(self, type_, state, halt_on=None):
3266 r"""Iterate each element and its mapper in an object graph,
3267 for all relationships that meet the given cascade rule.
3269 :param type\_:
3270 The name of the cascade rule (i.e. ``"save-update"``, ``"delete"``,
3271 etc.).
3273 .. note:: the ``"all"`` cascade is not accepted here. For a generic
3274 object traversal function, see :ref:`faq_walk_objects`.
3276 :param state:
3277 The lead InstanceState. child items will be processed per
3278 the relationships defined for this object's mapper.
3280 :return: the method yields individual object instances.
3282 .. seealso::
3284 :ref:`unitofwork_cascades`
3286 :ref:`faq_walk_objects` - illustrates a generic function to
3287 traverse all objects without relying on cascades.
3289 """
3290 visited_states = set()
3291 prp, mpp = object(), object()
3293 assert state.mapper.isa(self)
3295 visitables = deque(
3296 [(deque(state.mapper._props.values()), prp, state, state.dict)]
3297 )
3299 while visitables:
3300 iterator, item_type, parent_state, parent_dict = visitables[-1]
3301 if not iterator:
3302 visitables.pop()
3303 continue
3305 if item_type is prp:
3306 prop = iterator.popleft()
3307 if type_ not in prop.cascade:
3308 continue
3309 queue = deque(
3310 prop.cascade_iterator(
3311 type_,
3312 parent_state,
3313 parent_dict,
3314 visited_states,
3315 halt_on,
3316 )
3317 )
3318 if queue:
3319 visitables.append((queue, mpp, None, None))
3320 elif item_type is mpp:
3321 (
3322 instance,
3323 instance_mapper,
3324 corresponding_state,
3325 corresponding_dict,
3326 ) = iterator.popleft()
3327 yield (
3328 instance,
3329 instance_mapper,
3330 corresponding_state,
3331 corresponding_dict,
3332 )
3333 visitables.append(
3334 (
3335 deque(instance_mapper._props.values()),
3336 prp,
3337 corresponding_state,
3338 corresponding_dict,
3339 )
3340 )
3342 @HasMemoized.memoized_attribute
3343 def _compiled_cache(self):
3344 return util.LRUCache(self._compiled_cache_size)
3346 @HasMemoized.memoized_attribute
3347 def _sorted_tables(self):
3348 table_to_mapper = {}
3350 for mapper in self.base_mapper.self_and_descendants:
3351 for t in mapper.tables:
3352 table_to_mapper.setdefault(t, mapper)
3354 extra_dependencies = []
3355 for table, mapper in table_to_mapper.items():
3356 super_ = mapper.inherits
3357 if super_:
3358 extra_dependencies.extend(
3359 [(super_table, table) for super_table in super_.tables]
3360 )
3362 def skip(fk):
3363 # attempt to skip dependencies that are not
3364 # significant to the inheritance chain
3365 # for two tables that are related by inheritance.
3366 # while that dependency may be important, it's technically
3367 # not what we mean to sort on here.
3368 parent = table_to_mapper.get(fk.parent.table)
3369 dep = table_to_mapper.get(fk.column.table)
3370 if (
3371 parent is not None
3372 and dep is not None
3373 and dep is not parent
3374 and dep.inherit_condition is not None
3375 ):
3376 cols = set(sql_util._find_columns(dep.inherit_condition))
3377 if parent.inherit_condition is not None:
3378 cols = cols.union(
3379 sql_util._find_columns(parent.inherit_condition)
3380 )
3381 return fk.parent not in cols and fk.column not in cols
3382 else:
3383 return fk.parent not in cols
3384 return False
3386 sorted_ = sql_util.sort_tables(
3387 table_to_mapper,
3388 skip_fn=skip,
3389 extra_dependencies=extra_dependencies,
3390 )
3392 ret = util.OrderedDict()
3393 for t in sorted_:
3394 ret[t] = table_to_mapper[t]
3395 return ret
3397 def _memo(self, key, callable_):
3398 if key in self._memoized_values:
3399 return self._memoized_values[key]
3400 else:
3401 self._memoized_values[key] = value = callable_()
3402 return value
3404 @util.memoized_property
3405 def _table_to_equated(self):
3406 """memoized map of tables to collections of columns to be
3407 synchronized upwards to the base mapper."""
3409 result = util.defaultdict(list)
3411 for table in self._sorted_tables:
3412 cols = set(table.c)
3413 for m in self.iterate_to_root():
3414 if m._inherits_equated_pairs and cols.intersection(
3415 util.reduce(
3416 set.union,
3417 [l.proxy_set for l, r in m._inherits_equated_pairs],
3418 )
3419 ):
3420 result[table].append((m, m._inherits_equated_pairs))
3422 return result
3425class _OptGetColumnsNotAvailable(Exception):
3426 pass
3429def configure_mappers():
3430 """Initialize the inter-mapper relationships of all mappers that
3431 have been constructed thus far across all :class:`_orm.registry`
3432 collections.
3434 The configure step is used to reconcile and initialize the
3435 :func:`_orm.relationship` linkages between mapped classes, as well as to
3436 invoke configuration events such as the
3437 :meth:`_orm.MapperEvents.before_configured` and
3438 :meth:`_orm.MapperEvents.after_configured`, which may be used by ORM
3439 extensions or user-defined extension hooks.
3441 Mapper configuration is normally invoked automatically, the first time
3442 mappings from a particular :class:`_orm.registry` are used, as well as
3443 whenever mappings are used and additional not-yet-configured mappers have
3444 been constructed. The automatic configuration process however is local only
3445 to the :class:`_orm.registry` involving the target mapper and any related
3446 :class:`_orm.registry` objects which it may depend on; this is
3447 equivalent to invoking the :meth:`_orm.registry.configure` method
3448 on a particular :class:`_orm.registry`.
3450 By contrast, the :func:`_orm.configure_mappers` function will invoke the
3451 configuration process on all :class:`_orm.registry` objects that
3452 exist in memory, and may be useful for scenarios where many individual
3453 :class:`_orm.registry` objects that are nonetheless interrelated are
3454 in use.
3456 .. versionchanged:: 1.4
3458 As of SQLAlchemy 1.4.0b2, this function works on a
3459 per-:class:`_orm.registry` basis, locating all :class:`_orm.registry`
3460 objects present and invoking the :meth:`_orm.registry.configure` method
3461 on each. The :meth:`_orm.registry.configure` method may be preferred to
3462 limit the configuration of mappers to those local to a particular
3463 :class:`_orm.registry` and/or declarative base class.
3465 Points at which automatic configuration is invoked include when a mapped
3466 class is instantiated into an instance, as well as when ORM queries
3467 are emitted using :meth:`.Session.query` or :meth:`_orm.Session.execute`
3468 with an ORM-enabled statement.
3470 The mapper configure process, whether invoked by
3471 :func:`_orm.configure_mappers` or from :meth:`_orm.registry.configure`,
3472 provides several event hooks that can be used to augment the mapper
3473 configuration step. These hooks include:
3475 * :meth:`.MapperEvents.before_configured` - called once before
3476 :func:`.configure_mappers` or :meth:`_orm.registry.configure` does any
3477 work; this can be used to establish additional options, properties, or
3478 related mappings before the operation proceeds.
3480 * :meth:`.MapperEvents.mapper_configured` - called as each individual
3481 :class:`_orm.Mapper` is configured within the process; will include all
3482 mapper state except for backrefs set up by other mappers that are still
3483 to be configured.
3485 * :meth:`.MapperEvents.after_configured` - called once after
3486 :func:`.configure_mappers` or :meth:`_orm.registry.configure` is
3487 complete; at this stage, all :class:`_orm.Mapper` objects that fall
3488 within the scope of the configuration operation will be fully configured.
3489 Note that the calling application may still have other mappings that
3490 haven't been produced yet, such as if they are in modules as yet
3491 unimported, and may also have mappings that are still to be configured,
3492 if they are in other :class:`_orm.registry` collections not part of the
3493 current scope of configuration.
3495 """
3497 _configure_registries(_all_registries(), cascade=True)
3500def _configure_registries(registries, cascade):
3501 for reg in registries:
3502 if reg._new_mappers:
3503 break
3504 else:
3505 return
3507 with _CONFIGURE_MUTEX:
3508 global _already_compiling
3509 if _already_compiling:
3510 return
3511 _already_compiling = True
3512 try:
3514 # double-check inside mutex
3515 for reg in registries:
3516 if reg._new_mappers:
3517 break
3518 else:
3519 return
3521 Mapper.dispatch._for_class(Mapper).before_configured()
3522 # initialize properties on all mappers
3523 # note that _mapper_registry is unordered, which
3524 # may randomly conceal/reveal issues related to
3525 # the order of mapper compilation
3527 _do_configure_registries(registries, cascade)
3528 finally:
3529 _already_compiling = False
3530 Mapper.dispatch._for_class(Mapper).after_configured()
3533@util.preload_module("sqlalchemy.orm.decl_api")
3534def _do_configure_registries(registries, cascade):
3536 registry = util.preloaded.orm_decl_api.registry
3538 orig = set(registries)
3540 for reg in registry._recurse_with_dependencies(registries):
3541 has_skip = False
3543 for mapper in reg._mappers_to_configure():
3544 run_configure = None
3545 for fn in mapper.dispatch.before_mapper_configured:
3546 run_configure = fn(mapper, mapper.class_)
3547 if run_configure is EXT_SKIP:
3548 has_skip = True
3549 break
3550 if run_configure is EXT_SKIP:
3551 continue
3553 if getattr(mapper, "_configure_failed", False):
3554 e = sa_exc.InvalidRequestError(
3555 "One or more mappers failed to initialize - "
3556 "can't proceed with initialization of other "
3557 "mappers. Triggering mapper: '%s'. "
3558 "Original exception was: %s"
3559 % (mapper, mapper._configure_failed)
3560 )
3561 e._configure_failed = mapper._configure_failed
3562 raise e
3564 if not mapper.configured:
3565 try:
3566 mapper._post_configure_properties()
3567 mapper._expire_memoizations()
3568 mapper.dispatch.mapper_configured(mapper, mapper.class_)
3569 except Exception:
3570 exc = sys.exc_info()[1]
3571 if not hasattr(exc, "_configure_failed"):
3572 mapper._configure_failed = exc
3573 raise
3574 if not has_skip:
3575 reg._new_mappers = False
3577 if not cascade and reg._dependencies.difference(orig):
3578 raise sa_exc.InvalidRequestError(
3579 "configure was called with cascade=False but "
3580 "additional registries remain"
3581 )
3584@util.preload_module("sqlalchemy.orm.decl_api")
3585def _dispose_registries(registries, cascade):
3587 registry = util.preloaded.orm_decl_api.registry
3589 orig = set(registries)
3591 for reg in registry._recurse_with_dependents(registries):
3592 if not cascade and reg._dependents.difference(orig):
3593 raise sa_exc.InvalidRequestError(
3594 "Registry has dependent registries that are not disposed; "
3595 "pass cascade=True to clear these also"
3596 )
3598 while reg._managers:
3599 try:
3600 manager, _ = reg._managers.popitem()
3601 except KeyError:
3602 # guard against race between while and popitem
3603 pass
3604 else:
3605 reg._dispose_manager_and_mapper(manager)
3607 reg._non_primary_mappers.clear()
3608 reg._dependents.clear()
3609 for dep in reg._dependencies:
3610 dep._dependents.discard(reg)
3611 reg._dependencies.clear()
3612 # this wasn't done in the 1.3 clear_mappers() and in fact it
3613 # was a bug, as it could cause configure_mappers() to invoke
3614 # the "before_configured" event even though mappers had all been
3615 # disposed.
3616 reg._new_mappers = False
3619def reconstructor(fn):
3620 """Decorate a method as the 'reconstructor' hook.
3622 Designates a single method as the "reconstructor", an ``__init__``-like
3623 method that will be called by the ORM after the instance has been
3624 loaded from the database or otherwise reconstituted.
3626 .. tip::
3628 The :func:`_orm.reconstructor` decorator makes use of the
3629 :meth:`_orm.InstanceEvents.load` event hook, which can be
3630 used directly.
3632 The reconstructor will be invoked with no arguments. Scalar
3633 (non-collection) database-mapped attributes of the instance will
3634 be available for use within the function. Eagerly-loaded
3635 collections are generally not yet available and will usually only
3636 contain the first element. ORM state changes made to objects at
3637 this stage will not be recorded for the next flush() operation, so
3638 the activity within a reconstructor should be conservative.
3640 .. seealso::
3642 :meth:`.InstanceEvents.load`
3644 """
3645 fn.__sa_reconstructor__ = True
3646 return fn
3649def validates(*names, **kw):
3650 r"""Decorate a method as a 'validator' for one or more named properties.
3652 Designates a method as a validator, a method which receives the
3653 name of the attribute as well as a value to be assigned, or in the
3654 case of a collection, the value to be added to the collection.
3655 The function can then raise validation exceptions to halt the
3656 process from continuing (where Python's built-in ``ValueError``
3657 and ``AssertionError`` exceptions are reasonable choices), or can
3658 modify or replace the value before proceeding. The function should
3659 otherwise return the given value.
3661 Note that a validator for a collection **cannot** issue a load of that
3662 collection within the validation routine - this usage raises
3663 an assertion to avoid recursion overflows. This is a reentrant
3664 condition which is not supported.
3666 :param \*names: list of attribute names to be validated.
3667 :param include_removes: if True, "remove" events will be
3668 sent as well - the validation function must accept an additional
3669 argument "is_remove" which will be a boolean.
3671 :param include_backrefs: defaults to ``True``; if ``False``, the
3672 validation function will not emit if the originator is an attribute
3673 event related via a backref. This can be used for bi-directional
3674 :func:`.validates` usage where only one validator should emit per
3675 attribute operation.
3677 .. versionadded:: 0.9.0
3679 .. seealso::
3681 :ref:`simple_validators` - usage examples for :func:`.validates`
3683 """
3684 include_removes = kw.pop("include_removes", False)
3685 include_backrefs = kw.pop("include_backrefs", True)
3687 def wrap(fn):
3688 fn.__sa_validators__ = names
3689 fn.__sa_validation_opts__ = {
3690 "include_removes": include_removes,
3691 "include_backrefs": include_backrefs,
3692 }
3693 return fn
3695 return wrap
3698def _event_on_load(state, ctx):
3699 instrumenting_mapper = state.manager.mapper
3701 if instrumenting_mapper._reconstructor:
3702 instrumenting_mapper._reconstructor(state.obj())
3705def _event_on_init(state, args, kwargs):
3706 """Run init_instance hooks.
3708 This also includes mapper compilation, normally not needed
3709 here but helps with some piecemeal configuration
3710 scenarios (such as in the ORM tutorial).
3712 """
3714 instrumenting_mapper = state.manager.mapper
3715 if instrumenting_mapper:
3716 instrumenting_mapper._check_configure()
3717 if instrumenting_mapper._set_polymorphic_identity:
3718 instrumenting_mapper._set_polymorphic_identity(state)
3721class _ColumnMapping(dict):
3722 """Error reporting helper for mapper._columntoproperty."""
3724 __slots__ = ("mapper",)
3726 def __init__(self, mapper):
3727 # TODO: weakref would be a good idea here
3728 self.mapper = mapper
3730 def __missing__(self, column):
3731 prop = self.mapper._props.get(column)
3732 if prop:
3733 raise orm_exc.UnmappedColumnError(
3734 "Column '%s.%s' is not available, due to "
3735 "conflicting property '%s':%r"
3736 % (column.table.name, column.name, column.key, prop)
3737 )
3738 raise orm_exc.UnmappedColumnError(
3739 "No column %s is configured on mapper %s..."
3740 % (column, self.mapper)
3741 )