Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/sqlalchemy/orm/mapper.py : 53%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# orm/mapper.py
2# Copyright (C) 2005-2020 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: http://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 types
23import weakref
25from . import attributes
26from . import exc as orm_exc
27from . import instrumentation
28from . import loading
29from . import properties
30from . import util as orm_util
31from .base import _class_to_mapper
32from .base import _INSTRUMENTOR
33from .base import _state_mapper
34from .base import class_mapper
35from .base import state_str
36from .interfaces import _MappedAttribute
37from .interfaces import EXT_SKIP
38from .interfaces import InspectionAttr
39from .interfaces import MapperProperty
40from .path_registry import PathRegistry
41from .. import event
42from .. import exc as sa_exc
43from .. import inspection
44from .. import log
45from .. import schema
46from .. import sql
47from .. import util
48from ..sql import expression
49from ..sql import operators
50from ..sql import util as sql_util
51from ..sql import visitors
54_mapper_registry = weakref.WeakKeyDictionary()
55_already_compiling = False
57_memoized_configured_property = util.group_expirable_memoized_property()
60# a constant returned by _get_attr_by_column to indicate
61# this mapper is not handling an attribute for a particular
62# column
63NO_ATTRIBUTE = util.symbol("NO_ATTRIBUTE")
65# lock used to synchronize the "mapper configure" step
66_CONFIGURE_MUTEX = util.threading.RLock()
69@inspection._self_inspects
70@log.class_logger
71class Mapper(InspectionAttr):
72 """Define the correlation of class attributes to database table
73 columns.
75 The :class:`_orm.Mapper` object is instantiated using the
76 :func:`~sqlalchemy.orm.mapper` function. For information
77 about instantiating new :class:`_orm.Mapper` objects, see
78 that function's documentation.
81 When :func:`.mapper` is used
82 explicitly to link a user defined class with table
83 metadata, this is referred to as *classical mapping*.
84 Modern SQLAlchemy usage tends to favor the
85 :mod:`sqlalchemy.ext.declarative` extension for class
86 configuration, which
87 makes usage of :func:`.mapper` behind the scenes.
89 Given a particular class known to be mapped by the ORM,
90 the :class:`_orm.Mapper` which maintains it can be acquired
91 using the :func:`_sa.inspect` function::
93 from sqlalchemy import inspect
95 mapper = inspect(MyClass)
97 A class which was mapped by the :mod:`sqlalchemy.ext.declarative`
98 extension will also have its mapper available via the ``__mapper__``
99 attribute.
102 """
104 _new_mappers = False
105 _dispose_called = False
107 @util.deprecated_params(
108 extension=(
109 "0.7",
110 ":class:`.MapperExtension` is deprecated in favor of the "
111 ":class:`.MapperEvents` listener interface. The "
112 ":paramref:`.mapper.extension` parameter will be "
113 "removed in a future release.",
114 ),
115 order_by=(
116 "1.1",
117 "The :paramref:`.mapper.order_by` parameter "
118 "is deprecated, and will be removed in a future release. "
119 "Use :meth:`_query.Query.order_by` "
120 "to determine the ordering of a "
121 "result set.",
122 ),
123 non_primary=(
124 "1.3",
125 "The :paramref:`.mapper.non_primary` parameter is deprecated, "
126 "and will be removed in a future release. The functionality "
127 "of non primary mappers is now better suited using the "
128 ":class:`.AliasedClass` construct, which can also be used "
129 "as the target of a :func:`_orm.relationship` in 1.3.",
130 ),
131 )
132 def __init__(
133 self,
134 class_,
135 local_table=None,
136 properties=None,
137 primary_key=None,
138 non_primary=False,
139 inherits=None,
140 inherit_condition=None,
141 inherit_foreign_keys=None,
142 extension=None,
143 order_by=False,
144 always_refresh=False,
145 version_id_col=None,
146 version_id_generator=None,
147 polymorphic_on=None,
148 _polymorphic_map=None,
149 polymorphic_identity=None,
150 concrete=False,
151 with_polymorphic=None,
152 polymorphic_load=None,
153 allow_partial_pks=True,
154 batch=True,
155 column_prefix=None,
156 include_properties=None,
157 exclude_properties=None,
158 passive_updates=True,
159 passive_deletes=False,
160 confirm_deleted_rows=True,
161 eager_defaults=False,
162 legacy_is_orphan=False,
163 _compiled_cache_size=100,
164 ):
165 r"""Return a new :class:`_orm.Mapper` object.
167 This function is typically used behind the scenes
168 via the Declarative extension. When using Declarative,
169 many of the usual :func:`.mapper` arguments are handled
170 by the Declarative extension itself, including ``class_``,
171 ``local_table``, ``properties``, and ``inherits``.
172 Other options are passed to :func:`.mapper` using
173 the ``__mapper_args__`` class variable::
175 class MyClass(Base):
176 __tablename__ = 'my_table'
177 id = Column(Integer, primary_key=True)
178 type = Column(String(50))
179 alt = Column("some_alt", Integer)
181 __mapper_args__ = {
182 'polymorphic_on' : type
183 }
186 Explicit use of :func:`.mapper`
187 is often referred to as *classical mapping*. The above
188 declarative example is equivalent in classical form to::
190 my_table = Table("my_table", metadata,
191 Column('id', Integer, primary_key=True),
192 Column('type', String(50)),
193 Column("some_alt", Integer)
194 )
196 class MyClass(object):
197 pass
199 mapper(MyClass, my_table,
200 polymorphic_on=my_table.c.type,
201 properties={
202 'alt':my_table.c.some_alt
203 })
205 .. seealso::
207 :ref:`classical_mapping` - discussion of direct usage of
208 :func:`.mapper`
210 :param class\_: The class to be mapped. When using Declarative,
211 this argument is automatically passed as the declared class
212 itself.
214 :param local_table: The :class:`_schema.Table` or other selectable
215 to which the class is mapped. May be ``None`` if
216 this mapper inherits from another mapper using single-table
217 inheritance. When using Declarative, this argument is
218 automatically passed by the extension, based on what
219 is configured via the ``__table__`` argument or via the
220 :class:`_schema.Table`
221 produced as a result of the ``__tablename__``
222 and :class:`_schema.Column` arguments present.
224 :param always_refresh: If True, all query operations for this mapped
225 class will overwrite all data within object instances that already
226 exist within the session, erasing any in-memory changes with
227 whatever information was loaded from the database. Usage of this
228 flag is highly discouraged; as an alternative, see the method
229 :meth:`_query.Query.populate_existing`.
231 :param allow_partial_pks: Defaults to True. Indicates that a
232 composite primary key with some NULL values should be considered as
233 possibly existing within the database. This affects whether a
234 mapper will assign an incoming row to an existing identity, as well
235 as if :meth:`.Session.merge` will check the database first for a
236 particular primary key value. A "partial primary key" can occur if
237 one has mapped to an OUTER JOIN, for example.
239 :param batch: Defaults to ``True``, indicating that save operations
240 of multiple entities can be batched together for efficiency.
241 Setting to False indicates
242 that an instance will be fully saved before saving the next
243 instance. This is used in the extremely rare case that a
244 :class:`.MapperEvents` listener requires being called
245 in between individual row persistence operations.
247 :param column_prefix: A string which will be prepended
248 to the mapped attribute name when :class:`_schema.Column`
249 objects are automatically assigned as attributes to the
250 mapped class. Does not affect explicitly specified
251 column-based properties.
253 See the section :ref:`column_prefix` for an example.
255 :param concrete: If True, indicates this mapper should use concrete
256 table inheritance with its parent mapper.
258 See the section :ref:`concrete_inheritance` for an example.
260 :param confirm_deleted_rows: defaults to True; when a DELETE occurs
261 of one more rows based on specific primary keys, a warning is
262 emitted when the number of rows matched does not equal the number
263 of rows expected. This parameter may be set to False to handle the
264 case where database ON DELETE CASCADE rules may be deleting some of
265 those rows automatically. The warning may be changed to an
266 exception in a future release.
268 .. versionadded:: 0.9.4 - added
269 :paramref:`.mapper.confirm_deleted_rows` as well as conditional
270 matched row checking on delete.
272 :param eager_defaults: if True, the ORM will immediately fetch the
273 value of server-generated default values after an INSERT or UPDATE,
274 rather than leaving them as expired to be fetched on next access.
275 This can be used for event schemes where the server-generated values
276 are needed immediately before the flush completes. By default,
277 this scheme will emit an individual ``SELECT`` statement per row
278 inserted or updated, which note can add significant performance
279 overhead. However, if the
280 target database supports :term:`RETURNING`, the default values will
281 be returned inline with the INSERT or UPDATE statement, which can
282 greatly enhance performance for an application that needs frequent
283 access to just-generated server defaults.
285 .. seealso::
287 :ref:`orm_server_defaults`
289 .. versionchanged:: 0.9.0 The ``eager_defaults`` option can now
290 make use of :term:`RETURNING` for backends which support it.
292 :param exclude_properties: A list or set of string column names to
293 be excluded from mapping.
295 See :ref:`include_exclude_cols` for an example.
297 :param extension: A :class:`.MapperExtension` instance or
298 list of :class:`.MapperExtension` instances which will be applied
299 to all operations by this :class:`_orm.Mapper`.
301 :param include_properties: An inclusive list or set of string column
302 names to map.
304 See :ref:`include_exclude_cols` for an example.
306 :param inherits: A mapped class or the corresponding
307 :class:`_orm.Mapper`
308 of one indicating a superclass to which this :class:`_orm.Mapper`
309 should *inherit* from. The mapped class here must be a subclass
310 of the other mapper's class. When using Declarative, this argument
311 is passed automatically as a result of the natural class
312 hierarchy of the declared classes.
314 .. seealso::
316 :ref:`inheritance_toplevel`
318 :param inherit_condition: For joined table inheritance, a SQL
319 expression which will
320 define how the two tables are joined; defaults to a natural join
321 between the two tables.
323 :param inherit_foreign_keys: When ``inherit_condition`` is used and
324 the columns present are missing a :class:`_schema.ForeignKey`
325 configuration, this parameter can be used to specify which columns
326 are "foreign". In most cases can be left as ``None``.
328 :param legacy_is_orphan: Boolean, defaults to ``False``.
329 When ``True``, specifies that "legacy" orphan consideration
330 is to be applied to objects mapped by this mapper, which means
331 that a pending (that is, not persistent) object is auto-expunged
332 from an owning :class:`.Session` only when it is de-associated
333 from *all* parents that specify a ``delete-orphan`` cascade towards
334 this mapper. The new default behavior is that the object is
335 auto-expunged when it is de-associated with *any* of its parents
336 that specify ``delete-orphan`` cascade. This behavior is more
337 consistent with that of a persistent object, and allows behavior to
338 be consistent in more scenarios independently of whether or not an
339 orphanable object has been flushed yet or not.
341 See the change note and example at :ref:`legacy_is_orphan_addition`
342 for more detail on this change.
344 :param non_primary: Specify that this :class:`_orm.Mapper`
345 is in addition
346 to the "primary" mapper, that is, the one used for persistence.
347 The :class:`_orm.Mapper` created here may be used for ad-hoc
348 mapping of the class to an alternate selectable, for loading
349 only.
351 :paramref:`_orm.Mapper.non_primary` is not an often used option, but
352 is useful in some specific :func:`_orm.relationship` cases.
354 .. seealso::
356 :ref:`relationship_non_primary_mapper`
358 :param order_by: A single :class:`_schema.Column` or list of
359 :class:`_schema.Column`
360 objects for which selection operations should use as the default
361 ordering for entities. By default mappers have no pre-defined
362 ordering.
364 :param passive_deletes: Indicates DELETE behavior of foreign key
365 columns when a joined-table inheritance entity is being deleted.
366 Defaults to ``False`` for a base mapper; for an inheriting mapper,
367 defaults to ``False`` unless the value is set to ``True``
368 on the superclass mapper.
370 When ``True``, it is assumed that ON DELETE CASCADE is configured
371 on the foreign key relationships that link this mapper's table
372 to its superclass table, so that when the unit of work attempts
373 to delete the entity, it need only emit a DELETE statement for the
374 superclass table, and not this table.
376 When ``False``, a DELETE statement is emitted for this mapper's
377 table individually. If the primary key attributes local to this
378 table are unloaded, then a SELECT must be emitted in order to
379 validate these attributes; note that the primary key columns
380 of a joined-table subclass are not part of the "primary key" of
381 the object as a whole.
383 Note that a value of ``True`` is **always** forced onto the
384 subclass mappers; that is, it's not possible for a superclass
385 to specify passive_deletes without this taking effect for
386 all subclass mappers.
388 .. versionadded:: 1.1
390 .. seealso::
392 :ref:`passive_deletes` - description of similar feature as
393 used with :func:`_orm.relationship`
395 :paramref:`.mapper.passive_updates` - supporting ON UPDATE
396 CASCADE for joined-table inheritance mappers
398 :param passive_updates: Indicates UPDATE behavior of foreign key
399 columns when a primary key column changes on a joined-table
400 inheritance mapping. Defaults to ``True``.
402 When True, it is assumed that ON UPDATE CASCADE is configured on
403 the foreign key in the database, and that the database will handle
404 propagation of an UPDATE from a source column to dependent columns
405 on joined-table rows.
407 When False, it is assumed that the database does not enforce
408 referential integrity and will not be issuing its own CASCADE
409 operation for an update. The unit of work process will
410 emit an UPDATE statement for the dependent columns during a
411 primary key change.
413 .. seealso::
415 :ref:`passive_updates` - description of a similar feature as
416 used with :func:`_orm.relationship`
418 :paramref:`.mapper.passive_deletes` - supporting ON DELETE
419 CASCADE for joined-table inheritance mappers
421 :param polymorphic_load: Specifies "polymorphic loading" behavior
422 for a subclass in an inheritance hierarchy (joined and single
423 table inheritance only). Valid values are:
425 * "'inline'" - specifies this class should be part of the
426 "with_polymorphic" mappers, e.g. its columns will be included
427 in a SELECT query against the base.
429 * "'selectin'" - specifies that when instances of this class
430 are loaded, an additional SELECT will be emitted to retrieve
431 the columns specific to this subclass. The SELECT uses
432 IN to fetch multiple subclasses at once.
434 .. versionadded:: 1.2
436 .. seealso::
438 :ref:`with_polymorphic_mapper_config`
440 :ref:`polymorphic_selectin`
442 :param polymorphic_on: Specifies the column, attribute, or
443 SQL expression used to determine the target class for an
444 incoming row, when inheriting classes are present.
446 This value is commonly a :class:`_schema.Column` object that's
447 present in the mapped :class:`_schema.Table`::
449 class Employee(Base):
450 __tablename__ = 'employee'
452 id = Column(Integer, primary_key=True)
453 discriminator = Column(String(50))
455 __mapper_args__ = {
456 "polymorphic_on":discriminator,
457 "polymorphic_identity":"employee"
458 }
460 It may also be specified
461 as a SQL expression, as in this example where we
462 use the :func:`.case` construct to provide a conditional
463 approach::
465 class Employee(Base):
466 __tablename__ = 'employee'
468 id = Column(Integer, primary_key=True)
469 discriminator = Column(String(50))
471 __mapper_args__ = {
472 "polymorphic_on":case([
473 (discriminator == "EN", "engineer"),
474 (discriminator == "MA", "manager"),
475 ], else_="employee"),
476 "polymorphic_identity":"employee"
477 }
479 It may also refer to any attribute
480 configured with :func:`.column_property`, or to the
481 string name of one::
483 class Employee(Base):
484 __tablename__ = 'employee'
486 id = Column(Integer, primary_key=True)
487 discriminator = Column(String(50))
488 employee_type = column_property(
489 case([
490 (discriminator == "EN", "engineer"),
491 (discriminator == "MA", "manager"),
492 ], else_="employee")
493 )
495 __mapper_args__ = {
496 "polymorphic_on":employee_type,
497 "polymorphic_identity":"employee"
498 }
500 When setting ``polymorphic_on`` to reference an
501 attribute or expression that's not present in the
502 locally mapped :class:`_schema.Table`, yet the value
503 of the discriminator should be persisted to the database,
504 the value of the
505 discriminator is not automatically set on new
506 instances; this must be handled by the user,
507 either through manual means or via event listeners.
508 A typical approach to establishing such a listener
509 looks like::
511 from sqlalchemy import event
512 from sqlalchemy.orm import object_mapper
514 @event.listens_for(Employee, "init", propagate=True)
515 def set_identity(instance, *arg, **kw):
516 mapper = object_mapper(instance)
517 instance.discriminator = mapper.polymorphic_identity
519 Where above, we assign the value of ``polymorphic_identity``
520 for the mapped class to the ``discriminator`` attribute,
521 thus persisting the value to the ``discriminator`` column
522 in the database.
524 .. warning::
526 Currently, **only one discriminator column may be set**, typically
527 on the base-most class in the hierarchy. "Cascading" polymorphic
528 columns are not yet supported.
530 .. seealso::
532 :ref:`inheritance_toplevel`
534 :param polymorphic_identity: Specifies the value which
535 identifies this particular class as returned by the
536 column expression referred to by the ``polymorphic_on``
537 setting. As rows are received, the value corresponding
538 to the ``polymorphic_on`` column expression is compared
539 to this value, indicating which subclass should
540 be used for the newly reconstructed object.
542 :param properties: A dictionary mapping the string names of object
543 attributes to :class:`.MapperProperty` instances, which define the
544 persistence behavior of that attribute. Note that
545 :class:`_schema.Column`
546 objects present in
547 the mapped :class:`_schema.Table` are automatically placed into
548 ``ColumnProperty`` instances upon mapping, unless overridden.
549 When using Declarative, this argument is passed automatically,
550 based on all those :class:`.MapperProperty` instances declared
551 in the declared class body.
553 :param primary_key: A list of :class:`_schema.Column`
554 objects which define
555 the primary key to be used against this mapper's selectable unit.
556 This is normally simply the primary key of the ``local_table``, but
557 can be overridden here.
559 :param version_id_col: A :class:`_schema.Column`
560 that will be used to keep a running version id of rows
561 in the table. This is used to detect concurrent updates or
562 the presence of stale data in a flush. The methodology is to
563 detect if an UPDATE statement does not match the last known
564 version id, a
565 :class:`~sqlalchemy.orm.exc.StaleDataError` exception is
566 thrown.
567 By default, the column must be of :class:`.Integer` type,
568 unless ``version_id_generator`` specifies an alternative version
569 generator.
571 .. seealso::
573 :ref:`mapper_version_counter` - discussion of version counting
574 and rationale.
576 :param version_id_generator: Define how new version ids should
577 be generated. Defaults to ``None``, which indicates that
578 a simple integer counting scheme be employed. To provide a custom
579 versioning scheme, provide a callable function of the form::
581 def generate_version(version):
582 return next_version
584 Alternatively, server-side versioning functions such as triggers,
585 or programmatic versioning schemes outside of the version id
586 generator may be used, by specifying the value ``False``.
587 Please see :ref:`server_side_version_counter` for a discussion
588 of important points when using this option.
590 .. versionadded:: 0.9.0 ``version_id_generator`` supports
591 server-side version number generation.
593 .. seealso::
595 :ref:`custom_version_counter`
597 :ref:`server_side_version_counter`
600 :param with_polymorphic: A tuple in the form ``(<classes>,
601 <selectable>)`` indicating the default style of "polymorphic"
602 loading, that is, which tables are queried at once. <classes> is
603 any single or list of mappers and/or classes indicating the
604 inherited classes that should be loaded at once. The special value
605 ``'*'`` may be used to indicate all descending classes should be
606 loaded immediately. The second tuple argument <selectable>
607 indicates a selectable that will be used to query for multiple
608 classes.
610 .. seealso::
612 :ref:`with_polymorphic` - discussion of polymorphic querying
613 techniques.
615 """
617 self.class_ = util.assert_arg_type(class_, type, "class_")
619 self.class_manager = None
621 self._primary_key_argument = util.to_list(primary_key)
622 self.non_primary = non_primary
624 if order_by is not False:
625 self.order_by = util.to_list(order_by)
626 else:
627 self.order_by = order_by
629 self.always_refresh = always_refresh
631 if isinstance(version_id_col, MapperProperty):
632 self.version_id_prop = version_id_col
633 self.version_id_col = None
634 else:
635 self.version_id_col = version_id_col
636 if version_id_generator is False:
637 self.version_id_generator = False
638 elif version_id_generator is None:
639 self.version_id_generator = lambda x: (x or 0) + 1
640 else:
641 self.version_id_generator = version_id_generator
643 self.concrete = concrete
644 self.single = False
645 self.inherits = inherits
646 self.local_table = local_table
647 self.inherit_condition = inherit_condition
648 self.inherit_foreign_keys = inherit_foreign_keys
649 self._init_properties = properties or {}
650 self._delete_orphans = []
651 self.batch = batch
652 self.eager_defaults = eager_defaults
653 self.column_prefix = column_prefix
654 self.polymorphic_on = expression._clause_element_as_expr(
655 polymorphic_on
656 )
657 self._dependency_processors = []
658 self.validators = util.immutabledict()
659 self.passive_updates = passive_updates
660 self.passive_deletes = passive_deletes
661 self.legacy_is_orphan = legacy_is_orphan
662 self._clause_adapter = None
663 self._requires_row_aliasing = False
664 self._inherits_equated_pairs = None
665 self._memoized_values = {}
666 self._compiled_cache_size = _compiled_cache_size
667 self._reconstructor = None
668 self._deprecated_extensions = util.to_list(extension or [])
669 self.allow_partial_pks = allow_partial_pks
671 if self.inherits and not self.concrete:
672 self.confirm_deleted_rows = False
673 else:
674 self.confirm_deleted_rows = confirm_deleted_rows
676 if isinstance(self.local_table, expression.SelectBase):
677 raise sa_exc.InvalidRequestError(
678 "When mapping against a select() construct, map against "
679 "an alias() of the construct instead."
680 "This because several databases don't allow a "
681 "SELECT from a subquery that does not have an alias."
682 )
684 self._set_with_polymorphic(with_polymorphic)
685 self.polymorphic_load = polymorphic_load
687 # our 'polymorphic identity', a string name that when located in a
688 # result set row indicates this Mapper should be used to construct
689 # the object instance for that row.
690 self.polymorphic_identity = polymorphic_identity
692 # a dictionary of 'polymorphic identity' names, associating those
693 # names with Mappers that will be used to construct object instances
694 # upon a select operation.
695 if _polymorphic_map is None:
696 self.polymorphic_map = {}
697 else:
698 self.polymorphic_map = _polymorphic_map
700 if include_properties is not None:
701 self.include_properties = util.to_set(include_properties)
702 else:
703 self.include_properties = None
704 if exclude_properties:
705 self.exclude_properties = util.to_set(exclude_properties)
706 else:
707 self.exclude_properties = None
709 self.configured = False
711 # prevent this mapper from being constructed
712 # while a configure_mappers() is occurring (and defer a
713 # configure_mappers() until construction succeeds)
714 _CONFIGURE_MUTEX.acquire()
715 try:
716 self.dispatch._events._new_mapper_instance(class_, self)
717 self._configure_inheritance()
718 self._configure_legacy_instrument_class()
719 self._configure_class_instrumentation()
720 self._configure_listeners()
721 self._configure_properties()
722 self._configure_polymorphic_setter()
723 self._configure_pks()
724 Mapper._new_mappers = True
725 self._log("constructed")
726 self._expire_memoizations()
727 finally:
728 _CONFIGURE_MUTEX.release()
730 # major attributes initialized at the classlevel so that
731 # they can be Sphinx-documented.
733 is_mapper = True
734 """Part of the inspection API."""
736 represents_outer_join = False
738 @property
739 def mapper(self):
740 """Part of the inspection API.
742 Returns self.
744 """
745 return self
747 @property
748 def entity(self):
749 r"""Part of the inspection API.
751 Returns self.class\_.
753 """
754 return self.class_
756 local_table = None
757 """The :class:`expression.Selectable` which this :class:`_orm.Mapper`
758 manages.
760 Typically is an instance of :class:`_schema.Table` or
761 :class:`_expression.Alias`.
762 May also be ``None``.
764 The "local" table is the
765 selectable that the :class:`_orm.Mapper` is directly responsible for
766 managing from an attribute access and flush perspective. For
767 non-inheriting mappers, the local table is the same as the
768 "mapped" table. For joined-table inheritance mappers, local_table
769 will be the particular sub-table of the overall "join" which
770 this :class:`_orm.Mapper` represents. If this mapper is a
771 single-table inheriting mapper, local_table will be ``None``.
773 .. seealso::
775 :attr:`_orm.Mapper.persist_selectable`.
777 """
779 persist_selectable = None
780 """The :class:`expression.Selectable` to which this :class:`_orm.Mapper`
781 is mapped.
783 Typically an instance of :class:`_schema.Table`, :class:`_expression.Join`
784 , or
785 :class:`_expression.Alias`.
787 The :attr:`_orm.Mapper.persist_selectable` is separate from
788 :attr:`_orm.Mapper.selectable` in that the former represents columns
789 that are mapped on this class or its superclasses, whereas the
790 latter may be a "polymorphic" selectable that contains additional columns
791 which are in fact mapped on subclasses only.
793 "persist selectable" is the "thing the mapper writes to" and
794 "selectable" is the "thing the mapper selects from".
796 :attr:`_orm.Mapper.persist_selectable` is also separate from
797 :attr:`_orm.Mapper.local_table`, which represents the set of columns that
798 are locally mapped on this class directly.
801 .. seealso::
803 :attr:`_orm.Mapper.selectable`.
805 :attr:`_orm.Mapper.local_table`.
807 """
809 inherits = None
810 """References the :class:`_orm.Mapper` which this :class:`_orm.Mapper`
811 inherits from, if any.
813 This is a *read only* attribute determined during mapper construction.
814 Behavior is undefined if directly modified.
816 """
818 configured = None
819 """Represent ``True`` if this :class:`_orm.Mapper` has been configured.
821 This is a *read only* attribute determined during mapper construction.
822 Behavior is undefined if directly modified.
824 .. seealso::
826 :func:`.configure_mappers`.
828 """
830 concrete = None
831 """Represent ``True`` if this :class:`_orm.Mapper` is a concrete
832 inheritance mapper.
834 This is a *read only* attribute determined during mapper construction.
835 Behavior is undefined if directly modified.
837 """
839 tables = None
840 """An iterable containing the collection of :class:`_schema.Table` objects
841 which this :class:`_orm.Mapper` is aware of.
843 If the mapper is mapped to a :class:`_expression.Join`, or an
844 :class:`_expression.Alias`
845 representing a :class:`_expression.Select`, the individual
846 :class:`_schema.Table`
847 objects that comprise the full construct will be represented here.
849 This is a *read only* attribute determined during mapper construction.
850 Behavior is undefined if directly modified.
852 """
854 primary_key = None
855 """An iterable containing the collection of :class:`_schema.Column`
856 objects
857 which comprise the 'primary key' of the mapped table, from the
858 perspective of this :class:`_orm.Mapper`.
860 This list is against the selectable in
861 :attr:`_orm.Mapper.persist_selectable`.
862 In the case of inheriting mappers, some columns may be managed by a
863 superclass mapper. For example, in the case of a
864 :class:`_expression.Join`, the
865 primary key is determined by all of the primary key columns across all
866 tables referenced by the :class:`_expression.Join`.
868 The list is also not necessarily the same as the primary key column
869 collection associated with the underlying tables; the :class:`_orm.Mapper`
870 features a ``primary_key`` argument that can override what the
871 :class:`_orm.Mapper` considers as primary key columns.
873 This is a *read only* attribute determined during mapper construction.
874 Behavior is undefined if directly modified.
876 """
878 class_ = None
879 """The Python class which this :class:`_orm.Mapper` maps.
881 This is a *read only* attribute determined during mapper construction.
882 Behavior is undefined if directly modified.
884 """
886 class_manager = None
887 """The :class:`.ClassManager` which maintains event listeners
888 and class-bound descriptors for this :class:`_orm.Mapper`.
890 This is a *read only* attribute determined during mapper construction.
891 Behavior is undefined if directly modified.
893 """
895 single = None
896 """Represent ``True`` if this :class:`_orm.Mapper` is a single table
897 inheritance mapper.
899 :attr:`_orm.Mapper.local_table` will be ``None`` if this flag is set.
901 This is a *read only* attribute determined during mapper construction.
902 Behavior is undefined if directly modified.
904 """
906 non_primary = None
907 """Represent ``True`` if this :class:`_orm.Mapper` is a "non-primary"
908 mapper, e.g. a mapper that is used only to select rows but not for
909 persistence management.
911 This is a *read only* attribute determined during mapper construction.
912 Behavior is undefined if directly modified.
914 """
916 polymorphic_on = None
917 """The :class:`_schema.Column` or SQL expression specified as the
918 ``polymorphic_on`` argument
919 for this :class:`_orm.Mapper`, within an inheritance scenario.
921 This attribute is normally a :class:`_schema.Column` instance but
922 may also be an expression, such as one derived from
923 :func:`.cast`.
925 This is a *read only* attribute determined during mapper construction.
926 Behavior is undefined if directly modified.
928 """
930 polymorphic_map = None
931 """A mapping of "polymorphic identity" identifiers mapped to
932 :class:`_orm.Mapper` instances, within an inheritance scenario.
934 The identifiers can be of any type which is comparable to the
935 type of column represented by :attr:`_orm.Mapper.polymorphic_on`.
937 An inheritance chain of mappers will all reference the same
938 polymorphic map object. The object is used to correlate incoming
939 result rows to target mappers.
941 This is a *read only* attribute determined during mapper construction.
942 Behavior is undefined if directly modified.
944 """
946 polymorphic_identity = None
947 """Represent an identifier which is matched against the
948 :attr:`_orm.Mapper.polymorphic_on` column during result row loading.
950 Used only with inheritance, this object can be of any type which is
951 comparable to the type of column represented by
952 :attr:`_orm.Mapper.polymorphic_on`.
954 This is a *read only* attribute determined during mapper construction.
955 Behavior is undefined if directly modified.
957 """
959 base_mapper = None
960 """The base-most :class:`_orm.Mapper` in an inheritance chain.
962 In a non-inheriting scenario, this attribute will always be this
963 :class:`_orm.Mapper`. In an inheritance scenario, it references
964 the :class:`_orm.Mapper` which is parent to all other :class:`_orm.Mapper`
965 objects in the inheritance chain.
967 This is a *read only* attribute determined during mapper construction.
968 Behavior is undefined if directly modified.
970 """
972 columns = None
973 """A collection of :class:`_schema.Column` or other scalar expression
974 objects maintained by this :class:`_orm.Mapper`.
976 The collection behaves the same as that of the ``c`` attribute on
977 any :class:`_schema.Table` object,
978 except that only those columns included in
979 this mapping are present, and are keyed based on the attribute name
980 defined in the mapping, not necessarily the ``key`` attribute of the
981 :class:`_schema.Column` itself. Additionally, scalar expressions mapped
982 by :func:`.column_property` are also present here.
984 This is a *read only* attribute determined during mapper construction.
985 Behavior is undefined if directly modified.
987 """
989 validators = None
990 """An immutable dictionary of attributes which have been decorated
991 using the :func:`_orm.validates` decorator.
993 The dictionary contains string attribute names as keys
994 mapped to the actual validation method.
996 """
998 c = None
999 """A synonym for :attr:`_orm.Mapper.columns`."""
1001 @property
1002 @util.deprecated("1.3", "Use .persist_selectable")
1003 def mapped_table(self):
1004 return self.persist_selectable
1006 @util.memoized_property
1007 def _path_registry(self):
1008 return PathRegistry.per_mapper(self)
1010 def _configure_inheritance(self):
1011 """Configure settings related to inheriting and/or inherited mappers
1012 being present."""
1014 # a set of all mappers which inherit from this one.
1015 self._inheriting_mappers = util.WeakSequence()
1017 if self.inherits:
1018 if isinstance(self.inherits, type):
1019 self.inherits = class_mapper(self.inherits, configure=False)
1020 if not issubclass(self.class_, self.inherits.class_):
1021 raise sa_exc.ArgumentError(
1022 "Class '%s' does not inherit from '%s'"
1023 % (self.class_.__name__, self.inherits.class_.__name__)
1024 )
1025 if self.non_primary != self.inherits.non_primary:
1026 np = not self.non_primary and "primary" or "non-primary"
1027 raise sa_exc.ArgumentError(
1028 "Inheritance of %s mapper for class '%s' is "
1029 "only allowed from a %s mapper"
1030 % (np, self.class_.__name__, np)
1031 )
1032 # inherit_condition is optional.
1033 if self.local_table is None:
1034 self.local_table = self.inherits.local_table
1035 self.persist_selectable = self.inherits.persist_selectable
1036 self.single = True
1037 elif self.local_table is not self.inherits.local_table:
1038 if self.concrete:
1039 self.persist_selectable = self.local_table
1040 for mapper in self.iterate_to_root():
1041 if mapper.polymorphic_on is not None:
1042 mapper._requires_row_aliasing = True
1043 else:
1044 if self.inherit_condition is None:
1045 # figure out inherit condition from our table to the
1046 # immediate table of the inherited mapper, not its
1047 # full table which could pull in other stuff we don't
1048 # want (allows test/inheritance.InheritTest4 to pass)
1049 self.inherit_condition = sql_util.join_condition(
1050 self.inherits.local_table, self.local_table
1051 )
1052 self.persist_selectable = sql.join(
1053 self.inherits.persist_selectable,
1054 self.local_table,
1055 self.inherit_condition,
1056 )
1058 fks = util.to_set(self.inherit_foreign_keys)
1059 self._inherits_equated_pairs = sql_util.criterion_as_pairs(
1060 self.persist_selectable.onclause,
1061 consider_as_foreign_keys=fks,
1062 )
1063 else:
1064 self.persist_selectable = self.local_table
1066 if self.polymorphic_identity is not None and not self.concrete:
1067 self._identity_class = self.inherits._identity_class
1068 else:
1069 self._identity_class = self.class_
1071 if self.version_id_col is None:
1072 self.version_id_col = self.inherits.version_id_col
1073 self.version_id_generator = self.inherits.version_id_generator
1074 elif (
1075 self.inherits.version_id_col is not None
1076 and self.version_id_col is not self.inherits.version_id_col
1077 ):
1078 util.warn(
1079 "Inheriting version_id_col '%s' does not match inherited "
1080 "version_id_col '%s' and will not automatically populate "
1081 "the inherited versioning column. "
1082 "version_id_col should only be specified on "
1083 "the base-most mapper that includes versioning."
1084 % (
1085 self.version_id_col.description,
1086 self.inherits.version_id_col.description,
1087 )
1088 )
1090 if (
1091 self.order_by is False
1092 and not self.concrete
1093 and self.inherits.order_by is not False
1094 ):
1095 self.order_by = self.inherits.order_by
1097 self.polymorphic_map = self.inherits.polymorphic_map
1098 self.batch = self.inherits.batch
1099 self.inherits._inheriting_mappers.append(self)
1100 self.base_mapper = self.inherits.base_mapper
1101 self.passive_updates = self.inherits.passive_updates
1102 self.passive_deletes = (
1103 self.inherits.passive_deletes or self.passive_deletes
1104 )
1105 self._all_tables = self.inherits._all_tables
1107 if self.polymorphic_identity is not None:
1108 if self.polymorphic_identity in self.polymorphic_map:
1109 util.warn(
1110 "Reassigning polymorphic association for identity %r "
1111 "from %r to %r: Check for duplicate use of %r as "
1112 "value for polymorphic_identity."
1113 % (
1114 self.polymorphic_identity,
1115 self.polymorphic_map[self.polymorphic_identity],
1116 self,
1117 self.polymorphic_identity,
1118 )
1119 )
1120 self.polymorphic_map[self.polymorphic_identity] = self
1122 if self.polymorphic_load and self.concrete:
1123 raise sa_exc.ArgumentError(
1124 "polymorphic_load is not currently supported "
1125 "with concrete table inheritance"
1126 )
1127 if self.polymorphic_load == "inline":
1128 self.inherits._add_with_polymorphic_subclass(self)
1129 elif self.polymorphic_load == "selectin":
1130 pass
1131 elif self.polymorphic_load is not None:
1132 raise sa_exc.ArgumentError(
1133 "unknown argument for polymorphic_load: %r"
1134 % self.polymorphic_load
1135 )
1137 else:
1138 self._all_tables = set()
1139 self.base_mapper = self
1140 self.persist_selectable = self.local_table
1141 if self.polymorphic_identity is not None:
1142 self.polymorphic_map[self.polymorphic_identity] = self
1143 self._identity_class = self.class_
1145 if self.persist_selectable is None:
1146 raise sa_exc.ArgumentError(
1147 "Mapper '%s' does not have a persist_selectable specified."
1148 % self
1149 )
1151 def _set_with_polymorphic(self, with_polymorphic):
1152 if with_polymorphic == "*":
1153 self.with_polymorphic = ("*", None)
1154 elif isinstance(with_polymorphic, (tuple, list)):
1155 if isinstance(
1156 with_polymorphic[0], util.string_types + (tuple, list)
1157 ):
1158 self.with_polymorphic = with_polymorphic
1159 else:
1160 self.with_polymorphic = (with_polymorphic, None)
1161 elif with_polymorphic is not None:
1162 raise sa_exc.ArgumentError("Invalid setting for with_polymorphic")
1163 else:
1164 self.with_polymorphic = None
1166 if isinstance(self.local_table, expression.SelectBase):
1167 raise sa_exc.InvalidRequestError(
1168 "When mapping against a select() construct, map against "
1169 "an alias() of the construct instead."
1170 "This because several databases don't allow a "
1171 "SELECT from a subquery that does not have an alias."
1172 )
1174 if self.with_polymorphic and isinstance(
1175 self.with_polymorphic[1], expression.SelectBase
1176 ):
1177 self.with_polymorphic = (
1178 self.with_polymorphic[0],
1179 self.with_polymorphic[1].alias(),
1180 )
1182 if self.configured:
1183 self._expire_memoizations()
1185 def _add_with_polymorphic_subclass(self, mapper):
1186 subcl = mapper.class_
1187 if self.with_polymorphic is None:
1188 self._set_with_polymorphic((subcl,))
1189 elif self.with_polymorphic[0] != "*":
1190 self._set_with_polymorphic(
1191 (self.with_polymorphic[0] + (subcl,), self.with_polymorphic[1])
1192 )
1194 def _set_concrete_base(self, mapper):
1195 """Set the given :class:`_orm.Mapper` as the 'inherits' for this
1196 :class:`_orm.Mapper`, assuming this :class:`_orm.Mapper` is concrete
1197 and does not already have an inherits."""
1199 assert self.concrete
1200 assert not self.inherits
1201 assert isinstance(mapper, Mapper)
1202 self.inherits = mapper
1203 self.inherits.polymorphic_map.update(self.polymorphic_map)
1204 self.polymorphic_map = self.inherits.polymorphic_map
1205 for mapper in self.iterate_to_root():
1206 if mapper.polymorphic_on is not None:
1207 mapper._requires_row_aliasing = True
1208 self.batch = self.inherits.batch
1209 for mp in self.self_and_descendants:
1210 mp.base_mapper = self.inherits.base_mapper
1211 self.inherits._inheriting_mappers.append(self)
1212 self.passive_updates = self.inherits.passive_updates
1213 self._all_tables = self.inherits._all_tables
1215 for key, prop in mapper._props.items():
1216 if key not in self._props and not self._should_exclude(
1217 key, key, local=False, column=None
1218 ):
1219 self._adapt_inherited_property(key, prop, False)
1221 def _set_polymorphic_on(self, polymorphic_on):
1222 self.polymorphic_on = polymorphic_on
1223 self._configure_polymorphic_setter(True)
1225 def _configure_legacy_instrument_class(self):
1227 if self.inherits:
1228 self.dispatch._update(self.inherits.dispatch)
1229 super_extensions = set(
1230 chain(
1231 *[
1232 m._deprecated_extensions
1233 for m in self.inherits.iterate_to_root()
1234 ]
1235 )
1236 )
1237 else:
1238 super_extensions = set()
1240 for ext in self._deprecated_extensions:
1241 if ext not in super_extensions:
1242 ext._adapt_instrument_class(self, ext)
1244 def _configure_listeners(self):
1245 if self.inherits:
1246 super_extensions = set(
1247 chain(
1248 *[
1249 m._deprecated_extensions
1250 for m in self.inherits.iterate_to_root()
1251 ]
1252 )
1253 )
1254 else:
1255 super_extensions = set()
1257 for ext in self._deprecated_extensions:
1258 if ext not in super_extensions:
1259 ext._adapt_listener(self, ext)
1261 def _configure_class_instrumentation(self):
1262 """If this mapper is to be a primary mapper (i.e. the
1263 non_primary flag is not set), associate this Mapper with the
1264 given class and entity name.
1266 Subsequent calls to ``class_mapper()`` for the ``class_`` / ``entity``
1267 name combination will return this mapper. Also decorate the
1268 `__init__` method on the mapped class to include optional
1269 auto-session attachment logic.
1271 """
1273 manager = attributes.manager_of_class(self.class_)
1275 if self.non_primary:
1276 if not manager or not manager.is_mapped:
1277 raise sa_exc.InvalidRequestError(
1278 "Class %s has no primary mapper configured. Configure "
1279 "a primary mapper first before setting up a non primary "
1280 "Mapper." % self.class_
1281 )
1282 self.class_manager = manager
1283 self._identity_class = manager.mapper._identity_class
1284 _mapper_registry[self] = True
1285 return
1287 if manager is not None:
1288 assert manager.class_ is self.class_
1289 if manager.is_mapped:
1290 raise sa_exc.ArgumentError(
1291 "Class '%s' already has a primary mapper defined. "
1292 "Use non_primary=True to "
1293 "create a non primary Mapper. clear_mappers() will "
1294 "remove *all* current mappers from all classes."
1295 % self.class_
1296 )
1297 # else:
1298 # a ClassManager may already exist as
1299 # ClassManager.instrument_attribute() creates
1300 # new managers for each subclass if they don't yet exist.
1302 _mapper_registry[self] = True
1304 # note: this *must be called before instrumentation.register_class*
1305 # to maintain the documented behavior of instrument_class
1306 self.dispatch.instrument_class(self, self.class_)
1308 if manager is None:
1309 manager = instrumentation.register_class(self.class_)
1311 self.class_manager = manager
1313 manager.mapper = self
1314 manager.deferred_scalar_loader = util.partial(
1315 loading.load_scalar_attributes, self
1316 )
1318 # The remaining members can be added by any mapper,
1319 # e_name None or not.
1320 if manager.info.get(_INSTRUMENTOR, False):
1321 return
1323 event.listen(manager, "first_init", _event_on_first_init, raw=True)
1324 event.listen(manager, "init", _event_on_init, raw=True)
1326 for key, method in util.iterate_attributes(self.class_):
1327 if key == "__init__" and hasattr(method, "_sa_original_init"):
1328 method = method._sa_original_init
1329 if isinstance(method, types.MethodType):
1330 method = method.im_func
1331 if isinstance(method, types.FunctionType):
1332 if hasattr(method, "__sa_reconstructor__"):
1333 self._reconstructor = method
1334 event.listen(manager, "load", _event_on_load, raw=True)
1335 elif hasattr(method, "__sa_validators__"):
1336 validation_opts = method.__sa_validation_opts__
1337 for name in method.__sa_validators__:
1338 if name in self.validators:
1339 raise sa_exc.InvalidRequestError(
1340 "A validation function for mapped "
1341 "attribute %r on mapper %s already exists."
1342 % (name, self)
1343 )
1344 self.validators = self.validators.union(
1345 {name: (method, validation_opts)}
1346 )
1348 manager.info[_INSTRUMENTOR] = self
1350 @classmethod
1351 def _configure_all(cls):
1352 """Class-level path to the :func:`.configure_mappers` call.
1353 """
1354 configure_mappers()
1356 def dispose(self):
1357 # Disable any attribute-based compilation.
1358 self.configured = True
1359 self._dispose_called = True
1361 if hasattr(self, "_configure_failed"):
1362 del self._configure_failed
1364 if (
1365 not self.non_primary
1366 and self.class_manager is not None
1367 and self.class_manager.is_mapped
1368 and self.class_manager.mapper is self
1369 ):
1370 instrumentation.unregister_class(self.class_)
1372 def _configure_pks(self):
1373 self.tables = sql_util.find_tables(self.persist_selectable)
1375 self._pks_by_table = {}
1376 self._cols_by_table = {}
1378 all_cols = util.column_set(
1379 chain(*[col.proxy_set for col in self._columntoproperty])
1380 )
1382 pk_cols = util.column_set(c for c in all_cols if c.primary_key)
1384 # identify primary key columns which are also mapped by this mapper.
1385 tables = set(self.tables + [self.persist_selectable])
1386 self._all_tables.update(tables)
1387 for t in tables:
1388 if t.primary_key and pk_cols.issuperset(t.primary_key):
1389 # ordering is important since it determines the ordering of
1390 # mapper.primary_key (and therefore query.get())
1391 self._pks_by_table[t] = util.ordered_column_set(
1392 t.primary_key
1393 ).intersection(pk_cols)
1394 self._cols_by_table[t] = util.ordered_column_set(t.c).intersection(
1395 all_cols
1396 )
1398 # if explicit PK argument sent, add those columns to the
1399 # primary key mappings
1400 if self._primary_key_argument:
1401 for k in self._primary_key_argument:
1402 if k.table not in self._pks_by_table:
1403 self._pks_by_table[k.table] = util.OrderedSet()
1404 self._pks_by_table[k.table].add(k)
1406 # otherwise, see that we got a full PK for the mapped table
1407 elif (
1408 self.persist_selectable not in self._pks_by_table
1409 or len(self._pks_by_table[self.persist_selectable]) == 0
1410 ):
1411 raise sa_exc.ArgumentError(
1412 "Mapper %s could not assemble any primary "
1413 "key columns for mapped table '%s'"
1414 % (self, self.persist_selectable.description)
1415 )
1416 elif self.local_table not in self._pks_by_table and isinstance(
1417 self.local_table, schema.Table
1418 ):
1419 util.warn(
1420 "Could not assemble any primary "
1421 "keys for locally mapped table '%s' - "
1422 "no rows will be persisted in this Table."
1423 % self.local_table.description
1424 )
1426 if (
1427 self.inherits
1428 and not self.concrete
1429 and not self._primary_key_argument
1430 ):
1431 # if inheriting, the "primary key" for this mapper is
1432 # that of the inheriting (unless concrete or explicit)
1433 self.primary_key = self.inherits.primary_key
1434 else:
1435 # determine primary key from argument or persist_selectable pks -
1436 # reduce to the minimal set of columns
1437 if self._primary_key_argument:
1438 primary_key = sql_util.reduce_columns(
1439 [
1440 self.persist_selectable.corresponding_column(c)
1441 for c in self._primary_key_argument
1442 ],
1443 ignore_nonexistent_tables=True,
1444 )
1445 else:
1446 primary_key = sql_util.reduce_columns(
1447 self._pks_by_table[self.persist_selectable],
1448 ignore_nonexistent_tables=True,
1449 )
1451 if len(primary_key) == 0:
1452 raise sa_exc.ArgumentError(
1453 "Mapper %s could not assemble any primary "
1454 "key columns for mapped table '%s'"
1455 % (self, self.persist_selectable.description)
1456 )
1458 self.primary_key = tuple(primary_key)
1459 self._log("Identified primary key columns: %s", primary_key)
1461 # determine cols that aren't expressed within our tables; mark these
1462 # as "read only" properties which are refreshed upon INSERT/UPDATE
1463 self._readonly_props = set(
1464 self._columntoproperty[col]
1465 for col in self._columntoproperty
1466 if self._columntoproperty[col] not in self._identity_key_props
1467 and (
1468 not hasattr(col, "table")
1469 or col.table not in self._cols_by_table
1470 )
1471 )
1473 def _configure_properties(self):
1474 # Column and other ClauseElement objects which are mapped
1475 self.columns = self.c = util.OrderedProperties()
1477 # object attribute names mapped to MapperProperty objects
1478 self._props = util.OrderedDict()
1480 # table columns mapped to lists of MapperProperty objects
1481 # using a list allows a single column to be defined as
1482 # populating multiple object attributes
1483 self._columntoproperty = _ColumnMapping(self)
1485 # load custom properties
1486 if self._init_properties:
1487 for key, prop in self._init_properties.items():
1488 self._configure_property(key, prop, False)
1490 # pull properties from the inherited mapper if any.
1491 if self.inherits:
1492 for key, prop in self.inherits._props.items():
1493 if key not in self._props and not self._should_exclude(
1494 key, key, local=False, column=None
1495 ):
1496 self._adapt_inherited_property(key, prop, False)
1498 # create properties for each column in the mapped table,
1499 # for those columns which don't already map to a property
1500 for column in self.persist_selectable.columns:
1501 if column in self._columntoproperty:
1502 continue
1504 column_key = (self.column_prefix or "") + column.key
1506 if self._should_exclude(
1507 column.key,
1508 column_key,
1509 local=self.local_table.c.contains_column(column),
1510 column=column,
1511 ):
1512 continue
1514 # adjust the "key" used for this column to that
1515 # of the inheriting mapper
1516 for mapper in self.iterate_to_root():
1517 if column in mapper._columntoproperty:
1518 column_key = mapper._columntoproperty[column].key
1520 self._configure_property(
1521 column_key, column, init=False, setparent=True
1522 )
1524 def _configure_polymorphic_setter(self, init=False):
1525 """Configure an attribute on the mapper representing the
1526 'polymorphic_on' column, if applicable, and not
1527 already generated by _configure_properties (which is typical).
1529 Also create a setter function which will assign this
1530 attribute to the value of the 'polymorphic_identity'
1531 upon instance construction, also if applicable. This
1532 routine will run when an instance is created.
1534 """
1535 setter = False
1537 if self.polymorphic_on is not None:
1538 setter = True
1540 if isinstance(self.polymorphic_on, util.string_types):
1541 # polymorphic_on specified as a string - link
1542 # it to mapped ColumnProperty
1543 try:
1544 self.polymorphic_on = self._props[self.polymorphic_on]
1545 except KeyError as err:
1546 util.raise_(
1547 sa_exc.ArgumentError(
1548 "Can't determine polymorphic_on "
1549 "value '%s' - no attribute is "
1550 "mapped to this name." % self.polymorphic_on
1551 ),
1552 replace_context=err,
1553 )
1555 if self.polymorphic_on in self._columntoproperty:
1556 # polymorphic_on is a column that is already mapped
1557 # to a ColumnProperty
1558 prop = self._columntoproperty[self.polymorphic_on]
1559 elif isinstance(self.polymorphic_on, MapperProperty):
1560 # polymorphic_on is directly a MapperProperty,
1561 # ensure it's a ColumnProperty
1562 if not isinstance(
1563 self.polymorphic_on, properties.ColumnProperty
1564 ):
1565 raise sa_exc.ArgumentError(
1566 "Only direct column-mapped "
1567 "property or SQL expression "
1568 "can be passed for polymorphic_on"
1569 )
1570 prop = self.polymorphic_on
1571 elif not expression._is_column(self.polymorphic_on):
1572 # polymorphic_on is not a Column and not a ColumnProperty;
1573 # not supported right now.
1574 raise sa_exc.ArgumentError(
1575 "Only direct column-mapped "
1576 "property or SQL expression "
1577 "can be passed for polymorphic_on"
1578 )
1579 else:
1580 # polymorphic_on is a Column or SQL expression and
1581 # doesn't appear to be mapped. this means it can be 1.
1582 # only present in the with_polymorphic selectable or
1583 # 2. a totally standalone SQL expression which we'd
1584 # hope is compatible with this mapper's persist_selectable
1585 col = self.persist_selectable.corresponding_column(
1586 self.polymorphic_on
1587 )
1588 if col is None:
1589 # polymorphic_on doesn't derive from any
1590 # column/expression isn't present in the mapped
1591 # table. we will make a "hidden" ColumnProperty
1592 # for it. Just check that if it's directly a
1593 # schema.Column and we have with_polymorphic, it's
1594 # likely a user error if the schema.Column isn't
1595 # represented somehow in either persist_selectable or
1596 # with_polymorphic. Otherwise as of 0.7.4 we
1597 # just go with it and assume the user wants it
1598 # that way (i.e. a CASE statement)
1599 setter = False
1600 instrument = False
1601 col = self.polymorphic_on
1602 if isinstance(col, schema.Column) and (
1603 self.with_polymorphic is None
1604 or self.with_polymorphic[1].corresponding_column(col)
1605 is None
1606 ):
1607 raise sa_exc.InvalidRequestError(
1608 "Could not map polymorphic_on column "
1609 "'%s' to the mapped table - polymorphic "
1610 "loads will not function properly"
1611 % col.description
1612 )
1613 else:
1614 # column/expression that polymorphic_on derives from
1615 # is present in our mapped table
1616 # and is probably mapped, but polymorphic_on itself
1617 # is not. This happens when
1618 # the polymorphic_on is only directly present in the
1619 # with_polymorphic selectable, as when use
1620 # polymorphic_union.
1621 # we'll make a separate ColumnProperty for it.
1622 instrument = True
1623 key = getattr(col, "key", None)
1624 if key:
1625 if self._should_exclude(col.key, col.key, False, col):
1626 raise sa_exc.InvalidRequestError(
1627 "Cannot exclude or override the "
1628 "discriminator column %r" % col.key
1629 )
1630 else:
1631 self.polymorphic_on = col = col.label("_sa_polymorphic_on")
1632 key = col.key
1634 prop = properties.ColumnProperty(col, _instrument=instrument)
1635 self._configure_property(key, prop, init=init, setparent=True)
1637 # the actual polymorphic_on should be the first public-facing
1638 # column in the property
1639 self.polymorphic_on = prop.columns[0]
1640 polymorphic_key = prop.key
1642 else:
1643 # no polymorphic_on was set.
1644 # check inheriting mappers for one.
1645 for mapper in self.iterate_to_root():
1646 # determine if polymorphic_on of the parent
1647 # should be propagated here. If the col
1648 # is present in our mapped table, or if our mapped
1649 # table is the same as the parent (i.e. single table
1650 # inheritance), we can use it
1651 if mapper.polymorphic_on is not None:
1652 if self.persist_selectable is mapper.persist_selectable:
1653 self.polymorphic_on = mapper.polymorphic_on
1654 else:
1655 self.polymorphic_on = (
1656 self.persist_selectable
1657 ).corresponding_column(mapper.polymorphic_on)
1658 # we can use the parent mapper's _set_polymorphic_identity
1659 # directly; it ensures the polymorphic_identity of the
1660 # instance's mapper is used so is portable to subclasses.
1661 if self.polymorphic_on is not None:
1662 self._set_polymorphic_identity = (
1663 mapper._set_polymorphic_identity
1664 )
1665 self._validate_polymorphic_identity = (
1666 mapper._validate_polymorphic_identity
1667 )
1668 else:
1669 self._set_polymorphic_identity = None
1670 return
1672 if setter:
1674 def _set_polymorphic_identity(state):
1675 dict_ = state.dict
1676 state.get_impl(polymorphic_key).set(
1677 state,
1678 dict_,
1679 state.manager.mapper.polymorphic_identity,
1680 None,
1681 )
1683 def _validate_polymorphic_identity(mapper, state, dict_):
1684 if (
1685 polymorphic_key in dict_
1686 and dict_[polymorphic_key]
1687 not in mapper._acceptable_polymorphic_identities
1688 ):
1689 util.warn_limited(
1690 "Flushing object %s with "
1691 "incompatible polymorphic identity %r; the "
1692 "object may not refresh and/or load correctly",
1693 (state_str(state), dict_[polymorphic_key]),
1694 )
1696 self._set_polymorphic_identity = _set_polymorphic_identity
1697 self._validate_polymorphic_identity = (
1698 _validate_polymorphic_identity
1699 )
1700 else:
1701 self._set_polymorphic_identity = None
1703 _validate_polymorphic_identity = None
1705 @_memoized_configured_property
1706 def _version_id_prop(self):
1707 if self.version_id_col is not None:
1708 return self._columntoproperty[self.version_id_col]
1709 else:
1710 return None
1712 @_memoized_configured_property
1713 def _acceptable_polymorphic_identities(self):
1714 identities = set()
1716 stack = deque([self])
1717 while stack:
1718 item = stack.popleft()
1719 if item.persist_selectable is self.persist_selectable:
1720 identities.add(item.polymorphic_identity)
1721 stack.extend(item._inheriting_mappers)
1723 return identities
1725 @_memoized_configured_property
1726 def _prop_set(self):
1727 return frozenset(self._props.values())
1729 def _adapt_inherited_property(self, key, prop, init):
1730 if not self.concrete:
1731 self._configure_property(key, prop, init=False, setparent=False)
1732 elif key not in self._props:
1733 # determine if the class implements this attribute; if not,
1734 # or if it is implemented by the attribute that is handling the
1735 # given superclass-mapped property, then we need to report that we
1736 # can't use this at the instance level since we are a concrete
1737 # mapper and we don't map this. don't trip user-defined
1738 # descriptors that might have side effects when invoked.
1739 implementing_attribute = self.class_manager._get_class_attr_mro(
1740 key, prop
1741 )
1742 if implementing_attribute is prop or (
1743 isinstance(
1744 implementing_attribute, attributes.InstrumentedAttribute
1745 )
1746 and implementing_attribute._parententity is prop.parent
1747 ):
1748 self._configure_property(
1749 key,
1750 properties.ConcreteInheritedProperty(),
1751 init=init,
1752 setparent=True,
1753 )
1755 def _configure_property(self, key, prop, init=True, setparent=True):
1756 self._log("_configure_property(%s, %s)", key, prop.__class__.__name__)
1758 if not isinstance(prop, MapperProperty):
1759 prop = self._property_from_column(key, prop)
1761 if isinstance(prop, properties.ColumnProperty):
1762 col = self.persist_selectable.corresponding_column(prop.columns[0])
1764 # if the column is not present in the mapped table,
1765 # test if a column has been added after the fact to the
1766 # parent table (or their parent, etc.) [ticket:1570]
1767 if col is None and self.inherits:
1768 path = [self]
1769 for m in self.inherits.iterate_to_root():
1770 col = m.local_table.corresponding_column(prop.columns[0])
1771 if col is not None:
1772 for m2 in path:
1773 m2.persist_selectable._reset_exported()
1774 col = self.persist_selectable.corresponding_column(
1775 prop.columns[0]
1776 )
1777 break
1778 path.append(m)
1780 # subquery expression, column not present in the mapped
1781 # selectable.
1782 if col is None:
1783 col = prop.columns[0]
1785 # column is coming in after _readonly_props was
1786 # initialized; check for 'readonly'
1787 if hasattr(self, "_readonly_props") and (
1788 not hasattr(col, "table")
1789 or col.table not in self._cols_by_table
1790 ):
1791 self._readonly_props.add(prop)
1793 else:
1794 # if column is coming in after _cols_by_table was
1795 # initialized, ensure the col is in the right set
1796 if (
1797 hasattr(self, "_cols_by_table")
1798 and col.table in self._cols_by_table
1799 and col not in self._cols_by_table[col.table]
1800 ):
1801 self._cols_by_table[col.table].add(col)
1803 # if this properties.ColumnProperty represents the "polymorphic
1804 # discriminator" column, mark it. We'll need this when rendering
1805 # columns in SELECT statements.
1806 if not hasattr(prop, "_is_polymorphic_discriminator"):
1807 prop._is_polymorphic_discriminator = (
1808 col is self.polymorphic_on
1809 or prop.columns[0] is self.polymorphic_on
1810 )
1812 self.columns[key] = col
1813 for col in prop.columns + prop._orig_columns:
1814 for col in col.proxy_set:
1815 self._columntoproperty[col] = prop
1817 prop.key = key
1819 if setparent:
1820 prop.set_parent(self, init)
1822 if key in self._props and getattr(
1823 self._props[key], "_mapped_by_synonym", False
1824 ):
1825 syn = self._props[key]._mapped_by_synonym
1826 raise sa_exc.ArgumentError(
1827 "Can't call map_column=True for synonym %r=%r, "
1828 "a ColumnProperty already exists keyed to the name "
1829 "%r for column %r" % (syn, key, key, syn)
1830 )
1832 if (
1833 key in self._props
1834 and not isinstance(prop, properties.ColumnProperty)
1835 and not isinstance(
1836 self._props[key],
1837 (
1838 properties.ColumnProperty,
1839 properties.ConcreteInheritedProperty,
1840 ),
1841 )
1842 ):
1843 util.warn(
1844 "Property %s on %s being replaced with new "
1845 "property %s; the old property will be discarded"
1846 % (self._props[key], self, prop)
1847 )
1848 oldprop = self._props[key]
1849 self._path_registry.pop(oldprop, None)
1851 self._props[key] = prop
1853 if not self.non_primary:
1854 prop.instrument_class(self)
1856 for mapper in self._inheriting_mappers:
1857 mapper._adapt_inherited_property(key, prop, init)
1859 if init:
1860 prop.init()
1861 prop.post_instrument_class(self)
1863 if self.configured:
1864 self._expire_memoizations()
1866 def _property_from_column(self, key, prop):
1867 """generate/update a :class:`.ColumnProprerty` given a
1868 :class:`_schema.Column` object. """
1870 # we were passed a Column or a list of Columns;
1871 # generate a properties.ColumnProperty
1872 columns = util.to_list(prop)
1873 column = columns[0]
1874 if not expression._is_column(column):
1875 raise sa_exc.ArgumentError(
1876 "%s=%r is not an instance of MapperProperty or Column"
1877 % (key, prop)
1878 )
1880 prop = self._props.get(key, None)
1882 if isinstance(prop, properties.ColumnProperty):
1883 if (
1884 (
1885 not self._inherits_equated_pairs
1886 or (prop.columns[0], column)
1887 not in self._inherits_equated_pairs
1888 )
1889 and not prop.columns[0].shares_lineage(column)
1890 and prop.columns[0] is not self.version_id_col
1891 and column is not self.version_id_col
1892 ):
1893 warn_only = prop.parent is not self
1894 msg = (
1895 "Implicitly combining column %s with column "
1896 "%s under attribute '%s'. Please configure one "
1897 "or more attributes for these same-named columns "
1898 "explicitly." % (prop.columns[-1], column, key)
1899 )
1900 if warn_only:
1901 util.warn(msg)
1902 else:
1903 raise sa_exc.InvalidRequestError(msg)
1905 # existing properties.ColumnProperty from an inheriting
1906 # mapper. make a copy and append our column to it
1907 prop = prop.copy()
1908 prop.columns.insert(0, column)
1909 self._log(
1910 "inserting column to existing list "
1911 "in properties.ColumnProperty %s" % (key)
1912 )
1913 return prop
1914 elif prop is None or isinstance(
1915 prop, properties.ConcreteInheritedProperty
1916 ):
1917 mapped_column = []
1918 for c in columns:
1919 mc = self.persist_selectable.corresponding_column(c)
1920 if mc is None:
1921 mc = self.local_table.corresponding_column(c)
1922 if mc is not None:
1923 # if the column is in the local table but not the
1924 # mapped table, this corresponds to adding a
1925 # column after the fact to the local table.
1926 # [ticket:1523]
1927 self.persist_selectable._reset_exported()
1928 mc = self.persist_selectable.corresponding_column(c)
1929 if mc is None:
1930 raise sa_exc.ArgumentError(
1931 "When configuring property '%s' on %s, "
1932 "column '%s' is not represented in the mapper's "
1933 "table. Use the `column_property()` function to "
1934 "force this column to be mapped as a read-only "
1935 "attribute." % (key, self, c)
1936 )
1937 mapped_column.append(mc)
1938 return properties.ColumnProperty(*mapped_column)
1939 else:
1940 raise sa_exc.ArgumentError(
1941 "WARNING: when configuring property '%s' on %s, "
1942 "column '%s' conflicts with property '%r'. "
1943 "To resolve this, map the column to the class under a "
1944 "different name in the 'properties' dictionary. Or, "
1945 "to remove all awareness of the column entirely "
1946 "(including its availability as a foreign key), "
1947 "use the 'include_properties' or 'exclude_properties' "
1948 "mapper arguments to control specifically which table "
1949 "columns get mapped." % (key, self, column.key, prop)
1950 )
1952 def _post_configure_properties(self):
1953 """Call the ``init()`` method on all ``MapperProperties``
1954 attached to this mapper.
1956 This is a deferred configuration step which is intended
1957 to execute once all mappers have been constructed.
1959 """
1961 self._log("_post_configure_properties() started")
1962 l = [(key, prop) for key, prop in self._props.items()]
1963 for key, prop in l:
1964 self._log("initialize prop %s", key)
1966 if prop.parent is self and not prop._configure_started:
1967 prop.init()
1969 if prop._configure_finished:
1970 prop.post_instrument_class(self)
1972 self._log("_post_configure_properties() complete")
1973 self.configured = True
1975 def add_properties(self, dict_of_properties):
1976 """Add the given dictionary of properties to this mapper,
1977 using `add_property`.
1979 """
1980 for key, value in dict_of_properties.items():
1981 self.add_property(key, value)
1983 def add_property(self, key, prop):
1984 """Add an individual MapperProperty to this mapper.
1986 If the mapper has not been configured yet, just adds the
1987 property to the initial properties dictionary sent to the
1988 constructor. If this Mapper has already been configured, then
1989 the given MapperProperty is configured immediately.
1991 """
1992 self._init_properties[key] = prop
1993 self._configure_property(key, prop, init=self.configured)
1995 def _expire_memoizations(self):
1996 for mapper in self.iterate_to_root():
1997 _memoized_configured_property.expire_instance(mapper)
1999 @property
2000 def _log_desc(self):
2001 return (
2002 "("
2003 + self.class_.__name__
2004 + "|"
2005 + (
2006 self.local_table is not None
2007 and self.local_table.description
2008 or str(self.local_table)
2009 )
2010 + (self.non_primary and "|non-primary" or "")
2011 + ")"
2012 )
2014 def _log(self, msg, *args):
2015 self.logger.info("%s " + msg, *((self._log_desc,) + args))
2017 def _log_debug(self, msg, *args):
2018 self.logger.debug("%s " + msg, *((self._log_desc,) + args))
2020 def __repr__(self):
2021 return "<Mapper at 0x%x; %s>" % (id(self), self.class_.__name__)
2023 def __str__(self):
2024 return "mapped class %s%s->%s" % (
2025 self.class_.__name__,
2026 self.non_primary and " (non-primary)" or "",
2027 self.local_table.description
2028 if self.local_table is not None
2029 else self.persist_selectable.description,
2030 )
2032 def _is_orphan(self, state):
2033 orphan_possible = False
2034 for mapper in self.iterate_to_root():
2035 for (key, cls) in mapper._delete_orphans:
2036 orphan_possible = True
2038 has_parent = attributes.manager_of_class(cls).has_parent(
2039 state, key, optimistic=state.has_identity
2040 )
2042 if self.legacy_is_orphan and has_parent:
2043 return False
2044 elif not self.legacy_is_orphan and not has_parent:
2045 return True
2047 if self.legacy_is_orphan:
2048 return orphan_possible
2049 else:
2050 return False
2052 def has_property(self, key):
2053 return key in self._props
2055 def get_property(self, key, _configure_mappers=True):
2056 """return a MapperProperty associated with the given key.
2057 """
2059 if _configure_mappers and Mapper._new_mappers:
2060 configure_mappers()
2062 try:
2063 return self._props[key]
2064 except KeyError as err:
2065 util.raise_(
2066 sa_exc.InvalidRequestError(
2067 "Mapper '%s' has no property '%s'" % (self, key)
2068 ),
2069 replace_context=err,
2070 )
2072 def get_property_by_column(self, column):
2073 """Given a :class:`_schema.Column` object, return the
2074 :class:`.MapperProperty` which maps this column."""
2076 return self._columntoproperty[column]
2078 @property
2079 def iterate_properties(self):
2080 """return an iterator of all MapperProperty objects."""
2081 if Mapper._new_mappers:
2082 configure_mappers()
2083 return iter(self._props.values())
2085 def _mappers_from_spec(self, spec, selectable):
2086 """given a with_polymorphic() argument, return the set of mappers it
2087 represents.
2089 Trims the list of mappers to just those represented within the given
2090 selectable, if present. This helps some more legacy-ish mappings.
2092 """
2093 if spec == "*":
2094 mappers = list(self.self_and_descendants)
2095 elif spec:
2096 mappers = set()
2097 for m in util.to_list(spec):
2098 m = _class_to_mapper(m)
2099 if not m.isa(self):
2100 raise sa_exc.InvalidRequestError(
2101 "%r does not inherit from %r" % (m, self)
2102 )
2104 if selectable is None:
2105 mappers.update(m.iterate_to_root())
2106 else:
2107 mappers.add(m)
2108 mappers = [m for m in self.self_and_descendants if m in mappers]
2109 else:
2110 mappers = []
2112 if selectable is not None:
2113 tables = set(
2114 sql_util.find_tables(selectable, include_aliases=True)
2115 )
2116 mappers = [m for m in mappers if m.local_table in tables]
2117 return mappers
2119 def _selectable_from_mappers(self, mappers, innerjoin):
2120 """given a list of mappers (assumed to be within this mapper's
2121 inheritance hierarchy), construct an outerjoin amongst those mapper's
2122 mapped tables.
2124 """
2125 from_obj = self.persist_selectable
2126 for m in mappers:
2127 if m is self:
2128 continue
2129 if m.concrete:
2130 raise sa_exc.InvalidRequestError(
2131 "'with_polymorphic()' requires 'selectable' argument "
2132 "when concrete-inheriting mappers are used."
2133 )
2134 elif not m.single:
2135 if innerjoin:
2136 from_obj = from_obj.join(
2137 m.local_table, m.inherit_condition
2138 )
2139 else:
2140 from_obj = from_obj.outerjoin(
2141 m.local_table, m.inherit_condition
2142 )
2144 return from_obj
2146 @_memoized_configured_property
2147 def _single_table_criterion(self):
2148 if self.single and self.inherits and self.polymorphic_on is not None:
2149 return self.polymorphic_on._annotate({"parentmapper": self}).in_(
2150 m.polymorphic_identity for m in self.self_and_descendants
2151 )
2152 else:
2153 return None
2155 @_memoized_configured_property
2156 def _with_polymorphic_mappers(self):
2157 if Mapper._new_mappers:
2158 configure_mappers()
2159 if not self.with_polymorphic:
2160 return []
2161 return self._mappers_from_spec(*self.with_polymorphic)
2163 @_memoized_configured_property
2164 def _with_polymorphic_selectable(self):
2165 if not self.with_polymorphic:
2166 return self.persist_selectable
2168 spec, selectable = self.with_polymorphic
2169 if selectable is not None:
2170 return selectable
2171 else:
2172 return self._selectable_from_mappers(
2173 self._mappers_from_spec(spec, selectable), False
2174 )
2176 with_polymorphic_mappers = _with_polymorphic_mappers
2177 """The list of :class:`_orm.Mapper` objects included in the
2178 default "polymorphic" query.
2180 """
2182 @_memoized_configured_property
2183 def _insert_cols_evaluating_none(self):
2184 return dict(
2185 (
2186 table,
2187 frozenset(
2188 col for col in columns if col.type.should_evaluate_none
2189 ),
2190 )
2191 for table, columns in self._cols_by_table.items()
2192 )
2194 @_memoized_configured_property
2195 def _insert_cols_as_none(self):
2196 return dict(
2197 (
2198 table,
2199 frozenset(
2200 col.key
2201 for col in columns
2202 if not col.primary_key
2203 and not col.server_default
2204 and not col.default
2205 and not col.type.should_evaluate_none
2206 ),
2207 )
2208 for table, columns in self._cols_by_table.items()
2209 )
2211 @_memoized_configured_property
2212 def _propkey_to_col(self):
2213 return dict(
2214 (
2215 table,
2216 dict(
2217 (self._columntoproperty[col].key, col) for col in columns
2218 ),
2219 )
2220 for table, columns in self._cols_by_table.items()
2221 )
2223 @_memoized_configured_property
2224 def _pk_keys_by_table(self):
2225 return dict(
2226 (table, frozenset([col.key for col in pks]))
2227 for table, pks in self._pks_by_table.items()
2228 )
2230 @_memoized_configured_property
2231 def _pk_attr_keys_by_table(self):
2232 return dict(
2233 (
2234 table,
2235 frozenset([self._columntoproperty[col].key for col in pks]),
2236 )
2237 for table, pks in self._pks_by_table.items()
2238 )
2240 @_memoized_configured_property
2241 def _server_default_cols(self):
2242 return dict(
2243 (
2244 table,
2245 frozenset(
2246 [
2247 col.key
2248 for col in columns
2249 if col.server_default is not None
2250 ]
2251 ),
2252 )
2253 for table, columns in self._cols_by_table.items()
2254 )
2256 @_memoized_configured_property
2257 def _server_default_plus_onupdate_propkeys(self):
2258 result = set()
2260 for table, columns in self._cols_by_table.items():
2261 for col in columns:
2262 if (
2263 col.server_default is not None
2264 or col.server_onupdate is not None
2265 ) and col in self._columntoproperty:
2266 result.add(self._columntoproperty[col].key)
2268 return result
2270 @_memoized_configured_property
2271 def _server_onupdate_default_cols(self):
2272 return dict(
2273 (
2274 table,
2275 frozenset(
2276 [
2277 col.key
2278 for col in columns
2279 if col.server_onupdate is not None
2280 ]
2281 ),
2282 )
2283 for table, columns in self._cols_by_table.items()
2284 )
2286 @property
2287 def selectable(self):
2288 """The :func:`_expression.select` construct this :class:`_orm.Mapper`
2289 selects from
2290 by default.
2292 Normally, this is equivalent to :attr:`.persist_selectable`, unless
2293 the ``with_polymorphic`` feature is in use, in which case the
2294 full "polymorphic" selectable is returned.
2296 """
2297 return self._with_polymorphic_selectable
2299 def _with_polymorphic_args(
2300 self, spec=None, selectable=False, innerjoin=False
2301 ):
2302 if self.with_polymorphic:
2303 if not spec:
2304 spec = self.with_polymorphic[0]
2305 if selectable is False:
2306 selectable = self.with_polymorphic[1]
2307 elif selectable is False:
2308 selectable = None
2309 mappers = self._mappers_from_spec(spec, selectable)
2310 if selectable is not None:
2311 return mappers, selectable
2312 else:
2313 return mappers, self._selectable_from_mappers(mappers, innerjoin)
2315 @_memoized_configured_property
2316 def _polymorphic_properties(self):
2317 return list(
2318 self._iterate_polymorphic_properties(
2319 self._with_polymorphic_mappers
2320 )
2321 )
2323 def _iterate_polymorphic_properties(self, mappers=None):
2324 """Return an iterator of MapperProperty objects which will render into
2325 a SELECT."""
2326 if mappers is None:
2327 mappers = self._with_polymorphic_mappers
2329 if not mappers:
2330 for c in self.iterate_properties:
2331 yield c
2332 else:
2333 # in the polymorphic case, filter out discriminator columns
2334 # from other mappers, as these are sometimes dependent on that
2335 # mapper's polymorphic selectable (which we don't want rendered)
2336 for c in util.unique_list(
2337 chain(
2338 *[
2339 list(mapper.iterate_properties)
2340 for mapper in [self] + mappers
2341 ]
2342 )
2343 ):
2344 if getattr(c, "_is_polymorphic_discriminator", False) and (
2345 self.polymorphic_on is None
2346 or c.columns[0] is not self.polymorphic_on
2347 ):
2348 continue
2349 yield c
2351 @_memoized_configured_property
2352 def attrs(self):
2353 """A namespace of all :class:`.MapperProperty` objects
2354 associated this mapper.
2356 This is an object that provides each property based on
2357 its key name. For instance, the mapper for a
2358 ``User`` class which has ``User.name`` attribute would
2359 provide ``mapper.attrs.name``, which would be the
2360 :class:`.ColumnProperty` representing the ``name``
2361 column. The namespace object can also be iterated,
2362 which would yield each :class:`.MapperProperty`.
2364 :class:`_orm.Mapper` has several pre-filtered views
2365 of this attribute which limit the types of properties
2366 returned, including :attr:`.synonyms`, :attr:`.column_attrs`,
2367 :attr:`.relationships`, and :attr:`.composites`.
2369 .. warning::
2371 The :attr:`_orm.Mapper.attrs` accessor namespace is an
2372 instance of :class:`.OrderedProperties`. This is
2373 a dictionary-like object which includes a small number of
2374 named methods such as :meth:`.OrderedProperties.items`
2375 and :meth:`.OrderedProperties.values`. When
2376 accessing attributes dynamically, favor using the dict-access
2377 scheme, e.g. ``mapper.attrs[somename]`` over
2378 ``getattr(mapper.attrs, somename)`` to avoid name collisions.
2380 .. seealso::
2382 :attr:`_orm.Mapper.all_orm_descriptors`
2384 """
2385 if Mapper._new_mappers:
2386 configure_mappers()
2387 return util.ImmutableProperties(self._props)
2389 @_memoized_configured_property
2390 def all_orm_descriptors(self):
2391 """A namespace of all :class:`.InspectionAttr` attributes associated
2392 with the mapped class.
2394 These attributes are in all cases Python :term:`descriptors`
2395 associated with the mapped class or its superclasses.
2397 This namespace includes attributes that are mapped to the class
2398 as well as attributes declared by extension modules.
2399 It includes any Python descriptor type that inherits from
2400 :class:`.InspectionAttr`. This includes
2401 :class:`.QueryableAttribute`, as well as extension types such as
2402 :class:`.hybrid_property`, :class:`.hybrid_method` and
2403 :class:`.AssociationProxy`.
2405 To distinguish between mapped attributes and extension attributes,
2406 the attribute :attr:`.InspectionAttr.extension_type` will refer
2407 to a constant that distinguishes between different extension types.
2409 When dealing with a :class:`.QueryableAttribute`, the
2410 :attr:`.QueryableAttribute.property` attribute refers to the
2411 :class:`.MapperProperty` property, which is what you get when
2412 referring to the collection of mapped properties via
2413 :attr:`_orm.Mapper.attrs`.
2415 .. warning::
2417 The :attr:`_orm.Mapper.all_orm_descriptors`
2418 accessor namespace is an
2419 instance of :class:`.OrderedProperties`. This is
2420 a dictionary-like object which includes a small number of
2421 named methods such as :meth:`.OrderedProperties.items`
2422 and :meth:`.OrderedProperties.values`. When
2423 accessing attributes dynamically, favor using the dict-access
2424 scheme, e.g. ``mapper.all_orm_descriptors[somename]`` over
2425 ``getattr(mapper.all_orm_descriptors, somename)`` to avoid name
2426 collisions.
2428 .. seealso::
2430 :attr:`_orm.Mapper.attrs`
2432 """
2433 return util.ImmutableProperties(
2434 dict(self.class_manager._all_sqla_attributes())
2435 )
2437 @_memoized_configured_property
2438 def synonyms(self):
2439 """Return a namespace of all :class:`.SynonymProperty`
2440 properties maintained by this :class:`_orm.Mapper`.
2442 .. seealso::
2444 :attr:`_orm.Mapper.attrs` - namespace of all
2445 :class:`.MapperProperty`
2446 objects.
2448 """
2449 return self._filter_properties(properties.SynonymProperty)
2451 @_memoized_configured_property
2452 def column_attrs(self):
2453 """Return a namespace of all :class:`.ColumnProperty`
2454 properties maintained by this :class:`_orm.Mapper`.
2456 .. seealso::
2458 :attr:`_orm.Mapper.attrs` - namespace of all
2459 :class:`.MapperProperty`
2460 objects.
2462 """
2463 return self._filter_properties(properties.ColumnProperty)
2465 @_memoized_configured_property
2466 def relationships(self):
2467 """A namespace of all :class:`.RelationshipProperty` properties
2468 maintained by this :class:`_orm.Mapper`.
2470 .. warning::
2472 the :attr:`_orm.Mapper.relationships` accessor namespace is an
2473 instance of :class:`.OrderedProperties`. This is
2474 a dictionary-like object which includes a small number of
2475 named methods such as :meth:`.OrderedProperties.items`
2476 and :meth:`.OrderedProperties.values`. When
2477 accessing attributes dynamically, favor using the dict-access
2478 scheme, e.g. ``mapper.relationships[somename]`` over
2479 ``getattr(mapper.relationships, somename)`` to avoid name
2480 collisions.
2482 .. seealso::
2484 :attr:`_orm.Mapper.attrs` - namespace of all
2485 :class:`.MapperProperty`
2486 objects.
2488 """
2489 return self._filter_properties(properties.RelationshipProperty)
2491 @_memoized_configured_property
2492 def composites(self):
2493 """Return a namespace of all :class:`.CompositeProperty`
2494 properties maintained by this :class:`_orm.Mapper`.
2496 .. seealso::
2498 :attr:`_orm.Mapper.attrs` - namespace of all
2499 :class:`.MapperProperty`
2500 objects.
2502 """
2503 return self._filter_properties(properties.CompositeProperty)
2505 def _filter_properties(self, type_):
2506 if Mapper._new_mappers:
2507 configure_mappers()
2508 return util.ImmutableProperties(
2509 util.OrderedDict(
2510 (k, v) for k, v in self._props.items() if isinstance(v, type_)
2511 )
2512 )
2514 @_memoized_configured_property
2515 def _get_clause(self):
2516 """create a "get clause" based on the primary key. this is used
2517 by query.get() and many-to-one lazyloads to load this item
2518 by primary key.
2520 """
2521 params = [
2522 (primary_key, sql.bindparam(None, type_=primary_key.type))
2523 for primary_key in self.primary_key
2524 ]
2525 return (
2526 sql.and_(*[k == v for (k, v) in params]),
2527 util.column_dict(params),
2528 )
2530 @_memoized_configured_property
2531 def _equivalent_columns(self):
2532 """Create a map of all equivalent columns, based on
2533 the determination of column pairs that are equated to
2534 one another based on inherit condition. This is designed
2535 to work with the queries that util.polymorphic_union
2536 comes up with, which often don't include the columns from
2537 the base table directly (including the subclass table columns
2538 only).
2540 The resulting structure is a dictionary of columns mapped
2541 to lists of equivalent columns, e.g.::
2543 {
2544 tablea.col1:
2545 {tableb.col1, tablec.col1},
2546 tablea.col2:
2547 {tabled.col2}
2548 }
2550 """
2551 result = util.column_dict()
2553 def visit_binary(binary):
2554 if binary.operator == operators.eq:
2555 if binary.left in result:
2556 result[binary.left].add(binary.right)
2557 else:
2558 result[binary.left] = util.column_set((binary.right,))
2559 if binary.right in result:
2560 result[binary.right].add(binary.left)
2561 else:
2562 result[binary.right] = util.column_set((binary.left,))
2564 for mapper in self.base_mapper.self_and_descendants:
2565 if mapper.inherit_condition is not None:
2566 visitors.traverse(
2567 mapper.inherit_condition, {}, {"binary": visit_binary}
2568 )
2570 return result
2572 def _is_userland_descriptor(self, obj):
2573 if isinstance(
2574 obj,
2575 (
2576 _MappedAttribute,
2577 instrumentation.ClassManager,
2578 expression.ColumnElement,
2579 ),
2580 ):
2581 return False
2582 else:
2583 return True
2585 def _should_exclude(self, name, assigned_name, local, column):
2586 """determine whether a particular property should be implicitly
2587 present on the class.
2589 This occurs when properties are propagated from an inherited class, or
2590 are applied from the columns present in the mapped table.
2592 """
2594 # check for class-bound attributes and/or descriptors,
2595 # either local or from an inherited class
2596 if local:
2597 if self.class_.__dict__.get(
2598 assigned_name, None
2599 ) is not None and self._is_userland_descriptor(
2600 self.class_.__dict__[assigned_name]
2601 ):
2602 return True
2603 else:
2604 attr = self.class_manager._get_class_attr_mro(assigned_name, None)
2605 if attr is not None and self._is_userland_descriptor(attr):
2606 return True
2608 if (
2609 self.include_properties is not None
2610 and name not in self.include_properties
2611 and (column is None or column not in self.include_properties)
2612 ):
2613 self._log("not including property %s" % (name))
2614 return True
2616 if self.exclude_properties is not None and (
2617 name in self.exclude_properties
2618 or (column is not None and column in self.exclude_properties)
2619 ):
2620 self._log("excluding property %s" % (name))
2621 return True
2623 return False
2625 def common_parent(self, other):
2626 """Return true if the given mapper shares a
2627 common inherited parent as this mapper."""
2629 return self.base_mapper is other.base_mapper
2631 def _canload(self, state, allow_subtypes):
2632 s = self.primary_mapper()
2633 if self.polymorphic_on is not None or allow_subtypes:
2634 return _state_mapper(state).isa(s)
2635 else:
2636 return _state_mapper(state) is s
2638 def isa(self, other):
2639 """Return True if the this mapper inherits from the given mapper."""
2641 m = self
2642 while m and m is not other:
2643 m = m.inherits
2644 return bool(m)
2646 def iterate_to_root(self):
2647 m = self
2648 while m:
2649 yield m
2650 m = m.inherits
2652 @_memoized_configured_property
2653 def self_and_descendants(self):
2654 """The collection including this mapper and all descendant mappers.
2656 This includes not just the immediately inheriting mappers but
2657 all their inheriting mappers as well.
2659 """
2660 descendants = []
2661 stack = deque([self])
2662 while stack:
2663 item = stack.popleft()
2664 descendants.append(item)
2665 stack.extend(item._inheriting_mappers)
2666 return util.WeakSequence(descendants)
2668 def polymorphic_iterator(self):
2669 """Iterate through the collection including this mapper and
2670 all descendant mappers.
2672 This includes not just the immediately inheriting mappers but
2673 all their inheriting mappers as well.
2675 To iterate through an entire hierarchy, use
2676 ``mapper.base_mapper.polymorphic_iterator()``.
2678 """
2679 return iter(self.self_and_descendants)
2681 def primary_mapper(self):
2682 """Return the primary mapper corresponding to this mapper's class key
2683 (class)."""
2685 return self.class_manager.mapper
2687 @property
2688 def primary_base_mapper(self):
2689 return self.class_manager.mapper.base_mapper
2691 def _result_has_identity_key(self, result, adapter=None):
2692 pk_cols = self.primary_key
2693 if adapter:
2694 pk_cols = [adapter.columns[c] for c in pk_cols]
2695 for col in pk_cols:
2696 if not result._has_key(col):
2697 return False
2698 else:
2699 return True
2701 def identity_key_from_row(self, row, identity_token=None, adapter=None):
2702 """Return an identity-map key for use in storing/retrieving an
2703 item from the identity map.
2705 :param row: A :class:`.RowProxy` instance. The columns which are
2706 mapped by this :class:`_orm.Mapper` should be locatable in the row,
2707 preferably via the :class:`_schema.Column`
2708 object directly (as is the case
2709 when a :func:`_expression.select` construct is executed),
2710 or via string names of
2711 the form ``<tablename>_<colname>``.
2713 """
2714 pk_cols = self.primary_key
2715 if adapter:
2716 pk_cols = [adapter.columns[c] for c in pk_cols]
2718 return (
2719 self._identity_class,
2720 tuple(row[column] for column in pk_cols),
2721 identity_token,
2722 )
2724 def identity_key_from_primary_key(self, primary_key, identity_token=None):
2725 """Return an identity-map key for use in storing/retrieving an
2726 item from an identity map.
2728 :param primary_key: A list of values indicating the identifier.
2730 """
2731 return self._identity_class, tuple(primary_key), identity_token
2733 def identity_key_from_instance(self, instance):
2734 """Return the identity key for the given instance, based on
2735 its primary key attributes.
2737 If the instance's state is expired, calling this method
2738 will result in a database check to see if the object has been deleted.
2739 If the row no longer exists,
2740 :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised.
2742 This value is typically also found on the instance state under the
2743 attribute name `key`.
2745 """
2746 state = attributes.instance_state(instance)
2747 return self._identity_key_from_state(state, attributes.PASSIVE_OFF)
2749 def _identity_key_from_state(
2750 self, state, passive=attributes.PASSIVE_RETURN_NEVER_SET
2751 ):
2752 dict_ = state.dict
2753 manager = state.manager
2754 return (
2755 self._identity_class,
2756 tuple(
2757 [
2758 manager[prop.key].impl.get(state, dict_, passive)
2759 for prop in self._identity_key_props
2760 ]
2761 ),
2762 state.identity_token,
2763 )
2765 def primary_key_from_instance(self, instance):
2766 """Return the list of primary key values for the given
2767 instance.
2769 If the instance's state is expired, calling this method
2770 will result in a database check to see if the object has been deleted.
2771 If the row no longer exists,
2772 :class:`~sqlalchemy.orm.exc.ObjectDeletedError` is raised.
2774 """
2775 state = attributes.instance_state(instance)
2776 identity_key = self._identity_key_from_state(
2777 state, attributes.PASSIVE_OFF
2778 )
2779 return identity_key[1]
2781 @_memoized_configured_property
2782 def _persistent_sortkey_fn(self):
2783 key_fns = [col.type.sort_key_function for col in self.primary_key]
2785 if set(key_fns).difference([None]):
2787 def key(state):
2788 return tuple(
2789 key_fn(val) if key_fn is not None else val
2790 for key_fn, val in zip(key_fns, state.key[1])
2791 )
2793 else:
2795 def key(state):
2796 return state.key[1]
2798 return key
2800 @_memoized_configured_property
2801 def _identity_key_props(self):
2802 return [self._columntoproperty[col] for col in self.primary_key]
2804 @_memoized_configured_property
2805 def _all_pk_props(self):
2806 collection = set()
2807 for table in self.tables:
2808 collection.update(self._pks_by_table[table])
2809 return collection
2811 @_memoized_configured_property
2812 def _should_undefer_in_wildcard(self):
2813 cols = set(self.primary_key)
2814 if self.polymorphic_on is not None:
2815 cols.add(self.polymorphic_on)
2816 return cols
2818 @_memoized_configured_property
2819 def _primary_key_propkeys(self):
2820 return {prop.key for prop in self._all_pk_props}
2822 def _get_state_attr_by_column(
2823 self, state, dict_, column, passive=attributes.PASSIVE_RETURN_NEVER_SET
2824 ):
2825 prop = self._columntoproperty[column]
2826 return state.manager[prop.key].impl.get(state, dict_, passive=passive)
2828 def _set_committed_state_attr_by_column(self, state, dict_, column, value):
2829 prop = self._columntoproperty[column]
2830 state.manager[prop.key].impl.set_committed_value(state, dict_, value)
2832 def _set_state_attr_by_column(self, state, dict_, column, value):
2833 prop = self._columntoproperty[column]
2834 state.manager[prop.key].impl.set(state, dict_, value, None)
2836 def _get_committed_attr_by_column(self, obj, column):
2837 state = attributes.instance_state(obj)
2838 dict_ = attributes.instance_dict(obj)
2839 return self._get_committed_state_attr_by_column(
2840 state, dict_, column, passive=attributes.PASSIVE_OFF
2841 )
2843 def _get_committed_state_attr_by_column(
2844 self, state, dict_, column, passive=attributes.PASSIVE_RETURN_NEVER_SET
2845 ):
2847 prop = self._columntoproperty[column]
2848 return state.manager[prop.key].impl.get_committed_value(
2849 state, dict_, passive=passive
2850 )
2852 def _optimized_get_statement(self, state, attribute_names):
2853 """assemble a WHERE clause which retrieves a given state by primary
2854 key, using a minimized set of tables.
2856 Applies to a joined-table inheritance mapper where the
2857 requested attribute names are only present on joined tables,
2858 not the base table. The WHERE clause attempts to include
2859 only those tables to minimize joins.
2861 """
2862 props = self._props
2864 tables = set(
2865 chain(
2866 *[
2867 sql_util.find_tables(c, check_columns=True)
2868 for key in attribute_names
2869 for c in props[key].columns
2870 ]
2871 )
2872 )
2874 if self.base_mapper.local_table in tables:
2875 return None
2877 def visit_binary(binary):
2878 leftcol = binary.left
2879 rightcol = binary.right
2880 if leftcol is None or rightcol is None:
2881 return
2883 if leftcol.table not in tables:
2884 leftval = self._get_committed_state_attr_by_column(
2885 state,
2886 state.dict,
2887 leftcol,
2888 passive=attributes.PASSIVE_NO_INITIALIZE,
2889 )
2890 if leftval in orm_util._none_set:
2891 raise _OptGetColumnsNotAvailable()
2892 binary.left = sql.bindparam(
2893 None, leftval, type_=binary.right.type
2894 )
2895 elif rightcol.table not in tables:
2896 rightval = self._get_committed_state_attr_by_column(
2897 state,
2898 state.dict,
2899 rightcol,
2900 passive=attributes.PASSIVE_NO_INITIALIZE,
2901 )
2902 if rightval in orm_util._none_set:
2903 raise _OptGetColumnsNotAvailable()
2904 binary.right = sql.bindparam(
2905 None, rightval, type_=binary.right.type
2906 )
2908 allconds = []
2910 try:
2911 start = False
2912 for mapper in reversed(list(self.iterate_to_root())):
2913 if mapper.local_table in tables:
2914 start = True
2915 elif not isinstance(
2916 mapper.local_table, expression.TableClause
2917 ):
2918 return None
2919 if start and not mapper.single:
2920 allconds.append(
2921 visitors.cloned_traverse(
2922 mapper.inherit_condition,
2923 {},
2924 {"binary": visit_binary},
2925 )
2926 )
2927 except _OptGetColumnsNotAvailable:
2928 return None
2930 cond = sql.and_(*allconds)
2932 cols = []
2933 for key in attribute_names:
2934 cols.extend(props[key].columns)
2935 return sql.select(cols, cond, use_labels=True)
2937 def _iterate_to_target_viawpoly(self, mapper):
2938 if self.isa(mapper):
2939 prev = self
2940 for m in self.iterate_to_root():
2941 yield m
2943 if m is not prev and prev not in m._with_polymorphic_mappers:
2944 break
2946 prev = m
2947 if m is mapper:
2948 break
2950 def _should_selectin_load(self, enabled_via_opt, polymorphic_from):
2951 if not enabled_via_opt:
2952 # common case, takes place for all polymorphic loads
2953 mapper = polymorphic_from
2954 for m in self._iterate_to_target_viawpoly(mapper):
2955 if m.polymorphic_load == "selectin":
2956 return m
2957 else:
2958 # uncommon case, selectin load options were used
2959 enabled_via_opt = set(enabled_via_opt)
2960 enabled_via_opt_mappers = {e.mapper: e for e in enabled_via_opt}
2961 for entity in enabled_via_opt.union([polymorphic_from]):
2962 mapper = entity.mapper
2963 for m in self._iterate_to_target_viawpoly(mapper):
2964 if (
2965 m.polymorphic_load == "selectin"
2966 or m in enabled_via_opt_mappers
2967 ):
2968 return enabled_via_opt_mappers.get(m, m)
2970 return None
2972 @util.dependencies(
2973 "sqlalchemy.ext.baked", "sqlalchemy.orm.strategy_options"
2974 )
2975 def _subclass_load_via_in(self, baked, strategy_options, entity):
2976 """Assemble a BakedQuery that can load the columns local to
2977 this subclass as a SELECT with IN.
2979 """
2980 assert self.inherits
2982 polymorphic_prop = self._columntoproperty[self.polymorphic_on]
2983 keep_props = set([polymorphic_prop] + self._identity_key_props)
2985 disable_opt = strategy_options.Load(entity)
2986 enable_opt = strategy_options.Load(entity)
2988 for prop in self.attrs:
2989 if prop.parent is self or prop in keep_props:
2990 # "enable" options, to turn on the properties that we want to
2991 # load by default (subject to options from the query)
2992 enable_opt.set_generic_strategy(
2993 (prop.key,), dict(prop.strategy_key)
2994 )
2995 else:
2996 # "disable" options, to turn off the properties from the
2997 # superclass that we *don't* want to load, applied after
2998 # the options from the query to override them
2999 disable_opt.set_generic_strategy(
3000 (prop.key,), {"do_nothing": True}
3001 )
3003 if len(self.primary_key) > 1:
3004 in_expr = sql.tuple_(*self.primary_key)
3005 else:
3006 in_expr = self.primary_key[0]
3008 if entity.is_aliased_class:
3009 assert entity.mapper is self
3010 q = baked.BakedQuery(
3011 self._compiled_cache,
3012 lambda session: session.query(entity)
3013 .select_entity_from(entity.selectable)
3014 ._adapt_all_clauses(),
3015 (self,),
3016 )
3017 q.spoil()
3018 else:
3019 q = baked.BakedQuery(
3020 self._compiled_cache,
3021 lambda session: session.query(self),
3022 (self,),
3023 )
3025 q += lambda q: q.filter(
3026 in_expr.in_(sql.bindparam("primary_keys", expanding=True))
3027 ).order_by(*self.primary_key)
3029 return q, enable_opt, disable_opt
3031 @_memoized_configured_property
3032 def _subclass_load_via_in_mapper(self):
3033 return self._subclass_load_via_in(self)
3035 def cascade_iterator(self, type_, state, halt_on=None):
3036 r"""Iterate each element and its mapper in an object graph,
3037 for all relationships that meet the given cascade rule.
3039 :param type\_:
3040 The name of the cascade rule (i.e. ``"save-update"``, ``"delete"``,
3041 etc.).
3043 .. note:: the ``"all"`` cascade is not accepted here. For a generic
3044 object traversal function, see :ref:`faq_walk_objects`.
3046 :param state:
3047 The lead InstanceState. child items will be processed per
3048 the relationships defined for this object's mapper.
3050 :return: the method yields individual object instances.
3052 .. seealso::
3054 :ref:`unitofwork_cascades`
3056 :ref:`faq_walk_objects` - illustrates a generic function to
3057 traverse all objects without relying on cascades.
3059 """
3060 visited_states = set()
3061 prp, mpp = object(), object()
3063 assert state.mapper.isa(self)
3065 visitables = deque(
3066 [(deque(state.mapper._props.values()), prp, state, state.dict)]
3067 )
3069 while visitables:
3070 iterator, item_type, parent_state, parent_dict = visitables[-1]
3071 if not iterator:
3072 visitables.pop()
3073 continue
3075 if item_type is prp:
3076 prop = iterator.popleft()
3077 if type_ not in prop.cascade:
3078 continue
3079 queue = deque(
3080 prop.cascade_iterator(
3081 type_,
3082 parent_state,
3083 parent_dict,
3084 visited_states,
3085 halt_on,
3086 )
3087 )
3088 if queue:
3089 visitables.append((queue, mpp, None, None))
3090 elif item_type is mpp:
3091 (
3092 instance,
3093 instance_mapper,
3094 corresponding_state,
3095 corresponding_dict,
3096 ) = iterator.popleft()
3097 yield (
3098 instance,
3099 instance_mapper,
3100 corresponding_state,
3101 corresponding_dict,
3102 )
3103 visitables.append(
3104 (
3105 deque(instance_mapper._props.values()),
3106 prp,
3107 corresponding_state,
3108 corresponding_dict,
3109 )
3110 )
3112 @_memoized_configured_property
3113 def _compiled_cache(self):
3114 return util.LRUCache(self._compiled_cache_size)
3116 @_memoized_configured_property
3117 def _sorted_tables(self):
3118 table_to_mapper = {}
3120 for mapper in self.base_mapper.self_and_descendants:
3121 for t in mapper.tables:
3122 table_to_mapper.setdefault(t, mapper)
3124 extra_dependencies = []
3125 for table, mapper in table_to_mapper.items():
3126 super_ = mapper.inherits
3127 if super_:
3128 extra_dependencies.extend(
3129 [(super_table, table) for super_table in super_.tables]
3130 )
3132 def skip(fk):
3133 # attempt to skip dependencies that are not
3134 # significant to the inheritance chain
3135 # for two tables that are related by inheritance.
3136 # while that dependency may be important, it's technically
3137 # not what we mean to sort on here.
3138 parent = table_to_mapper.get(fk.parent.table)
3139 dep = table_to_mapper.get(fk.column.table)
3140 if (
3141 parent is not None
3142 and dep is not None
3143 and dep is not parent
3144 and dep.inherit_condition is not None
3145 ):
3146 cols = set(sql_util._find_columns(dep.inherit_condition))
3147 if parent.inherit_condition is not None:
3148 cols = cols.union(
3149 sql_util._find_columns(parent.inherit_condition)
3150 )
3151 return fk.parent not in cols and fk.column not in cols
3152 else:
3153 return fk.parent not in cols
3154 return False
3156 sorted_ = sql_util.sort_tables(
3157 table_to_mapper,
3158 skip_fn=skip,
3159 extra_dependencies=extra_dependencies,
3160 )
3162 ret = util.OrderedDict()
3163 for t in sorted_:
3164 ret[t] = table_to_mapper[t]
3165 return ret
3167 def _memo(self, key, callable_):
3168 if key in self._memoized_values:
3169 return self._memoized_values[key]
3170 else:
3171 self._memoized_values[key] = value = callable_()
3172 return value
3174 @util.memoized_property
3175 def _table_to_equated(self):
3176 """memoized map of tables to collections of columns to be
3177 synchronized upwards to the base mapper."""
3179 result = util.defaultdict(list)
3181 for table in self._sorted_tables:
3182 cols = set(table.c)
3183 for m in self.iterate_to_root():
3184 if m._inherits_equated_pairs and cols.intersection(
3185 util.reduce(
3186 set.union,
3187 [l.proxy_set for l, r in m._inherits_equated_pairs],
3188 )
3189 ):
3190 result[table].append((m, m._inherits_equated_pairs))
3192 return result
3195class _OptGetColumnsNotAvailable(Exception):
3196 pass
3199def configure_mappers():
3200 """Initialize the inter-mapper relationships of all mappers that
3201 have been constructed thus far.
3203 This function can be called any number of times, but in
3204 most cases is invoked automatically, the first time mappings are used,
3205 as well as whenever mappings are used and additional not-yet-configured
3206 mappers have been constructed.
3208 Points at which this occur include when a mapped class is instantiated
3209 into an instance, as well as when the :meth:`.Session.query` method
3210 is used.
3212 The :func:`.configure_mappers` function provides several event hooks
3213 that can be used to augment its functionality. These methods include:
3215 * :meth:`.MapperEvents.before_configured` - called once before
3216 :func:`.configure_mappers` does any work; this can be used to establish
3217 additional options, properties, or related mappings before the operation
3218 proceeds.
3220 * :meth:`.MapperEvents.mapper_configured` - called as each individual
3221 :class:`_orm.Mapper` is configured within the process; will include all
3222 mapper state except for backrefs set up by other mappers that are still
3223 to be configured.
3225 * :meth:`.MapperEvents.after_configured` - called once after
3226 :func:`.configure_mappers` is complete; at this stage, all
3227 :class:`_orm.Mapper` objects that are known to SQLAlchemy will be fully
3228 configured. Note that the calling application may still have other
3229 mappings that haven't been produced yet, such as if they are in modules
3230 as yet unimported.
3232 """
3234 if not Mapper._new_mappers:
3235 return
3237 _CONFIGURE_MUTEX.acquire()
3238 try:
3239 global _already_compiling
3240 if _already_compiling:
3241 return
3242 _already_compiling = True
3243 try:
3245 # double-check inside mutex
3246 if not Mapper._new_mappers:
3247 return
3249 has_skip = False
3251 Mapper.dispatch._for_class(Mapper).before_configured()
3252 # initialize properties on all mappers
3253 # note that _mapper_registry is unordered, which
3254 # may randomly conceal/reveal issues related to
3255 # the order of mapper compilation
3257 for mapper in list(_mapper_registry):
3258 run_configure = None
3259 for fn in mapper.dispatch.before_mapper_configured:
3260 run_configure = fn(mapper, mapper.class_)
3261 if run_configure is EXT_SKIP:
3262 has_skip = True
3263 break
3264 if run_configure is EXT_SKIP:
3265 continue
3267 if getattr(mapper, "_configure_failed", False):
3268 e = sa_exc.InvalidRequestError(
3269 "One or more mappers failed to initialize - "
3270 "can't proceed with initialization of other "
3271 "mappers. Triggering mapper: '%s'. "
3272 "Original exception was: %s"
3273 % (mapper, mapper._configure_failed)
3274 )
3275 e._configure_failed = mapper._configure_failed
3276 raise e
3278 if not mapper.configured:
3279 try:
3280 mapper._post_configure_properties()
3281 mapper._expire_memoizations()
3282 mapper.dispatch.mapper_configured(
3283 mapper, mapper.class_
3284 )
3285 except Exception:
3286 exc = sys.exc_info()[1]
3287 if not hasattr(exc, "_configure_failed"):
3288 mapper._configure_failed = exc
3289 raise
3291 if not has_skip:
3292 Mapper._new_mappers = False
3293 finally:
3294 _already_compiling = False
3295 finally:
3296 _CONFIGURE_MUTEX.release()
3297 Mapper.dispatch._for_class(Mapper).after_configured()
3300def reconstructor(fn):
3301 """Decorate a method as the 'reconstructor' hook.
3303 Designates a method as the "reconstructor", an ``__init__``-like
3304 method that will be called by the ORM after the instance has been
3305 loaded from the database or otherwise reconstituted.
3307 The reconstructor will be invoked with no arguments. Scalar
3308 (non-collection) database-mapped attributes of the instance will
3309 be available for use within the function. Eagerly-loaded
3310 collections are generally not yet available and will usually only
3311 contain the first element. ORM state changes made to objects at
3312 this stage will not be recorded for the next flush() operation, so
3313 the activity within a reconstructor should be conservative.
3315 .. seealso::
3317 :ref:`mapping_constructors`
3319 :meth:`.InstanceEvents.load`
3321 """
3322 fn.__sa_reconstructor__ = True
3323 return fn
3326def validates(*names, **kw):
3327 r"""Decorate a method as a 'validator' for one or more named properties.
3329 Designates a method as a validator, a method which receives the
3330 name of the attribute as well as a value to be assigned, or in the
3331 case of a collection, the value to be added to the collection.
3332 The function can then raise validation exceptions to halt the
3333 process from continuing (where Python's built-in ``ValueError``
3334 and ``AssertionError`` exceptions are reasonable choices), or can
3335 modify or replace the value before proceeding. The function should
3336 otherwise return the given value.
3338 Note that a validator for a collection **cannot** issue a load of that
3339 collection within the validation routine - this usage raises
3340 an assertion to avoid recursion overflows. This is a reentrant
3341 condition which is not supported.
3343 :param \*names: list of attribute names to be validated.
3344 :param include_removes: if True, "remove" events will be
3345 sent as well - the validation function must accept an additional
3346 argument "is_remove" which will be a boolean.
3348 :param include_backrefs: defaults to ``True``; if ``False``, the
3349 validation function will not emit if the originator is an attribute
3350 event related via a backref. This can be used for bi-directional
3351 :func:`.validates` usage where only one validator should emit per
3352 attribute operation.
3354 .. versionadded:: 0.9.0
3356 .. seealso::
3358 :ref:`simple_validators` - usage examples for :func:`.validates`
3360 """
3361 include_removes = kw.pop("include_removes", False)
3362 include_backrefs = kw.pop("include_backrefs", True)
3364 def wrap(fn):
3365 fn.__sa_validators__ = names
3366 fn.__sa_validation_opts__ = {
3367 "include_removes": include_removes,
3368 "include_backrefs": include_backrefs,
3369 }
3370 return fn
3372 return wrap
3375def _event_on_load(state, ctx):
3376 instrumenting_mapper = state.manager.info[_INSTRUMENTOR]
3377 if instrumenting_mapper._reconstructor:
3378 instrumenting_mapper._reconstructor(state.obj())
3381def _event_on_first_init(manager, cls):
3382 """Initial mapper compilation trigger.
3384 instrumentation calls this one when InstanceState
3385 is first generated, and is needed for legacy mutable
3386 attributes to work.
3387 """
3389 instrumenting_mapper = manager.info.get(_INSTRUMENTOR)
3390 if instrumenting_mapper:
3391 if Mapper._new_mappers:
3392 configure_mappers()
3395def _event_on_init(state, args, kwargs):
3396 """Run init_instance hooks.
3398 This also includes mapper compilation, normally not needed
3399 here but helps with some piecemeal configuration
3400 scenarios (such as in the ORM tutorial).
3402 """
3404 instrumenting_mapper = state.manager.info.get(_INSTRUMENTOR)
3405 if instrumenting_mapper:
3406 if Mapper._new_mappers:
3407 configure_mappers()
3408 if instrumenting_mapper._set_polymorphic_identity:
3409 instrumenting_mapper._set_polymorphic_identity(state)
3412class _ColumnMapping(dict):
3413 """Error reporting helper for mapper._columntoproperty."""
3415 __slots__ = ("mapper",)
3417 def __init__(self, mapper):
3418 self.mapper = mapper
3420 def __missing__(self, column):
3421 prop = self.mapper._props.get(column)
3422 if prop:
3423 raise orm_exc.UnmappedColumnError(
3424 "Column '%s.%s' is not available, due to "
3425 "conflicting property '%s':%r"
3426 % (column.table.name, column.name, column.key, prop)
3427 )
3428 raise orm_exc.UnmappedColumnError(
3429 "No column %s is configured on mapper %s..."
3430 % (column, self.mapper)
3431 )