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

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/relationships.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"""Heuristics related to join conditions as used in
9:func:`_orm.relationship`.
11Provides the :class:`.JoinCondition` object, which encapsulates
12SQL annotation and aliasing behavior focused on the `primaryjoin`
13and `secondaryjoin` aspects of :func:`_orm.relationship`.
15"""
16from __future__ import absolute_import
18import collections
19import weakref
21from . import attributes
22from . import dependency
23from . import mapper as mapperlib
24from .base import state_str
25from .interfaces import MANYTOMANY
26from .interfaces import MANYTOONE
27from .interfaces import ONETOMANY
28from .interfaces import PropComparator
29from .interfaces import StrategizedProperty
30from .util import _orm_annotate
31from .util import _orm_deannotate
32from .util import CascadeOptions
33from .. import exc as sa_exc
34from .. import log
35from .. import schema
36from .. import sql
37from .. import util
38from ..inspection import inspect
39from ..sql import expression
40from ..sql import operators
41from ..sql import visitors
42from ..sql.util import _deep_deannotate
43from ..sql.util import _shallow_annotate
44from ..sql.util import adapt_criterion_to_null
45from ..sql.util import ClauseAdapter
46from ..sql.util import join_condition
47from ..sql.util import selectables_overlap
48from ..sql.util import visit_binary_product
51def remote(expr):
52 """Annotate a portion of a primaryjoin expression
53 with a 'remote' annotation.
55 See the section :ref:`relationship_custom_foreign` for a
56 description of use.
58 .. seealso::
60 :ref:`relationship_custom_foreign`
62 :func:`.foreign`
64 """
65 return _annotate_columns(
66 expression._clause_element_as_expr(expr), {"remote": True}
67 )
70def foreign(expr):
71 """Annotate a portion of a primaryjoin expression
72 with a 'foreign' annotation.
74 See the section :ref:`relationship_custom_foreign` for a
75 description of use.
77 .. seealso::
79 :ref:`relationship_custom_foreign`
81 :func:`.remote`
83 """
85 return _annotate_columns(
86 expression._clause_element_as_expr(expr), {"foreign": True}
87 )
90@log.class_logger
91@util.langhelpers.dependency_for("sqlalchemy.orm.properties", add_to_all=True)
92class RelationshipProperty(StrategizedProperty):
93 """Describes an object property that holds a single item or list
94 of items that correspond to a related database table.
96 Public constructor is the :func:`_orm.relationship` function.
98 .. seealso::
100 :ref:`relationship_config_toplevel`
102 """
104 strategy_wildcard_key = "relationship"
106 _persistence_only = dict(
107 passive_deletes=False,
108 passive_updates=True,
109 enable_typechecks=True,
110 active_history=False,
111 cascade_backrefs=True,
112 )
114 _dependency_processor = None
116 @util.deprecated_params(
117 extension=(
118 "0.7",
119 ":class:`.AttributeExtension` is deprecated in favor of the "
120 ":class:`.AttributeEvents` listener interface. The "
121 ":paramref:`_orm.relationship.extension` parameter will be "
122 "removed in a future release.",
123 )
124 )
125 def __init__(
126 self,
127 argument,
128 secondary=None,
129 primaryjoin=None,
130 secondaryjoin=None,
131 foreign_keys=None,
132 uselist=None,
133 order_by=False,
134 backref=None,
135 back_populates=None,
136 post_update=False,
137 cascade=False,
138 extension=None,
139 viewonly=False,
140 lazy="select",
141 collection_class=None,
142 passive_deletes=_persistence_only["passive_deletes"],
143 passive_updates=_persistence_only["passive_updates"],
144 remote_side=None,
145 enable_typechecks=_persistence_only["enable_typechecks"],
146 join_depth=None,
147 comparator_factory=None,
148 single_parent=False,
149 innerjoin=False,
150 distinct_target_key=None,
151 doc=None,
152 active_history=_persistence_only["active_history"],
153 cascade_backrefs=_persistence_only["cascade_backrefs"],
154 load_on_pending=False,
155 bake_queries=True,
156 _local_remote_pairs=None,
157 query_class=None,
158 info=None,
159 omit_join=None,
160 sync_backref=None,
161 ):
162 """Provide a relationship between two mapped classes.
164 This corresponds to a parent-child or associative table relationship.
165 The constructed class is an instance of
166 :class:`.RelationshipProperty`.
168 A typical :func:`_orm.relationship`, used in a classical mapping::
170 mapper(Parent, properties={
171 'children': relationship(Child)
172 })
174 Some arguments accepted by :func:`_orm.relationship`
175 optionally accept a
176 callable function, which when called produces the desired value.
177 The callable is invoked by the parent :class:`_orm.Mapper` at "mapper
178 initialization" time, which happens only when mappers are first used,
179 and is assumed to be after all mappings have been constructed. This
180 can be used to resolve order-of-declaration and other dependency
181 issues, such as if ``Child`` is declared below ``Parent`` in the same
182 file::
184 mapper(Parent, properties={
185 "children":relationship(lambda: Child,
186 order_by=lambda: Child.id)
187 })
189 When using the :ref:`declarative_toplevel` extension, the Declarative
190 initializer allows string arguments to be passed to
191 :func:`_orm.relationship`. These string arguments are converted into
192 callables that evaluate the string as Python code, using the
193 Declarative class-registry as a namespace. This allows the lookup of
194 related classes to be automatic via their string name, and removes the
195 need for related classes to be imported into the local module space
196 before the dependent classes have been declared. It is still required
197 that the modules in which these related classes appear are imported
198 anywhere in the application at some point before the related mappings
199 are actually used, else a lookup error will be raised when the
200 :func:`_orm.relationship`
201 attempts to resolve the string reference to the
202 related class. An example of a string- resolved class is as
203 follows::
205 from sqlalchemy.ext.declarative import declarative_base
207 Base = declarative_base()
209 class Parent(Base):
210 __tablename__ = 'parent'
211 id = Column(Integer, primary_key=True)
212 children = relationship("Child", order_by="Child.id")
214 .. seealso::
216 :ref:`relationship_config_toplevel` - Full introductory and
217 reference documentation for :func:`_orm.relationship`.
219 :ref:`orm_tutorial_relationship` - ORM tutorial introduction.
221 :param argument:
222 A mapped class, or actual :class:`_orm.Mapper` instance,
223 representing
224 the target of the relationship.
226 :paramref:`_orm.relationship.argument`
227 may also be passed as a callable
228 function which is evaluated at mapper initialization time, and may
229 be passed as a string name when using Declarative.
231 .. warning:: Prior to SQLAlchemy 1.3.16, this value is interpreted
232 using Python's ``eval()`` function.
233 **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**.
234 See :ref:`declarative_relationship_eval` for details on
235 declarative evaluation of :func:`_orm.relationship` arguments.
237 .. versionchanged 1.3.16::
239 The string evaluation of the main "argument" no longer accepts an
240 open ended Python expression, instead only accepting a string
241 class name or dotted package-qualified name.
243 .. seealso::
245 :ref:`declarative_configuring_relationships` - further detail
246 on relationship configuration when using Declarative.
248 :param secondary:
249 For a many-to-many relationship, specifies the intermediary
250 table, and is typically an instance of :class:`_schema.Table`.
251 In less common circumstances, the argument may also be specified
252 as an :class:`_expression.Alias` construct, or even a
253 :class:`_expression.Join` construct.
255 :paramref:`_orm.relationship.secondary` may
256 also be passed as a callable function which is evaluated at
257 mapper initialization time. When using Declarative, it may also
258 be a string argument noting the name of a :class:`_schema.Table`
259 that is
260 present in the :class:`_schema.MetaData`
261 collection associated with the
262 parent-mapped :class:`_schema.Table`.
264 .. warning:: When passed as a Python-evaluable string, the
265 argument is interpreted using Python's ``eval()`` function.
266 **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**.
267 See :ref:`declarative_relationship_eval` for details on
268 declarative evaluation of :func:`_orm.relationship` arguments.
270 The :paramref:`_orm.relationship.secondary` keyword argument is
271 typically applied in the case where the intermediary
272 :class:`_schema.Table`
273 is not otherwise expressed in any direct class mapping. If the
274 "secondary" table is also explicitly mapped elsewhere (e.g. as in
275 :ref:`association_pattern`), one should consider applying the
276 :paramref:`_orm.relationship.viewonly` flag so that this
277 :func:`_orm.relationship`
278 is not used for persistence operations which
279 may conflict with those of the association object pattern.
281 .. seealso::
283 :ref:`relationships_many_to_many` - Reference example of "many
284 to many".
286 :ref:`orm_tutorial_many_to_many` - ORM tutorial introduction to
287 many-to-many relationships.
289 :ref:`self_referential_many_to_many` - Specifics on using
290 many-to-many in a self-referential case.
292 :ref:`declarative_many_to_many` - Additional options when using
293 Declarative.
295 :ref:`association_pattern` - an alternative to
296 :paramref:`_orm.relationship.secondary`
297 when composing association
298 table relationships, allowing additional attributes to be
299 specified on the association table.
301 :ref:`composite_secondary_join` - a lesser-used pattern which
302 in some cases can enable complex :func:`_orm.relationship` SQL
303 conditions to be used.
305 .. versionadded:: 0.9.2 :paramref:`_orm.relationship.secondary`
306 works
307 more effectively when referring to a :class:`_expression.Join`
308 instance.
310 :param active_history=False:
311 When ``True``, indicates that the "previous" value for a
312 many-to-one reference should be loaded when replaced, if
313 not already loaded. Normally, history tracking logic for
314 simple many-to-ones only needs to be aware of the "new"
315 value in order to perform a flush. This flag is available
316 for applications that make use of
317 :func:`.attributes.get_history` which also need to know
318 the "previous" value of the attribute.
320 :param backref:
321 Indicates the string name of a property to be placed on the related
322 mapper's class that will handle this relationship in the other
323 direction. The other property will be created automatically
324 when the mappers are configured. Can also be passed as a
325 :func:`.backref` object to control the configuration of the
326 new relationship.
328 .. seealso::
330 :ref:`relationships_backref` - Introductory documentation and
331 examples.
333 :paramref:`_orm.relationship.back_populates` - alternative form
334 of backref specification.
336 :func:`.backref` - allows control over :func:`_orm.relationship`
337 configuration when using :paramref:`_orm.relationship.backref`.
340 :param back_populates:
341 Takes a string name and has the same meaning as
342 :paramref:`_orm.relationship.backref`, except the complementing
343 property is **not** created automatically, and instead must be
344 configured explicitly on the other mapper. The complementing
345 property should also indicate
346 :paramref:`_orm.relationship.back_populates` to this relationship to
347 ensure proper functioning.
349 .. seealso::
351 :ref:`relationships_backref` - Introductory documentation and
352 examples.
354 :paramref:`_orm.relationship.backref` - alternative form
355 of backref specification.
357 :param bake_queries=True:
358 Use the :class:`.BakedQuery` cache to cache the construction of SQL
359 used in lazy loads. True by default. Set to False if the
360 join condition of the relationship has unusual features that
361 might not respond well to statement caching.
363 .. versionchanged:: 1.2
364 "Baked" loading is the default implementation for the "select",
365 a.k.a. "lazy" loading strategy for relationships.
367 .. versionadded:: 1.0.0
369 .. seealso::
371 :ref:`baked_toplevel`
373 :param cascade:
374 A comma-separated list of cascade rules which determines how
375 Session operations should be "cascaded" from parent to child.
376 This defaults to ``False``, which means the default cascade
377 should be used - this default cascade is ``"save-update, merge"``.
379 The available cascades are ``save-update``, ``merge``,
380 ``expunge``, ``delete``, ``delete-orphan``, and ``refresh-expire``.
381 An additional option, ``all`` indicates shorthand for
382 ``"save-update, merge, refresh-expire,
383 expunge, delete"``, and is often used as in ``"all, delete-orphan"``
384 to indicate that related objects should follow along with the
385 parent object in all cases, and be deleted when de-associated.
387 .. seealso::
389 :ref:`unitofwork_cascades` - Full detail on each of the available
390 cascade options.
392 :ref:`tutorial_delete_cascade` - Tutorial example describing
393 a delete cascade.
395 :param cascade_backrefs=True:
396 A boolean value indicating if the ``save-update`` cascade should
397 operate along an assignment event intercepted by a backref.
398 When set to ``False``, the attribute managed by this relationship
399 will not cascade an incoming transient object into the session of a
400 persistent parent, if the event is received via backref.
402 .. seealso::
404 :ref:`backref_cascade` - Full discussion and examples on how
405 the :paramref:`_orm.relationship.cascade_backrefs` option is used.
407 :param collection_class:
408 A class or callable that returns a new list-holding object. will
409 be used in place of a plain list for storing elements.
411 .. seealso::
413 :ref:`custom_collections` - Introductory documentation and
414 examples.
416 :param comparator_factory:
417 A class which extends :class:`.RelationshipProperty.Comparator`
418 which provides custom SQL clause generation for comparison
419 operations.
421 .. seealso::
423 :class:`.PropComparator` - some detail on redefining comparators
424 at this level.
426 :ref:`custom_comparators` - Brief intro to this feature.
429 :param distinct_target_key=None:
430 Indicate if a "subquery" eager load should apply the DISTINCT
431 keyword to the innermost SELECT statement. When left as ``None``,
432 the DISTINCT keyword will be applied in those cases when the target
433 columns do not comprise the full primary key of the target table.
434 When set to ``True``, the DISTINCT keyword is applied to the
435 innermost SELECT unconditionally.
437 It may be desirable to set this flag to False when the DISTINCT is
438 reducing performance of the innermost subquery beyond that of what
439 duplicate innermost rows may be causing.
441 .. versionchanged:: 0.9.0 -
442 :paramref:`_orm.relationship.distinct_target_key` now defaults to
443 ``None``, so that the feature enables itself automatically for
444 those cases where the innermost query targets a non-unique
445 key.
447 .. seealso::
449 :ref:`loading_toplevel` - includes an introduction to subquery
450 eager loading.
452 :param doc:
453 Docstring which will be applied to the resulting descriptor.
455 :param extension:
456 an :class:`.AttributeExtension` instance, or list of extensions,
457 which will be prepended to the list of attribute listeners for
458 the resulting descriptor placed on the class.
460 :param foreign_keys:
462 A list of columns which are to be used as "foreign key"
463 columns, or columns which refer to the value in a remote
464 column, within the context of this :func:`_orm.relationship`
465 object's :paramref:`_orm.relationship.primaryjoin` condition.
466 That is, if the :paramref:`_orm.relationship.primaryjoin`
467 condition of this :func:`_orm.relationship` is ``a.id ==
468 b.a_id``, and the values in ``b.a_id`` are required to be
469 present in ``a.id``, then the "foreign key" column of this
470 :func:`_orm.relationship` is ``b.a_id``.
472 In normal cases, the :paramref:`_orm.relationship.foreign_keys`
473 parameter is **not required.** :func:`_orm.relationship` will
474 automatically determine which columns in the
475 :paramref:`_orm.relationship.primaryjoin` condition are to be
476 considered "foreign key" columns based on those
477 :class:`_schema.Column` objects that specify
478 :class:`_schema.ForeignKey`,
479 or are otherwise listed as referencing columns in a
480 :class:`_schema.ForeignKeyConstraint` construct.
481 :paramref:`_orm.relationship.foreign_keys` is only needed when:
483 1. There is more than one way to construct a join from the local
484 table to the remote table, as there are multiple foreign key
485 references present. Setting ``foreign_keys`` will limit the
486 :func:`_orm.relationship`
487 to consider just those columns specified
488 here as "foreign".
490 2. The :class:`_schema.Table` being mapped does not actually have
491 :class:`_schema.ForeignKey` or
492 :class:`_schema.ForeignKeyConstraint`
493 constructs present, often because the table
494 was reflected from a database that does not support foreign key
495 reflection (MySQL MyISAM).
497 3. The :paramref:`_orm.relationship.primaryjoin`
498 argument is used to
499 construct a non-standard join condition, which makes use of
500 columns or expressions that do not normally refer to their
501 "parent" column, such as a join condition expressed by a
502 complex comparison using a SQL function.
504 The :func:`_orm.relationship` construct will raise informative
505 error messages that suggest the use of the
506 :paramref:`_orm.relationship.foreign_keys` parameter when
507 presented with an ambiguous condition. In typical cases,
508 if :func:`_orm.relationship` doesn't raise any exceptions, the
509 :paramref:`_orm.relationship.foreign_keys` parameter is usually
510 not needed.
512 :paramref:`_orm.relationship.foreign_keys` may also be passed as a
513 callable function which is evaluated at mapper initialization time,
514 and may be passed as a Python-evaluable string when using
515 Declarative.
517 .. warning:: When passed as a Python-evaluable string, the
518 argument is interpreted using Python's ``eval()`` function.
519 **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**.
520 See :ref:`declarative_relationship_eval` for details on
521 declarative evaluation of :func:`_orm.relationship` arguments.
523 .. seealso::
525 :ref:`relationship_foreign_keys`
527 :ref:`relationship_custom_foreign`
529 :func:`.foreign` - allows direct annotation of the "foreign"
530 columns within a :paramref:`_orm.relationship.primaryjoin`
531 condition.
533 :param info: Optional data dictionary which will be populated into the
534 :attr:`.MapperProperty.info` attribute of this object.
536 :param innerjoin=False:
537 When ``True``, joined eager loads will use an inner join to join
538 against related tables instead of an outer join. The purpose
539 of this option is generally one of performance, as inner joins
540 generally perform better than outer joins.
542 This flag can be set to ``True`` when the relationship references an
543 object via many-to-one using local foreign keys that are not
544 nullable, or when the reference is one-to-one or a collection that
545 is guaranteed to have one or at least one entry.
547 The option supports the same "nested" and "unnested" options as
548 that of :paramref:`_orm.joinedload.innerjoin`. See that flag
549 for details on nested / unnested behaviors.
551 .. seealso::
553 :paramref:`_orm.joinedload.innerjoin` - the option as specified by
554 loader option, including detail on nesting behavior.
556 :ref:`what_kind_of_loading` - Discussion of some details of
557 various loader options.
560 :param join_depth:
561 When non-``None``, an integer value indicating how many levels
562 deep "eager" loaders should join on a self-referring or cyclical
563 relationship. The number counts how many times the same Mapper
564 shall be present in the loading condition along a particular join
565 branch. When left at its default of ``None``, eager loaders
566 will stop chaining when they encounter a the same target mapper
567 which is already higher up in the chain. This option applies
568 both to joined- and subquery- eager loaders.
570 .. seealso::
572 :ref:`self_referential_eager_loading` - Introductory documentation
573 and examples.
575 :param lazy='select': specifies
576 How the related items should be loaded. Default value is
577 ``select``. Values include:
579 * ``select`` - items should be loaded lazily when the property is
580 first accessed, using a separate SELECT statement, or identity map
581 fetch for simple many-to-one references.
583 * ``immediate`` - items should be loaded as the parents are loaded,
584 using a separate SELECT statement, or identity map fetch for
585 simple many-to-one references.
587 * ``joined`` - items should be loaded "eagerly" in the same query as
588 that of the parent, using a JOIN or LEFT OUTER JOIN. Whether
589 the join is "outer" or not is determined by the
590 :paramref:`_orm.relationship.innerjoin` parameter.
592 * ``subquery`` - items should be loaded "eagerly" as the parents are
593 loaded, using one additional SQL statement, which issues a JOIN to
594 a subquery of the original statement, for each collection
595 requested.
597 * ``selectin`` - items should be loaded "eagerly" as the parents
598 are loaded, using one or more additional SQL statements, which
599 issues a JOIN to the immediate parent object, specifying primary
600 key identifiers using an IN clause.
602 .. versionadded:: 1.2
604 * ``noload`` - no loading should occur at any time. This is to
605 support "write-only" attributes, or attributes which are
606 populated in some manner specific to the application.
608 * ``raise`` - lazy loading is disallowed; accessing
609 the attribute, if its value were not already loaded via eager
610 loading, will raise an :exc:`~sqlalchemy.exc.InvalidRequestError`.
611 This strategy can be used when objects are to be detached from
612 their attached :class:`.Session` after they are loaded.
614 .. versionadded:: 1.1
616 * ``raise_on_sql`` - lazy loading that emits SQL is disallowed;
617 accessing the attribute, if its value were not already loaded via
618 eager loading, will raise an
619 :exc:`~sqlalchemy.exc.InvalidRequestError`, **if the lazy load
620 needs to emit SQL**. If the lazy load can pull the related value
621 from the identity map or determine that it should be None, the
622 value is loaded. This strategy can be used when objects will
623 remain associated with the attached :class:`.Session`, however
624 additional SELECT statements should be blocked.
626 .. versionadded:: 1.1
628 * ``dynamic`` - the attribute will return a pre-configured
629 :class:`_query.Query` object for all read
630 operations, onto which further filtering operations can be
631 applied before iterating the results. See
632 the section :ref:`dynamic_relationship` for more details.
634 * True - a synonym for 'select'
636 * False - a synonym for 'joined'
638 * None - a synonym for 'noload'
640 .. seealso::
642 :doc:`/orm/loading_relationships` - Full documentation on
643 relationship loader configuration.
645 :ref:`dynamic_relationship` - detail on the ``dynamic`` option.
647 :ref:`collections_noload_raiseload` - notes on "noload" and "raise"
649 :param load_on_pending=False:
650 Indicates loading behavior for transient or pending parent objects.
652 When set to ``True``, causes the lazy-loader to
653 issue a query for a parent object that is not persistent, meaning it
654 has never been flushed. This may take effect for a pending object
655 when autoflush is disabled, or for a transient object that has been
656 "attached" to a :class:`.Session` but is not part of its pending
657 collection.
659 The :paramref:`_orm.relationship.load_on_pending`
660 flag does not improve
661 behavior when the ORM is used normally - object references should be
662 constructed at the object level, not at the foreign key level, so
663 that they are present in an ordinary way before a flush proceeds.
664 This flag is not not intended for general use.
666 .. seealso::
668 :meth:`.Session.enable_relationship_loading` - this method
669 establishes "load on pending" behavior for the whole object, and
670 also allows loading on objects that remain transient or
671 detached.
673 :param order_by:
674 Indicates the ordering that should be applied when loading these
675 items. :paramref:`_orm.relationship.order_by`
676 is expected to refer to
677 one of the :class:`_schema.Column`
678 objects to which the target class is
679 mapped, or the attribute itself bound to the target class which
680 refers to the column.
682 :paramref:`_orm.relationship.order_by`
683 may also be passed as a callable
684 function which is evaluated at mapper initialization time, and may
685 be passed as a Python-evaluable string when using Declarative.
687 .. warning:: When passed as a Python-evaluable string, the
688 argument is interpreted using Python's ``eval()`` function.
689 **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**.
690 See :ref:`declarative_relationship_eval` for details on
691 declarative evaluation of :func:`_orm.relationship` arguments.
693 :param passive_deletes=False:
694 Indicates loading behavior during delete operations.
696 A value of True indicates that unloaded child items should not
697 be loaded during a delete operation on the parent. Normally,
698 when a parent item is deleted, all child items are loaded so
699 that they can either be marked as deleted, or have their
700 foreign key to the parent set to NULL. Marking this flag as
701 True usually implies an ON DELETE <CASCADE|SET NULL> rule is in
702 place which will handle updating/deleting child rows on the
703 database side.
705 Additionally, setting the flag to the string value 'all' will
706 disable the "nulling out" of the child foreign keys, when the parent
707 object is deleted and there is no delete or delete-orphan cascade
708 enabled. This is typically used when a triggering or error raise
709 scenario is in place on the database side. Note that the foreign
710 key attributes on in-session child objects will not be changed after
711 a flush occurs so this is a very special use-case setting.
712 Additionally, the "nulling out" will still occur if the child
713 object is de-associated with the parent.
715 .. seealso::
717 :ref:`passive_deletes` - Introductory documentation
718 and examples.
720 :param passive_updates=True:
721 Indicates the persistence behavior to take when a referenced
722 primary key value changes in place, indicating that the referencing
723 foreign key columns will also need their value changed.
725 When True, it is assumed that ``ON UPDATE CASCADE`` is configured on
726 the foreign key in the database, and that the database will
727 handle propagation of an UPDATE from a source column to
728 dependent rows. When False, the SQLAlchemy
729 :func:`_orm.relationship`
730 construct will attempt to emit its own UPDATE statements to
731 modify related targets. However note that SQLAlchemy **cannot**
732 emit an UPDATE for more than one level of cascade. Also,
733 setting this flag to False is not compatible in the case where
734 the database is in fact enforcing referential integrity, unless
735 those constraints are explicitly "deferred", if the target backend
736 supports it.
738 It is highly advised that an application which is employing
739 mutable primary keys keeps ``passive_updates`` set to True,
740 and instead uses the referential integrity features of the database
741 itself in order to handle the change efficiently and fully.
743 .. seealso::
745 :ref:`passive_updates` - Introductory documentation and
746 examples.
748 :paramref:`.mapper.passive_updates` - a similar flag which
749 takes effect for joined-table inheritance mappings.
751 :param post_update:
752 This indicates that the relationship should be handled by a
753 second UPDATE statement after an INSERT or before a
754 DELETE. Currently, it also will issue an UPDATE after the
755 instance was UPDATEd as well, although this technically should
756 be improved. This flag is used to handle saving bi-directional
757 dependencies between two individual rows (i.e. each row
758 references the other), where it would otherwise be impossible to
759 INSERT or DELETE both rows fully since one row exists before the
760 other. Use this flag when a particular mapping arrangement will
761 incur two rows that are dependent on each other, such as a table
762 that has a one-to-many relationship to a set of child rows, and
763 also has a column that references a single child row within that
764 list (i.e. both tables contain a foreign key to each other). If
765 a flush operation returns an error that a "cyclical
766 dependency" was detected, this is a cue that you might want to
767 use :paramref:`_orm.relationship.post_update` to "break" the cycle.
769 .. seealso::
771 :ref:`post_update` - Introductory documentation and examples.
773 :param primaryjoin:
774 A SQL expression that will be used as the primary
775 join of the child object against the parent object, or in a
776 many-to-many relationship the join of the parent object to the
777 association table. By default, this value is computed based on the
778 foreign key relationships of the parent and child tables (or
779 association table).
781 :paramref:`_orm.relationship.primaryjoin` may also be passed as a
782 callable function which is evaluated at mapper initialization time,
783 and may be passed as a Python-evaluable string when using
784 Declarative.
786 .. warning:: When passed as a Python-evaluable string, the
787 argument is interpreted using Python's ``eval()`` function.
788 **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**.
789 See :ref:`declarative_relationship_eval` for details on
790 declarative evaluation of :func:`_orm.relationship` arguments.
792 .. seealso::
794 :ref:`relationship_primaryjoin`
796 :param remote_side:
797 Used for self-referential relationships, indicates the column or
798 list of columns that form the "remote side" of the relationship.
800 :paramref:`_orm.relationship.remote_side` may also be passed as a
801 callable function which is evaluated at mapper initialization time,
802 and may be passed as a Python-evaluable string when using
803 Declarative.
805 .. warning:: When passed as a Python-evaluable string, the
806 argument is interpreted using Python's ``eval()`` function.
807 **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**.
808 See :ref:`declarative_relationship_eval` for details on
809 declarative evaluation of :func:`_orm.relationship` arguments.
811 .. seealso::
813 :ref:`self_referential` - in-depth explanation of how
814 :paramref:`_orm.relationship.remote_side`
815 is used to configure self-referential relationships.
817 :func:`.remote` - an annotation function that accomplishes the
818 same purpose as :paramref:`_orm.relationship.remote_side`,
819 typically
820 when a custom :paramref:`_orm.relationship.primaryjoin` condition
821 is used.
823 :param query_class:
824 A :class:`_query.Query`
825 subclass that will be used as the base of the
826 "appender query" returned by a "dynamic" relationship, that
827 is, a relationship that specifies ``lazy="dynamic"`` or was
828 otherwise constructed using the :func:`_orm.dynamic_loader`
829 function.
831 .. seealso::
833 :ref:`dynamic_relationship` - Introduction to "dynamic"
834 relationship loaders.
836 :param secondaryjoin:
837 A SQL expression that will be used as the join of
838 an association table to the child object. By default, this value is
839 computed based on the foreign key relationships of the association
840 and child tables.
842 :paramref:`_orm.relationship.secondaryjoin` may also be passed as a
843 callable function which is evaluated at mapper initialization time,
844 and may be passed as a Python-evaluable string when using
845 Declarative.
847 .. warning:: When passed as a Python-evaluable string, the
848 argument is interpreted using Python's ``eval()`` function.
849 **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**.
850 See :ref:`declarative_relationship_eval` for details on
851 declarative evaluation of :func:`_orm.relationship` arguments.
853 .. seealso::
855 :ref:`relationship_primaryjoin`
857 :param single_parent:
858 When True, installs a validator which will prevent objects
859 from being associated with more than one parent at a time.
860 This is used for many-to-one or many-to-many relationships that
861 should be treated either as one-to-one or one-to-many. Its usage
862 is optional, except for :func:`_orm.relationship` constructs which
863 are many-to-one or many-to-many and also
864 specify the ``delete-orphan`` cascade option. The
865 :func:`_orm.relationship` construct itself will raise an error
866 instructing when this option is required.
868 .. seealso::
870 :ref:`unitofwork_cascades` - includes detail on when the
871 :paramref:`_orm.relationship.single_parent`
872 flag may be appropriate.
874 :param uselist:
875 A boolean that indicates if this property should be loaded as a
876 list or a scalar. In most cases, this value is determined
877 automatically by :func:`_orm.relationship` at mapper configuration
878 time, based on the type and direction
879 of the relationship - one to many forms a list, many to one
880 forms a scalar, many to many is a list. If a scalar is desired
881 where normally a list would be present, such as a bi-directional
882 one-to-one relationship, set :paramref:`_orm.relationship.uselist`
883 to
884 False.
886 The :paramref:`_orm.relationship.uselist`
887 flag is also available on an
888 existing :func:`_orm.relationship`
889 construct as a read-only attribute,
890 which can be used to determine if this :func:`_orm.relationship`
891 deals
892 with collections or scalar attributes::
894 >>> User.addresses.property.uselist
895 True
897 .. seealso::
899 :ref:`relationships_one_to_one` - Introduction to the "one to
900 one" relationship pattern, which is typically when the
901 :paramref:`_orm.relationship.uselist` flag is needed.
903 :param viewonly=False:
904 When set to ``True``, the relationship is used only for loading
905 objects, and not for any persistence operation. A
906 :func:`_orm.relationship` which specifies
907 :paramref:`_orm.relationship.viewonly` can work
908 with a wider range of SQL operations within the
909 :paramref:`_orm.relationship.primaryjoin` condition, including
910 operations that feature the use of a variety of comparison operators
911 as well as SQL functions such as :func:`_expression.cast`. The
912 :paramref:`_orm.relationship.viewonly`
913 flag is also of general use when defining any kind of
914 :func:`_orm.relationship` that doesn't represent
915 the full set of related objects, to prevent modifications of the
916 collection from resulting in persistence operations.
918 When using the :paramref:`_orm.relationship.viewonly` flag in
919 conjunction with backrefs, the
920 :paramref:`_orm.relationship.sync_backref` should be set to False;
921 this indicates that the backref should not actually populate this
922 relationship with data when changes occur on the other side; as this
923 is a viewonly relationship, it cannot accommodate changes in state
924 correctly as these will not be persisted.
926 .. versionadded:: 1.3.17 - the
927 :paramref:`_orm.relationship.sync_backref`
928 flag set to False is required when using viewonly in conjunction
929 with backrefs. A warning is emitted when this flag is not set.
931 .. seealso::
933 :paramref:`_orm.relationship.sync_backref`
935 :param sync_backref:
936 A boolean that enables the events used to synchronize the in-Python
937 attributes when this relationship is target of either
938 :paramref:`_orm.relationship.backref` or
939 :paramref:`_orm.relationship.back_populates`.
941 Defaults to ``None``, which indicates that an automatic value should
942 be selected based on the value of the
943 :paramref:`_orm.relationship.viewonly` flag. When left at its
944 default, changes in state for writable relationships will be
945 back-populated normally. For viewonly relationships, a warning is
946 emitted unless the flag is set to ``False``.
948 .. versionadded:: 1.3.17
950 .. seealso::
952 :paramref:`_orm.relationship.viewonly`
954 :param omit_join:
955 Allows manual control over the "selectin" automatic join
956 optimization. Set to ``False`` to disable the "omit join" feature
957 added in SQLAlchemy 1.3; or leave as ``None`` to leave automatic
958 optimization in place.
960 .. note:: This flag may only be set to ``False``. It is not
961 necessary to set it to ``True`` as the "omit_join" optimization is
962 automatically detected; if it is not detected, then the
963 optimization is not supported.
965 .. versionchanged:: 1.3.11 setting ``omit_join`` to True will now
966 emit a warning as this was not the intended use of this flag.
968 .. versionadded:: 1.3
971 """
972 super(RelationshipProperty, self).__init__()
974 self.uselist = uselist
975 self.argument = argument
976 self.secondary = secondary
977 self.primaryjoin = primaryjoin
978 self.secondaryjoin = secondaryjoin
979 self.post_update = post_update
980 self.direction = None
981 self.viewonly = viewonly
982 if viewonly:
983 self._warn_for_persistence_only_flags(
984 passive_deletes=passive_deletes,
985 passive_updates=passive_updates,
986 enable_typechecks=enable_typechecks,
987 active_history=active_history,
988 cascade_backrefs=cascade_backrefs,
989 )
990 if viewonly and sync_backref:
991 raise sa_exc.ArgumentError(
992 "sync_backref and viewonly cannot both be True"
993 )
994 self.sync_backref = sync_backref
995 self.lazy = lazy
996 self.single_parent = single_parent
997 self._user_defined_foreign_keys = foreign_keys
998 self.collection_class = collection_class
999 self.passive_deletes = passive_deletes
1000 self.cascade_backrefs = cascade_backrefs
1001 self.passive_updates = passive_updates
1002 self.remote_side = remote_side
1003 self.enable_typechecks = enable_typechecks
1004 self.query_class = query_class
1005 self.innerjoin = innerjoin
1006 self.distinct_target_key = distinct_target_key
1007 self.doc = doc
1008 self.active_history = active_history
1009 self.join_depth = join_depth
1010 if omit_join:
1011 util.warn(
1012 "setting omit_join to True is not supported; selectin "
1013 "loading of this relationship may not work correctly if this "
1014 "flag is set explicitly. omit_join optimization is "
1015 "automatically detected for conditions under which it is "
1016 "supported."
1017 )
1019 self.omit_join = omit_join
1020 self.local_remote_pairs = _local_remote_pairs
1021 self.extension = extension
1022 self.bake_queries = bake_queries
1023 self.load_on_pending = load_on_pending
1024 self.comparator_factory = (
1025 comparator_factory or RelationshipProperty.Comparator
1026 )
1027 self.comparator = self.comparator_factory(self, None)
1028 util.set_creation_order(self)
1030 if info is not None:
1031 self.info = info
1033 self.strategy_key = (("lazy", self.lazy),)
1035 self._reverse_property = set()
1037 if cascade is not False:
1038 self.cascade = cascade
1039 else:
1040 self._set_cascade("save-update, merge", warn=False)
1042 self.order_by = order_by
1044 self.back_populates = back_populates
1046 if self.back_populates:
1047 if backref:
1048 raise sa_exc.ArgumentError(
1049 "backref and back_populates keyword arguments "
1050 "are mutually exclusive"
1051 )
1052 self.backref = None
1053 else:
1054 self.backref = backref
1056 def _warn_for_persistence_only_flags(self, **kw):
1057 for k, v in kw.items():
1058 if v != self._persistence_only[k]:
1059 # we are warning here rather than warn deprecated as this is a
1060 # configuration mistake, and Python shows regular warnings more
1061 # aggressively than deprecation warnings by default. Unlike the
1062 # case of setting viewonly with cascade, the settings being
1063 # warned about here are not actively doing the wrong thing
1064 # against viewonly=True, so it is not as urgent to have these
1065 # raise an error.
1066 util.warn(
1067 "Setting %s on relationship() while also "
1068 "setting viewonly=True does not make sense, as a "
1069 "viewonly=True relationship does not perform persistence "
1070 "operations. This configuration may raise an error "
1071 "in a future release." % (k,)
1072 )
1074 def instrument_class(self, mapper):
1075 attributes.register_descriptor(
1076 mapper.class_,
1077 self.key,
1078 comparator=self.comparator_factory(self, mapper),
1079 parententity=mapper,
1080 doc=self.doc,
1081 )
1083 class Comparator(PropComparator):
1084 """Produce boolean, comparison, and other operators for
1085 :class:`.RelationshipProperty` attributes.
1087 See the documentation for :class:`.PropComparator` for a brief
1088 overview of ORM level operator definition.
1090 .. seealso::
1092 :class:`.PropComparator`
1094 :class:`.ColumnProperty.Comparator`
1096 :class:`.ColumnOperators`
1098 :ref:`types_operators`
1100 :attr:`.TypeEngine.comparator_factory`
1102 """
1104 _of_type = None
1106 def __init__(
1107 self, prop, parentmapper, adapt_to_entity=None, of_type=None
1108 ):
1109 """Construction of :class:`.RelationshipProperty.Comparator`
1110 is internal to the ORM's attribute mechanics.
1112 """
1113 self.prop = prop
1114 self._parententity = parentmapper
1115 self._adapt_to_entity = adapt_to_entity
1116 if of_type:
1117 self._of_type = of_type
1119 def adapt_to_entity(self, adapt_to_entity):
1120 return self.__class__(
1121 self.property,
1122 self._parententity,
1123 adapt_to_entity=adapt_to_entity,
1124 of_type=self._of_type,
1125 )
1127 @util.memoized_property
1128 def entity(self):
1129 """The target entity referred to by this
1130 :class:`.RelationshipProperty.Comparator`.
1132 This is either a :class:`_orm.Mapper` or :class:`.AliasedInsp`
1133 object.
1135 This is the "target" or "remote" side of the
1136 :func:`_orm.relationship`.
1138 """
1139 return self.property.entity
1141 @util.memoized_property
1142 def mapper(self):
1143 """The target :class:`_orm.Mapper` referred to by this
1144 :class:`.RelationshipProperty.Comparator`.
1146 This is the "target" or "remote" side of the
1147 :func:`_orm.relationship`.
1149 """
1150 return self.property.mapper
1152 @util.memoized_property
1153 def _parententity(self):
1154 return self.property.parent
1156 def _source_selectable(self):
1157 if self._adapt_to_entity:
1158 return self._adapt_to_entity.selectable
1159 else:
1160 return self.property.parent._with_polymorphic_selectable
1162 def __clause_element__(self):
1163 adapt_from = self._source_selectable()
1164 if self._of_type:
1165 of_type_mapper = inspect(self._of_type).mapper
1166 else:
1167 of_type_mapper = None
1169 (
1170 pj,
1171 sj,
1172 source,
1173 dest,
1174 secondary,
1175 target_adapter,
1176 ) = self.property._create_joins(
1177 source_selectable=adapt_from,
1178 source_polymorphic=True,
1179 of_type_mapper=of_type_mapper,
1180 alias_secondary=True,
1181 )
1182 if sj is not None:
1183 return pj & sj
1184 else:
1185 return pj
1187 def of_type(self, cls):
1188 r"""Redefine this object in terms of a polymorphic subclass.
1190 See :meth:`.PropComparator.of_type` for an example.
1192 """
1193 return RelationshipProperty.Comparator(
1194 self.property,
1195 self._parententity,
1196 adapt_to_entity=self._adapt_to_entity,
1197 of_type=cls,
1198 )
1200 def in_(self, other):
1201 """Produce an IN clause - this is not implemented
1202 for :func:`_orm.relationship`-based attributes at this time.
1204 """
1205 raise NotImplementedError(
1206 "in_() not yet supported for "
1207 "relationships. For a simple "
1208 "many-to-one, use in_() against "
1209 "the set of foreign key values."
1210 )
1212 __hash__ = None
1214 def __eq__(self, other):
1215 """Implement the ``==`` operator.
1217 In a many-to-one context, such as::
1219 MyClass.some_prop == <some object>
1221 this will typically produce a
1222 clause such as::
1224 mytable.related_id == <some id>
1226 Where ``<some id>`` is the primary key of the given
1227 object.
1229 The ``==`` operator provides partial functionality for non-
1230 many-to-one comparisons:
1232 * Comparisons against collections are not supported.
1233 Use :meth:`~.RelationshipProperty.Comparator.contains`.
1234 * Compared to a scalar one-to-many, will produce a
1235 clause that compares the target columns in the parent to
1236 the given target.
1237 * Compared to a scalar many-to-many, an alias
1238 of the association table will be rendered as
1239 well, forming a natural join that is part of the
1240 main body of the query. This will not work for
1241 queries that go beyond simple AND conjunctions of
1242 comparisons, such as those which use OR. Use
1243 explicit joins, outerjoins, or
1244 :meth:`~.RelationshipProperty.Comparator.has` for
1245 more comprehensive non-many-to-one scalar
1246 membership tests.
1247 * Comparisons against ``None`` given in a one-to-many
1248 or many-to-many context produce a NOT EXISTS clause.
1250 """
1251 if isinstance(other, (util.NoneType, expression.Null)):
1252 if self.property.direction in [ONETOMANY, MANYTOMANY]:
1253 return ~self._criterion_exists()
1254 else:
1255 return _orm_annotate(
1256 self.property._optimized_compare(
1257 None, adapt_source=self.adapter
1258 )
1259 )
1260 elif self.property.uselist:
1261 raise sa_exc.InvalidRequestError(
1262 "Can't compare a collection to an object or collection; "
1263 "use contains() to test for membership."
1264 )
1265 else:
1266 return _orm_annotate(
1267 self.property._optimized_compare(
1268 other, adapt_source=self.adapter
1269 )
1270 )
1272 def _criterion_exists(self, criterion=None, **kwargs):
1273 if getattr(self, "_of_type", None):
1274 info = inspect(self._of_type)
1275 target_mapper, to_selectable, is_aliased_class = (
1276 info.mapper,
1277 info.selectable,
1278 info.is_aliased_class,
1279 )
1280 if self.property._is_self_referential and not is_aliased_class:
1281 to_selectable = to_selectable.alias()
1283 single_crit = target_mapper._single_table_criterion
1284 if single_crit is not None:
1285 if criterion is not None:
1286 criterion = single_crit & criterion
1287 else:
1288 criterion = single_crit
1289 else:
1290 is_aliased_class = False
1291 to_selectable = None
1293 if self.adapter:
1294 source_selectable = self._source_selectable()
1295 else:
1296 source_selectable = None
1298 (
1299 pj,
1300 sj,
1301 source,
1302 dest,
1303 secondary,
1304 target_adapter,
1305 ) = self.property._create_joins(
1306 dest_polymorphic=True,
1307 dest_selectable=to_selectable,
1308 source_selectable=source_selectable,
1309 )
1311 for k in kwargs:
1312 crit = getattr(self.property.mapper.class_, k) == kwargs[k]
1313 if criterion is None:
1314 criterion = crit
1315 else:
1316 criterion = criterion & crit
1318 # annotate the *local* side of the join condition, in the case
1319 # of pj + sj this is the full primaryjoin, in the case of just
1320 # pj its the local side of the primaryjoin.
1321 if sj is not None:
1322 j = _orm_annotate(pj) & sj
1323 else:
1324 j = _orm_annotate(pj, exclude=self.property.remote_side)
1326 if (
1327 criterion is not None
1328 and target_adapter
1329 and not is_aliased_class
1330 ):
1331 # limit this adapter to annotated only?
1332 criterion = target_adapter.traverse(criterion)
1334 # only have the "joined left side" of what we
1335 # return be subject to Query adaption. The right
1336 # side of it is used for an exists() subquery and
1337 # should not correlate or otherwise reach out
1338 # to anything in the enclosing query.
1339 if criterion is not None:
1340 criterion = criterion._annotate(
1341 {"no_replacement_traverse": True}
1342 )
1344 crit = j & sql.True_._ifnone(criterion)
1346 if secondary is not None:
1347 ex = sql.exists(
1348 [1], crit, from_obj=[dest, secondary]
1349 ).correlate_except(dest, secondary)
1350 else:
1351 ex = sql.exists([1], crit, from_obj=dest).correlate_except(
1352 dest
1353 )
1354 return ex
1356 def any(self, criterion=None, **kwargs):
1357 """Produce an expression that tests a collection against
1358 particular criterion, using EXISTS.
1360 An expression like::
1362 session.query(MyClass).filter(
1363 MyClass.somereference.any(SomeRelated.x==2)
1364 )
1367 Will produce a query like::
1369 SELECT * FROM my_table WHERE
1370 EXISTS (SELECT 1 FROM related WHERE related.my_id=my_table.id
1371 AND related.x=2)
1373 Because :meth:`~.RelationshipProperty.Comparator.any` uses
1374 a correlated subquery, its performance is not nearly as
1375 good when compared against large target tables as that of
1376 using a join.
1378 :meth:`~.RelationshipProperty.Comparator.any` is particularly
1379 useful for testing for empty collections::
1381 session.query(MyClass).filter(
1382 ~MyClass.somereference.any()
1383 )
1385 will produce::
1387 SELECT * FROM my_table WHERE
1388 NOT EXISTS (SELECT 1 FROM related WHERE
1389 related.my_id=my_table.id)
1391 :meth:`~.RelationshipProperty.Comparator.any` is only
1392 valid for collections, i.e. a :func:`_orm.relationship`
1393 that has ``uselist=True``. For scalar references,
1394 use :meth:`~.RelationshipProperty.Comparator.has`.
1396 """
1397 if not self.property.uselist:
1398 raise sa_exc.InvalidRequestError(
1399 "'any()' not implemented for scalar "
1400 "attributes. Use has()."
1401 )
1403 return self._criterion_exists(criterion, **kwargs)
1405 def has(self, criterion=None, **kwargs):
1406 """Produce an expression that tests a scalar reference against
1407 particular criterion, using EXISTS.
1409 An expression like::
1411 session.query(MyClass).filter(
1412 MyClass.somereference.has(SomeRelated.x==2)
1413 )
1416 Will produce a query like::
1418 SELECT * FROM my_table WHERE
1419 EXISTS (SELECT 1 FROM related WHERE
1420 related.id==my_table.related_id AND related.x=2)
1422 Because :meth:`~.RelationshipProperty.Comparator.has` uses
1423 a correlated subquery, its performance is not nearly as
1424 good when compared against large target tables as that of
1425 using a join.
1427 :meth:`~.RelationshipProperty.Comparator.has` is only
1428 valid for scalar references, i.e. a :func:`_orm.relationship`
1429 that has ``uselist=False``. For collection references,
1430 use :meth:`~.RelationshipProperty.Comparator.any`.
1432 """
1433 if self.property.uselist:
1434 raise sa_exc.InvalidRequestError(
1435 "'has()' not implemented for collections. " "Use any()."
1436 )
1437 return self._criterion_exists(criterion, **kwargs)
1439 def contains(self, other, **kwargs):
1440 """Return a simple expression that tests a collection for
1441 containment of a particular item.
1443 :meth:`~.RelationshipProperty.Comparator.contains` is
1444 only valid for a collection, i.e. a
1445 :func:`_orm.relationship` that implements
1446 one-to-many or many-to-many with ``uselist=True``.
1448 When used in a simple one-to-many context, an
1449 expression like::
1451 MyClass.contains(other)
1453 Produces a clause like::
1455 mytable.id == <some id>
1457 Where ``<some id>`` is the value of the foreign key
1458 attribute on ``other`` which refers to the primary
1459 key of its parent object. From this it follows that
1460 :meth:`~.RelationshipProperty.Comparator.contains` is
1461 very useful when used with simple one-to-many
1462 operations.
1464 For many-to-many operations, the behavior of
1465 :meth:`~.RelationshipProperty.Comparator.contains`
1466 has more caveats. The association table will be
1467 rendered in the statement, producing an "implicit"
1468 join, that is, includes multiple tables in the FROM
1469 clause which are equated in the WHERE clause::
1471 query(MyClass).filter(MyClass.contains(other))
1473 Produces a query like::
1475 SELECT * FROM my_table, my_association_table AS
1476 my_association_table_1 WHERE
1477 my_table.id = my_association_table_1.parent_id
1478 AND my_association_table_1.child_id = <some id>
1480 Where ``<some id>`` would be the primary key of
1481 ``other``. From the above, it is clear that
1482 :meth:`~.RelationshipProperty.Comparator.contains`
1483 will **not** work with many-to-many collections when
1484 used in queries that move beyond simple AND
1485 conjunctions, such as multiple
1486 :meth:`~.RelationshipProperty.Comparator.contains`
1487 expressions joined by OR. In such cases subqueries or
1488 explicit "outer joins" will need to be used instead.
1489 See :meth:`~.RelationshipProperty.Comparator.any` for
1490 a less-performant alternative using EXISTS, or refer
1491 to :meth:`_query.Query.outerjoin`
1492 as well as :ref:`ormtutorial_joins`
1493 for more details on constructing outer joins.
1495 """
1496 if not self.property.uselist:
1497 raise sa_exc.InvalidRequestError(
1498 "'contains' not implemented for scalar "
1499 "attributes. Use =="
1500 )
1501 clause = self.property._optimized_compare(
1502 other, adapt_source=self.adapter
1503 )
1505 if self.property.secondaryjoin is not None:
1506 clause.negation_clause = self.__negated_contains_or_equals(
1507 other
1508 )
1510 return clause
1512 def __negated_contains_or_equals(self, other):
1513 if self.property.direction == MANYTOONE:
1514 state = attributes.instance_state(other)
1516 def state_bindparam(x, state, col):
1517 dict_ = state.dict
1518 return sql.bindparam(
1519 x,
1520 unique=True,
1521 callable_=self.property._get_attr_w_warn_on_none(
1522 self.property.mapper, state, dict_, col
1523 ),
1524 )
1526 def adapt(col):
1527 if self.adapter:
1528 return self.adapter(col)
1529 else:
1530 return col
1532 if self.property._use_get:
1533 return sql.and_(
1534 *[
1535 sql.or_(
1536 adapt(x)
1537 != state_bindparam(adapt(x), state, y),
1538 adapt(x) == None,
1539 )
1540 for (x, y) in self.property.local_remote_pairs
1541 ]
1542 )
1544 criterion = sql.and_(
1545 *[
1546 x == y
1547 for (x, y) in zip(
1548 self.property.mapper.primary_key,
1549 self.property.mapper.primary_key_from_instance(other),
1550 )
1551 ]
1552 )
1554 return ~self._criterion_exists(criterion)
1556 def __ne__(self, other):
1557 """Implement the ``!=`` operator.
1559 In a many-to-one context, such as::
1561 MyClass.some_prop != <some object>
1563 This will typically produce a clause such as::
1565 mytable.related_id != <some id>
1567 Where ``<some id>`` is the primary key of the
1568 given object.
1570 The ``!=`` operator provides partial functionality for non-
1571 many-to-one comparisons:
1573 * Comparisons against collections are not supported.
1574 Use
1575 :meth:`~.RelationshipProperty.Comparator.contains`
1576 in conjunction with :func:`_expression.not_`.
1577 * Compared to a scalar one-to-many, will produce a
1578 clause that compares the target columns in the parent to
1579 the given target.
1580 * Compared to a scalar many-to-many, an alias
1581 of the association table will be rendered as
1582 well, forming a natural join that is part of the
1583 main body of the query. This will not work for
1584 queries that go beyond simple AND conjunctions of
1585 comparisons, such as those which use OR. Use
1586 explicit joins, outerjoins, or
1587 :meth:`~.RelationshipProperty.Comparator.has` in
1588 conjunction with :func:`_expression.not_` for
1589 more comprehensive non-many-to-one scalar
1590 membership tests.
1591 * Comparisons against ``None`` given in a one-to-many
1592 or many-to-many context produce an EXISTS clause.
1594 """
1595 if isinstance(other, (util.NoneType, expression.Null)):
1596 if self.property.direction == MANYTOONE:
1597 return _orm_annotate(
1598 ~self.property._optimized_compare(
1599 None, adapt_source=self.adapter
1600 )
1601 )
1603 else:
1604 return self._criterion_exists()
1605 elif self.property.uselist:
1606 raise sa_exc.InvalidRequestError(
1607 "Can't compare a collection"
1608 " to an object or collection; use "
1609 "contains() to test for membership."
1610 )
1611 else:
1612 return _orm_annotate(self.__negated_contains_or_equals(other))
1614 @util.memoized_property
1615 def property(self):
1616 if mapperlib.Mapper._new_mappers:
1617 mapperlib.Mapper._configure_all()
1618 return self.prop
1620 def _with_parent(self, instance, alias_secondary=True, from_entity=None):
1621 assert instance is not None
1622 adapt_source = None
1623 if from_entity is not None:
1624 insp = inspect(from_entity)
1625 if insp.is_aliased_class:
1626 adapt_source = insp._adapter.adapt_clause
1627 return self._optimized_compare(
1628 instance,
1629 value_is_parent=True,
1630 adapt_source=adapt_source,
1631 alias_secondary=alias_secondary,
1632 )
1634 def _optimized_compare(
1635 self,
1636 state,
1637 value_is_parent=False,
1638 adapt_source=None,
1639 alias_secondary=True,
1640 ):
1641 if state is not None:
1642 try:
1643 state = inspect(state)
1644 except sa_exc.NoInspectionAvailable:
1645 state = None
1647 if state is None or not getattr(state, "is_instance", False):
1648 raise sa_exc.ArgumentError(
1649 "Mapped instance expected for relationship "
1650 "comparison to object. Classes, queries and other "
1651 "SQL elements are not accepted in this context; for "
1652 "comparison with a subquery, "
1653 "use %s.has(**criteria)." % self
1654 )
1655 reverse_direction = not value_is_parent
1657 if state is None:
1658 return self._lazy_none_clause(
1659 reverse_direction, adapt_source=adapt_source
1660 )
1662 if not reverse_direction:
1663 criterion, bind_to_col = (
1664 self._lazy_strategy._lazywhere,
1665 self._lazy_strategy._bind_to_col,
1666 )
1667 else:
1668 criterion, bind_to_col = (
1669 self._lazy_strategy._rev_lazywhere,
1670 self._lazy_strategy._rev_bind_to_col,
1671 )
1673 if reverse_direction:
1674 mapper = self.mapper
1675 else:
1676 mapper = self.parent
1678 dict_ = attributes.instance_dict(state.obj())
1680 def visit_bindparam(bindparam):
1681 if bindparam._identifying_key in bind_to_col:
1682 bindparam.callable = self._get_attr_w_warn_on_none(
1683 mapper,
1684 state,
1685 dict_,
1686 bind_to_col[bindparam._identifying_key],
1687 )
1689 if self.secondary is not None and alias_secondary:
1690 criterion = ClauseAdapter(self.secondary.alias()).traverse(
1691 criterion
1692 )
1694 criterion = visitors.cloned_traverse(
1695 criterion, {}, {"bindparam": visit_bindparam}
1696 )
1698 if adapt_source:
1699 criterion = adapt_source(criterion)
1700 return criterion
1702 def _get_attr_w_warn_on_none(self, mapper, state, dict_, column):
1703 """Create the callable that is used in a many-to-one expression.
1705 E.g.::
1707 u1 = s.query(User).get(5)
1709 expr = Address.user == u1
1711 Above, the SQL should be "address.user_id = 5". The callable
1712 returned by this method produces the value "5" based on the identity
1713 of ``u1``.
1715 """
1717 # in this callable, we're trying to thread the needle through
1718 # a wide variety of scenarios, including:
1719 #
1720 # * the object hasn't been flushed yet and there's no value for
1721 # the attribute as of yet
1722 #
1723 # * the object hasn't been flushed yet but it has a user-defined
1724 # value
1725 #
1726 # * the object has a value but it's expired and not locally present
1727 #
1728 # * the object has a value but it's expired and not locally present,
1729 # and the object is also detached
1730 #
1731 # * The object hadn't been flushed yet, there was no value, but
1732 # later, the object has been expired and detached, and *now*
1733 # they're trying to evaluate it
1734 #
1735 # * the object had a value, but it was changed to a new value, and
1736 # then expired
1737 #
1738 # * the object had a value, but it was changed to a new value, and
1739 # then expired, then the object was detached
1740 #
1741 # * the object has a user-set value, but it's None and we don't do
1742 # the comparison correctly for that so warn
1743 #
1745 prop = mapper.get_property_by_column(column)
1747 # by invoking this method, InstanceState will track the last known
1748 # value for this key each time the attribute is to be expired.
1749 # this feature was added explicitly for use in this method.
1750 state._track_last_known_value(prop.key)
1752 def _go():
1753 last_known = to_return = state._last_known_values[prop.key]
1754 existing_is_available = last_known is not attributes.NO_VALUE
1756 # we support that the value may have changed. so here we
1757 # try to get the most recent value including re-fetching.
1758 # only if we can't get a value now due to detachment do we return
1759 # the last known value
1760 current_value = mapper._get_state_attr_by_column(
1761 state,
1762 dict_,
1763 column,
1764 passive=attributes.PASSIVE_OFF
1765 if state.persistent
1766 else attributes.PASSIVE_NO_FETCH ^ attributes.INIT_OK,
1767 )
1769 if current_value is attributes.NEVER_SET:
1770 if not existing_is_available:
1771 raise sa_exc.InvalidRequestError(
1772 "Can't resolve value for column %s on object "
1773 "%s; no value has been set for this column"
1774 % (column, state_str(state))
1775 )
1776 elif current_value is attributes.PASSIVE_NO_RESULT:
1777 if not existing_is_available:
1778 raise sa_exc.InvalidRequestError(
1779 "Can't resolve value for column %s on object "
1780 "%s; the object is detached and the value was "
1781 "expired" % (column, state_str(state))
1782 )
1783 else:
1784 to_return = current_value
1785 if to_return is None:
1786 util.warn(
1787 "Got None for value of column %s; this is unsupported "
1788 "for a relationship comparison and will not "
1789 "currently produce an IS comparison "
1790 "(but may in a future release)" % column
1791 )
1792 return to_return
1794 return _go
1796 def _lazy_none_clause(self, reverse_direction=False, adapt_source=None):
1797 if not reverse_direction:
1798 criterion, bind_to_col = (
1799 self._lazy_strategy._lazywhere,
1800 self._lazy_strategy._bind_to_col,
1801 )
1802 else:
1803 criterion, bind_to_col = (
1804 self._lazy_strategy._rev_lazywhere,
1805 self._lazy_strategy._rev_bind_to_col,
1806 )
1808 criterion = adapt_criterion_to_null(criterion, bind_to_col)
1810 if adapt_source:
1811 criterion = adapt_source(criterion)
1812 return criterion
1814 def __str__(self):
1815 return str(self.parent.class_.__name__) + "." + self.key
1817 def merge(
1818 self,
1819 session,
1820 source_state,
1821 source_dict,
1822 dest_state,
1823 dest_dict,
1824 load,
1825 _recursive,
1826 _resolve_conflict_map,
1827 ):
1829 if load:
1830 for r in self._reverse_property:
1831 if (source_state, r) in _recursive:
1832 return
1834 if "merge" not in self._cascade:
1835 return
1837 if self.key not in source_dict:
1838 return
1840 if self.uselist:
1841 instances = source_state.get_impl(self.key).get(
1842 source_state, source_dict
1843 )
1844 if hasattr(instances, "_sa_adapter"):
1845 # convert collections to adapters to get a true iterator
1846 instances = instances._sa_adapter
1848 if load:
1849 # for a full merge, pre-load the destination collection,
1850 # so that individual _merge of each item pulls from identity
1851 # map for those already present.
1852 # also assumes CollectionAttributeImpl behavior of loading
1853 # "old" list in any case
1854 dest_state.get_impl(self.key).get(dest_state, dest_dict)
1856 dest_list = []
1857 for current in instances:
1858 current_state = attributes.instance_state(current)
1859 current_dict = attributes.instance_dict(current)
1860 _recursive[(current_state, self)] = True
1861 obj = session._merge(
1862 current_state,
1863 current_dict,
1864 load=load,
1865 _recursive=_recursive,
1866 _resolve_conflict_map=_resolve_conflict_map,
1867 )
1868 if obj is not None:
1869 dest_list.append(obj)
1871 if not load:
1872 coll = attributes.init_state_collection(
1873 dest_state, dest_dict, self.key
1874 )
1875 for c in dest_list:
1876 coll.append_without_event(c)
1877 else:
1878 dest_state.get_impl(self.key).set(
1879 dest_state, dest_dict, dest_list, _adapt=False
1880 )
1881 else:
1882 current = source_dict[self.key]
1883 if current is not None:
1884 current_state = attributes.instance_state(current)
1885 current_dict = attributes.instance_dict(current)
1886 _recursive[(current_state, self)] = True
1887 obj = session._merge(
1888 current_state,
1889 current_dict,
1890 load=load,
1891 _recursive=_recursive,
1892 _resolve_conflict_map=_resolve_conflict_map,
1893 )
1894 else:
1895 obj = None
1897 if not load:
1898 dest_dict[self.key] = obj
1899 else:
1900 dest_state.get_impl(self.key).set(
1901 dest_state, dest_dict, obj, None
1902 )
1904 def _value_as_iterable(
1905 self, state, dict_, key, passive=attributes.PASSIVE_OFF
1906 ):
1907 """Return a list of tuples (state, obj) for the given
1908 key.
1910 returns an empty list if the value is None/empty/PASSIVE_NO_RESULT
1911 """
1913 impl = state.manager[key].impl
1914 x = impl.get(state, dict_, passive=passive)
1915 if x is attributes.PASSIVE_NO_RESULT or x is None:
1916 return []
1917 elif hasattr(impl, "get_collection"):
1918 return [
1919 (attributes.instance_state(o), o)
1920 for o in impl.get_collection(state, dict_, x, passive=passive)
1921 ]
1922 else:
1923 return [(attributes.instance_state(x), x)]
1925 def cascade_iterator(
1926 self, type_, state, dict_, visited_states, halt_on=None
1927 ):
1928 # assert type_ in self._cascade
1930 # only actively lazy load on the 'delete' cascade
1931 if type_ != "delete" or self.passive_deletes:
1932 passive = attributes.PASSIVE_NO_INITIALIZE
1933 else:
1934 passive = attributes.PASSIVE_OFF
1936 if type_ == "save-update":
1937 tuples = state.manager[self.key].impl.get_all_pending(state, dict_)
1939 else:
1940 tuples = self._value_as_iterable(
1941 state, dict_, self.key, passive=passive
1942 )
1944 skip_pending = (
1945 type_ == "refresh-expire" and "delete-orphan" not in self._cascade
1946 )
1948 for instance_state, c in tuples:
1949 if instance_state in visited_states:
1950 continue
1952 if c is None:
1953 # would like to emit a warning here, but
1954 # would not be consistent with collection.append(None)
1955 # current behavior of silently skipping.
1956 # see [ticket:2229]
1957 continue
1959 instance_dict = attributes.instance_dict(c)
1961 if halt_on and halt_on(instance_state):
1962 continue
1964 if skip_pending and not instance_state.key:
1965 continue
1967 instance_mapper = instance_state.manager.mapper
1969 if not instance_mapper.isa(self.mapper.class_manager.mapper):
1970 raise AssertionError(
1971 "Attribute '%s' on class '%s' "
1972 "doesn't handle objects "
1973 "of type '%s'"
1974 % (self.key, self.parent.class_, c.__class__)
1975 )
1977 visited_states.add(instance_state)
1979 yield c, instance_mapper, instance_state, instance_dict
1981 @property
1982 def _effective_sync_backref(self):
1983 return self.sync_backref is not False
1985 @staticmethod
1986 def _check_sync_backref(rel_a, rel_b):
1987 if rel_a.viewonly and rel_b.sync_backref:
1988 raise sa_exc.InvalidRequestError(
1989 "Relationship %s cannot specify sync_backref=True since %s "
1990 "includes viewonly=True." % (rel_b, rel_a)
1991 )
1992 if rel_a.viewonly and rel_b.sync_backref is not False:
1993 util.warn_limited(
1994 "Setting backref / back_populates on relationship %s to refer "
1995 "to viewonly relationship %s should include "
1996 "sync_backref=False set on the %s relationship. ",
1997 (rel_b, rel_a, rel_b),
1998 )
2000 def _add_reverse_property(self, key):
2001 other = self.mapper.get_property(key, _configure_mappers=False)
2002 # viewonly and sync_backref cases
2003 # 1. self.viewonly==True and other.sync_backref==True -> error
2004 # 2. self.viewonly==True and other.viewonly==False and
2005 # other.sync_backref==None -> warn sync_backref=False, set to False
2006 self._check_sync_backref(self, other)
2007 # 3. other.viewonly==True and self.sync_backref==True -> error
2008 # 4. other.viewonly==True and self.viewonly==False and
2009 # self.sync_backref==None -> warn sync_backref=False, set to False
2010 self._check_sync_backref(other, self)
2012 self._reverse_property.add(other)
2013 other._reverse_property.add(self)
2015 if not other.mapper.common_parent(self.parent):
2016 raise sa_exc.ArgumentError(
2017 "reverse_property %r on "
2018 "relationship %s references relationship %s, which "
2019 "does not reference mapper %s"
2020 % (key, self, other, self.parent)
2021 )
2023 if (
2024 self.direction in (ONETOMANY, MANYTOONE)
2025 and self.direction == other.direction
2026 ):
2027 raise sa_exc.ArgumentError(
2028 "%s and back-reference %s are "
2029 "both of the same direction %r. Did you mean to "
2030 "set remote_side on the many-to-one side ?"
2031 % (other, self, self.direction)
2032 )
2034 @util.memoized_property
2035 def entity(self): # type: () -> Union[AliasedInsp, Mapper]
2036 """Return the target mapped entity, which is an inspect() of the
2037 class or aliased class tha is referred towards.
2039 """
2040 if util.callable(self.argument) and not isinstance(
2041 self.argument, (type, mapperlib.Mapper)
2042 ):
2043 argument = self.argument()
2044 else:
2045 argument = self.argument
2047 if isinstance(argument, type):
2048 return mapperlib.class_mapper(argument, configure=False)
2050 try:
2051 entity = inspect(argument)
2052 except sa_exc.NoInspectionAvailable:
2053 pass
2054 else:
2055 if hasattr(entity, "mapper"):
2056 return entity
2058 raise sa_exc.ArgumentError(
2059 "relationship '%s' expects "
2060 "a class or a mapper argument (received: %s)"
2061 % (self.key, type(argument))
2062 )
2064 @util.memoized_property
2065 def mapper(self):
2066 """Return the targeted :class:`_orm.Mapper` for this
2067 :class:`.RelationshipProperty`.
2069 This is a lazy-initializing static attribute.
2071 """
2072 return self.entity.mapper
2074 def do_init(self):
2075 self._check_conflicts()
2076 self._process_dependent_arguments()
2077 self._setup_join_conditions()
2078 self._check_cascade_settings(self._cascade)
2079 self._post_init()
2080 self._generate_backref()
2081 self._join_condition._warn_for_conflicting_sync_targets()
2082 super(RelationshipProperty, self).do_init()
2083 self._lazy_strategy = self._get_strategy((("lazy", "select"),))
2085 def _process_dependent_arguments(self):
2086 """Convert incoming configuration arguments to their
2087 proper form.
2089 Callables are resolved, ORM annotations removed.
2091 """
2092 # accept callables for other attributes which may require
2093 # deferred initialization. This technique is used
2094 # by declarative "string configs" and some recipes.
2095 for attr in (
2096 "order_by",
2097 "primaryjoin",
2098 "secondaryjoin",
2099 "secondary",
2100 "_user_defined_foreign_keys",
2101 "remote_side",
2102 ):
2103 attr_value = getattr(self, attr)
2104 if util.callable(attr_value):
2105 setattr(self, attr, attr_value())
2107 # remove "annotations" which are present if mapped class
2108 # descriptors are used to create the join expression.
2109 for attr in "primaryjoin", "secondaryjoin":
2110 val = getattr(self, attr)
2111 if val is not None:
2112 setattr(
2113 self,
2114 attr,
2115 _orm_deannotate(
2116 expression._only_column_elements(val, attr)
2117 ),
2118 )
2120 # ensure expressions in self.order_by, foreign_keys,
2121 # remote_side are all columns, not strings.
2122 if self.order_by is not False and self.order_by is not None:
2123 self.order_by = [
2124 expression._only_column_elements(x, "order_by")
2125 for x in util.to_list(self.order_by)
2126 ]
2128 self._user_defined_foreign_keys = util.column_set(
2129 expression._only_column_elements(x, "foreign_keys")
2130 for x in util.to_column_set(self._user_defined_foreign_keys)
2131 )
2133 self.remote_side = util.column_set(
2134 expression._only_column_elements(x, "remote_side")
2135 for x in util.to_column_set(self.remote_side)
2136 )
2138 self.target = self.entity.persist_selectable
2140 def _setup_join_conditions(self):
2141 self._join_condition = jc = JoinCondition(
2142 parent_persist_selectable=self.parent.persist_selectable,
2143 child_persist_selectable=self.entity.persist_selectable,
2144 parent_local_selectable=self.parent.local_table,
2145 child_local_selectable=self.entity.local_table,
2146 primaryjoin=self.primaryjoin,
2147 secondary=self.secondary,
2148 secondaryjoin=self.secondaryjoin,
2149 parent_equivalents=self.parent._equivalent_columns,
2150 child_equivalents=self.mapper._equivalent_columns,
2151 consider_as_foreign_keys=self._user_defined_foreign_keys,
2152 local_remote_pairs=self.local_remote_pairs,
2153 remote_side=self.remote_side,
2154 self_referential=self._is_self_referential,
2155 prop=self,
2156 support_sync=not self.viewonly,
2157 can_be_synced_fn=self._columns_are_mapped,
2158 )
2159 self.primaryjoin = jc.primaryjoin
2160 self.secondaryjoin = jc.secondaryjoin
2161 self.direction = jc.direction
2162 self.local_remote_pairs = jc.local_remote_pairs
2163 self.remote_side = jc.remote_columns
2164 self.local_columns = jc.local_columns
2165 self.synchronize_pairs = jc.synchronize_pairs
2166 self._calculated_foreign_keys = jc.foreign_key_columns
2167 self.secondary_synchronize_pairs = jc.secondary_synchronize_pairs
2169 def _check_conflicts(self):
2170 """Test that this relationship is legal, warn about
2171 inheritance conflicts."""
2173 if self.parent.non_primary and not mapperlib.class_mapper(
2174 self.parent.class_, configure=False
2175 ).has_property(self.key):
2176 raise sa_exc.ArgumentError(
2177 "Attempting to assign a new "
2178 "relationship '%s' to a non-primary mapper on "
2179 "class '%s'. New relationships can only be added "
2180 "to the primary mapper, i.e. the very first mapper "
2181 "created for class '%s' "
2182 % (
2183 self.key,
2184 self.parent.class_.__name__,
2185 self.parent.class_.__name__,
2186 )
2187 )
2189 @property
2190 def cascade(self):
2191 """Return the current cascade setting for this
2192 :class:`.RelationshipProperty`.
2193 """
2194 return self._cascade
2196 @cascade.setter
2197 def cascade(self, cascade):
2198 self._set_cascade(cascade)
2200 def _set_cascade(self, cascade, warn=True):
2201 cascade = CascadeOptions(cascade)
2203 if warn and self.viewonly:
2204 non_viewonly = set(cascade).difference(
2205 CascadeOptions._viewonly_cascades
2206 )
2207 if non_viewonly:
2208 # we are warning here rather than warn deprecated as this
2209 # setting actively does the wrong thing and Python shows
2210 # regular warnings more aggressively than deprecation warnings
2211 # by default. There's no other guard against setting active
2212 # persistence cascades under viewonly=True so this will raise
2213 # in 1.4.
2214 util.warn(
2215 'Cascade settings "%s" should not be combined with a '
2216 "viewonly=True relationship. This configuration will "
2217 "raise an error in version 1.4. Note that in versions "
2218 "prior to 1.4, "
2219 "these cascade settings may still produce a mutating "
2220 "effect even though this relationship is marked as "
2221 "viewonly=True." % (", ".join(sorted(non_viewonly)))
2222 )
2224 if "mapper" in self.__dict__:
2225 self._check_cascade_settings(cascade)
2226 self._cascade = cascade
2228 if self._dependency_processor:
2229 self._dependency_processor.cascade = cascade
2231 def _check_cascade_settings(self, cascade):
2232 if (
2233 cascade.delete_orphan
2234 and not self.single_parent
2235 and (self.direction is MANYTOMANY or self.direction is MANYTOONE)
2236 ):
2237 raise sa_exc.ArgumentError(
2238 "For %(direction)s relationship %(rel)s, delete-orphan "
2239 "cascade is normally "
2240 'configured only on the "one" side of a one-to-many '
2241 "relationship, "
2242 'and not on the "many" side of a many-to-one or many-to-many '
2243 "relationship. "
2244 "To force this relationship to allow a particular "
2245 '"%(relatedcls)s" object to be referred towards by only '
2246 'a single "%(clsname)s" object at a time via the '
2247 "%(rel)s relationship, which "
2248 "would allow "
2249 "delete-orphan cascade to take place in this direction, set "
2250 "the single_parent=True flag."
2251 % {
2252 "rel": self,
2253 "direction": "many-to-one"
2254 if self.direction is MANYTOONE
2255 else "many-to-many",
2256 "clsname": self.parent.class_.__name__,
2257 "relatedcls": self.mapper.class_.__name__,
2258 },
2259 code="bbf0",
2260 )
2262 if self.direction is MANYTOONE and self.passive_deletes:
2263 util.warn(
2264 "On %s, 'passive_deletes' is normally configured "
2265 "on one-to-many, one-to-one, many-to-many "
2266 "relationships only." % self
2267 )
2269 if self.passive_deletes == "all" and (
2270 "delete" in cascade or "delete-orphan" in cascade
2271 ):
2272 raise sa_exc.ArgumentError(
2273 "On %s, can't set passive_deletes='all' in conjunction "
2274 "with 'delete' or 'delete-orphan' cascade" % self
2275 )
2277 if cascade.delete_orphan:
2278 self.mapper.primary_mapper()._delete_orphans.append(
2279 (self.key, self.parent.class_)
2280 )
2282 def _persists_for(self, mapper):
2283 """Return True if this property will persist values on behalf
2284 of the given mapper.
2286 """
2288 return (
2289 self.key in mapper.relationships
2290 and mapper.relationships[self.key] is self
2291 )
2293 def _columns_are_mapped(self, *cols):
2294 """Return True if all columns in the given collection are
2295 mapped by the tables referenced by this :class:`.Relationship`.
2297 """
2298 for c in cols:
2299 if (
2300 self.secondary is not None
2301 and self.secondary.c.contains_column(c)
2302 ):
2303 continue
2304 if not self.parent.persist_selectable.c.contains_column(
2305 c
2306 ) and not self.target.c.contains_column(c):
2307 return False
2308 return True
2310 def _generate_backref(self):
2311 """Interpret the 'backref' instruction to create a
2312 :func:`_orm.relationship` complementary to this one."""
2314 if self.parent.non_primary:
2315 return
2316 if self.backref is not None and not self.back_populates:
2317 if isinstance(self.backref, util.string_types):
2318 backref_key, kwargs = self.backref, {}
2319 else:
2320 backref_key, kwargs = self.backref
2321 mapper = self.mapper.primary_mapper()
2323 if not mapper.concrete:
2324 check = set(mapper.iterate_to_root()).union(
2325 mapper.self_and_descendants
2326 )
2327 for m in check:
2328 if m.has_property(backref_key) and not m.concrete:
2329 raise sa_exc.ArgumentError(
2330 "Error creating backref "
2331 "'%s' on relationship '%s': property of that "
2332 "name exists on mapper '%s'"
2333 % (backref_key, self, m)
2334 )
2336 # determine primaryjoin/secondaryjoin for the
2337 # backref. Use the one we had, so that
2338 # a custom join doesn't have to be specified in
2339 # both directions.
2340 if self.secondary is not None:
2341 # for many to many, just switch primaryjoin/
2342 # secondaryjoin. use the annotated
2343 # pj/sj on the _join_condition.
2344 pj = kwargs.pop(
2345 "primaryjoin",
2346 self._join_condition.secondaryjoin_minus_local,
2347 )
2348 sj = kwargs.pop(
2349 "secondaryjoin",
2350 self._join_condition.primaryjoin_minus_local,
2351 )
2352 else:
2353 pj = kwargs.pop(
2354 "primaryjoin",
2355 self._join_condition.primaryjoin_reverse_remote,
2356 )
2357 sj = kwargs.pop("secondaryjoin", None)
2358 if sj:
2359 raise sa_exc.InvalidRequestError(
2360 "Can't assign 'secondaryjoin' on a backref "
2361 "against a non-secondary relationship."
2362 )
2364 foreign_keys = kwargs.pop(
2365 "foreign_keys", self._user_defined_foreign_keys
2366 )
2367 parent = self.parent.primary_mapper()
2368 kwargs.setdefault("viewonly", self.viewonly)
2369 kwargs.setdefault("post_update", self.post_update)
2370 kwargs.setdefault("passive_updates", self.passive_updates)
2371 kwargs.setdefault("sync_backref", self.sync_backref)
2372 self.back_populates = backref_key
2373 relationship = RelationshipProperty(
2374 parent,
2375 self.secondary,
2376 pj,
2377 sj,
2378 foreign_keys=foreign_keys,
2379 back_populates=self.key,
2380 **kwargs
2381 )
2382 mapper._configure_property(backref_key, relationship)
2384 if self.back_populates:
2385 self._add_reverse_property(self.back_populates)
2387 def _post_init(self):
2388 if self.uselist is None:
2389 self.uselist = self.direction is not MANYTOONE
2390 if not self.viewonly:
2391 self._dependency_processor = (
2392 dependency.DependencyProcessor.from_relationship
2393 )(self)
2395 @util.memoized_property
2396 def _use_get(self):
2397 """memoize the 'use_get' attribute of this RelationshipLoader's
2398 lazyloader."""
2400 strategy = self._lazy_strategy
2401 return strategy.use_get
2403 @util.memoized_property
2404 def _is_self_referential(self):
2405 return self.mapper.common_parent(self.parent)
2407 def _create_joins(
2408 self,
2409 source_polymorphic=False,
2410 source_selectable=None,
2411 dest_polymorphic=False,
2412 dest_selectable=None,
2413 of_type_mapper=None,
2414 alias_secondary=False,
2415 ):
2417 aliased = False
2419 if alias_secondary and self.secondary is not None:
2420 aliased = True
2422 if source_selectable is None:
2423 if source_polymorphic and self.parent.with_polymorphic:
2424 source_selectable = self.parent._with_polymorphic_selectable
2426 if dest_selectable is None:
2427 dest_selectable = self.entity.selectable
2428 if dest_polymorphic and self.mapper.with_polymorphic:
2429 aliased = True
2431 if self._is_self_referential and source_selectable is None:
2432 dest_selectable = dest_selectable.alias()
2433 aliased = True
2434 elif (
2435 dest_selectable is not self.mapper._with_polymorphic_selectable
2436 or self.mapper.with_polymorphic
2437 ):
2438 aliased = True
2440 dest_mapper = of_type_mapper or self.mapper
2442 single_crit = dest_mapper._single_table_criterion
2443 aliased = aliased or (
2444 source_selectable is not None
2445 and (
2446 source_selectable
2447 is not self.parent._with_polymorphic_selectable
2448 or source_selectable._is_from_container # e.g an alias
2449 )
2450 )
2452 (
2453 primaryjoin,
2454 secondaryjoin,
2455 secondary,
2456 target_adapter,
2457 dest_selectable,
2458 ) = self._join_condition.join_targets(
2459 source_selectable, dest_selectable, aliased, single_crit
2460 )
2461 if source_selectable is None:
2462 source_selectable = self.parent.local_table
2463 if dest_selectable is None:
2464 dest_selectable = self.entity.local_table
2465 return (
2466 primaryjoin,
2467 secondaryjoin,
2468 source_selectable,
2469 dest_selectable,
2470 secondary,
2471 target_adapter,
2472 )
2475def _annotate_columns(element, annotations):
2476 def clone(elem):
2477 if isinstance(elem, expression.ColumnClause):
2478 elem = elem._annotate(annotations.copy())
2479 elem._copy_internals(clone=clone)
2480 return elem
2482 if element is not None:
2483 element = clone(element)
2484 clone = None # remove gc cycles
2485 return element
2488class JoinCondition(object):
2489 def __init__(
2490 self,
2491 parent_persist_selectable,
2492 child_persist_selectable,
2493 parent_local_selectable,
2494 child_local_selectable,
2495 primaryjoin=None,
2496 secondary=None,
2497 secondaryjoin=None,
2498 parent_equivalents=None,
2499 child_equivalents=None,
2500 consider_as_foreign_keys=None,
2501 local_remote_pairs=None,
2502 remote_side=None,
2503 self_referential=False,
2504 prop=None,
2505 support_sync=True,
2506 can_be_synced_fn=lambda *c: True,
2507 ):
2508 self.parent_persist_selectable = parent_persist_selectable
2509 self.parent_local_selectable = parent_local_selectable
2510 self.child_persist_selectable = child_persist_selectable
2511 self.child_local_selectable = child_local_selectable
2512 self.parent_equivalents = parent_equivalents
2513 self.child_equivalents = child_equivalents
2514 self.primaryjoin = primaryjoin
2515 self.secondaryjoin = secondaryjoin
2516 self.secondary = secondary
2517 self.consider_as_foreign_keys = consider_as_foreign_keys
2518 self._local_remote_pairs = local_remote_pairs
2519 self._remote_side = remote_side
2520 self.prop = prop
2521 self.self_referential = self_referential
2522 self.support_sync = support_sync
2523 self.can_be_synced_fn = can_be_synced_fn
2524 self._determine_joins()
2525 self._sanitize_joins()
2526 self._annotate_fks()
2527 self._annotate_remote()
2528 self._annotate_local()
2529 self._annotate_parentmapper()
2530 self._setup_pairs()
2531 self._check_foreign_cols(self.primaryjoin, True)
2532 if self.secondaryjoin is not None:
2533 self._check_foreign_cols(self.secondaryjoin, False)
2534 self._determine_direction()
2535 self._check_remote_side()
2536 self._log_joins()
2538 def _log_joins(self):
2539 if self.prop is None:
2540 return
2541 log = self.prop.logger
2542 log.info("%s setup primary join %s", self.prop, self.primaryjoin)
2543 log.info("%s setup secondary join %s", self.prop, self.secondaryjoin)
2544 log.info(
2545 "%s synchronize pairs [%s]",
2546 self.prop,
2547 ",".join(
2548 "(%s => %s)" % (l, r) for (l, r) in self.synchronize_pairs
2549 ),
2550 )
2551 log.info(
2552 "%s secondary synchronize pairs [%s]",
2553 self.prop,
2554 ",".join(
2555 "(%s => %s)" % (l, r)
2556 for (l, r) in self.secondary_synchronize_pairs or []
2557 ),
2558 )
2559 log.info(
2560 "%s local/remote pairs [%s]",
2561 self.prop,
2562 ",".join(
2563 "(%s / %s)" % (l, r) for (l, r) in self.local_remote_pairs
2564 ),
2565 )
2566 log.info(
2567 "%s remote columns [%s]",
2568 self.prop,
2569 ",".join("%s" % col for col in self.remote_columns),
2570 )
2571 log.info(
2572 "%s local columns [%s]",
2573 self.prop,
2574 ",".join("%s" % col for col in self.local_columns),
2575 )
2576 log.info("%s relationship direction %s", self.prop, self.direction)
2578 def _sanitize_joins(self):
2579 """remove the parententity annotation from our join conditions which
2580 can leak in here based on some declarative patterns and maybe others.
2582 We'd want to remove "parentmapper" also, but apparently there's
2583 an exotic use case in _join_fixture_inh_selfref_w_entity
2584 that relies upon it being present, see :ticket:`3364`.
2586 """
2588 self.primaryjoin = _deep_deannotate(
2589 self.primaryjoin, values=("parententity",)
2590 )
2591 if self.secondaryjoin is not None:
2592 self.secondaryjoin = _deep_deannotate(
2593 self.secondaryjoin, values=("parententity",)
2594 )
2596 def _determine_joins(self):
2597 """Determine the 'primaryjoin' and 'secondaryjoin' attributes,
2598 if not passed to the constructor already.
2600 This is based on analysis of the foreign key relationships
2601 between the parent and target mapped selectables.
2603 """
2604 if self.secondaryjoin is not None and self.secondary is None:
2605 raise sa_exc.ArgumentError(
2606 "Property %s specified with secondary "
2607 "join condition but "
2608 "no secondary argument" % self.prop
2609 )
2611 # find a join between the given mapper's mapped table and
2612 # the given table. will try the mapper's local table first
2613 # for more specificity, then if not found will try the more
2614 # general mapped table, which in the case of inheritance is
2615 # a join.
2616 try:
2617 consider_as_foreign_keys = self.consider_as_foreign_keys or None
2618 if self.secondary is not None:
2619 if self.secondaryjoin is None:
2620 self.secondaryjoin = join_condition(
2621 self.child_persist_selectable,
2622 self.secondary,
2623 a_subset=self.child_local_selectable,
2624 consider_as_foreign_keys=consider_as_foreign_keys,
2625 )
2626 if self.primaryjoin is None:
2627 self.primaryjoin = join_condition(
2628 self.parent_persist_selectable,
2629 self.secondary,
2630 a_subset=self.parent_local_selectable,
2631 consider_as_foreign_keys=consider_as_foreign_keys,
2632 )
2633 else:
2634 if self.primaryjoin is None:
2635 self.primaryjoin = join_condition(
2636 self.parent_persist_selectable,
2637 self.child_persist_selectable,
2638 a_subset=self.parent_local_selectable,
2639 consider_as_foreign_keys=consider_as_foreign_keys,
2640 )
2641 except sa_exc.NoForeignKeysError as nfe:
2642 if self.secondary is not None:
2643 util.raise_(
2644 sa_exc.NoForeignKeysError(
2645 "Could not determine join "
2646 "condition between parent/child tables on "
2647 "relationship %s - there are no foreign keys "
2648 "linking these tables via secondary table '%s'. "
2649 "Ensure that referencing columns are associated "
2650 "with a ForeignKey or ForeignKeyConstraint, or "
2651 "specify 'primaryjoin' and 'secondaryjoin' "
2652 "expressions." % (self.prop, self.secondary)
2653 ),
2654 from_=nfe,
2655 )
2656 else:
2657 util.raise_(
2658 sa_exc.NoForeignKeysError(
2659 "Could not determine join "
2660 "condition between parent/child tables on "
2661 "relationship %s - there are no foreign keys "
2662 "linking these tables. "
2663 "Ensure that referencing columns are associated "
2664 "with a ForeignKey or ForeignKeyConstraint, or "
2665 "specify a 'primaryjoin' expression." % self.prop
2666 ),
2667 from_=nfe,
2668 )
2669 except sa_exc.AmbiguousForeignKeysError as afe:
2670 if self.secondary is not None:
2671 util.raise_(
2672 sa_exc.AmbiguousForeignKeysError(
2673 "Could not determine join "
2674 "condition between parent/child tables on "
2675 "relationship %s - there are multiple foreign key "
2676 "paths linking the tables via secondary table '%s'. "
2677 "Specify the 'foreign_keys' "
2678 "argument, providing a list of those columns which "
2679 "should be counted as containing a foreign key "
2680 "reference from the secondary table to each of the "
2681 "parent and child tables."
2682 % (self.prop, self.secondary)
2683 ),
2684 from_=afe,
2685 )
2686 else:
2687 util.raise_(
2688 sa_exc.AmbiguousForeignKeysError(
2689 "Could not determine join "
2690 "condition between parent/child tables on "
2691 "relationship %s - there are multiple foreign key "
2692 "paths linking the tables. Specify the "
2693 "'foreign_keys' argument, providing a list of those "
2694 "columns which should be counted as containing a "
2695 "foreign key reference to the parent table."
2696 % self.prop
2697 ),
2698 from_=afe,
2699 )
2701 @property
2702 def primaryjoin_minus_local(self):
2703 return _deep_deannotate(self.primaryjoin, values=("local", "remote"))
2705 @property
2706 def secondaryjoin_minus_local(self):
2707 return _deep_deannotate(self.secondaryjoin, values=("local", "remote"))
2709 @util.memoized_property
2710 def primaryjoin_reverse_remote(self):
2711 """Return the primaryjoin condition suitable for the
2712 "reverse" direction.
2714 If the primaryjoin was delivered here with pre-existing
2715 "remote" annotations, the local/remote annotations
2716 are reversed. Otherwise, the local/remote annotations
2717 are removed.
2719 """
2720 if self._has_remote_annotations:
2722 def replace(element):
2723 if "remote" in element._annotations:
2724 v = element._annotations.copy()
2725 del v["remote"]
2726 v["local"] = True
2727 return element._with_annotations(v)
2728 elif "local" in element._annotations:
2729 v = element._annotations.copy()
2730 del v["local"]
2731 v["remote"] = True
2732 return element._with_annotations(v)
2734 return visitors.replacement_traverse(self.primaryjoin, {}, replace)
2735 else:
2736 if self._has_foreign_annotations:
2737 # TODO: coverage
2738 return _deep_deannotate(
2739 self.primaryjoin, values=("local", "remote")
2740 )
2741 else:
2742 return _deep_deannotate(self.primaryjoin)
2744 def _has_annotation(self, clause, annotation):
2745 for col in visitors.iterate(clause, {}):
2746 if annotation in col._annotations:
2747 return True
2748 else:
2749 return False
2751 @util.memoized_property
2752 def _has_foreign_annotations(self):
2753 return self._has_annotation(self.primaryjoin, "foreign")
2755 @util.memoized_property
2756 def _has_remote_annotations(self):
2757 return self._has_annotation(self.primaryjoin, "remote")
2759 def _annotate_fks(self):
2760 """Annotate the primaryjoin and secondaryjoin
2761 structures with 'foreign' annotations marking columns
2762 considered as foreign.
2764 """
2765 if self._has_foreign_annotations:
2766 return
2768 if self.consider_as_foreign_keys:
2769 self._annotate_from_fk_list()
2770 else:
2771 self._annotate_present_fks()
2773 def _annotate_from_fk_list(self):
2774 def check_fk(col):
2775 if col in self.consider_as_foreign_keys:
2776 return col._annotate({"foreign": True})
2778 self.primaryjoin = visitors.replacement_traverse(
2779 self.primaryjoin, {}, check_fk
2780 )
2781 if self.secondaryjoin is not None:
2782 self.secondaryjoin = visitors.replacement_traverse(
2783 self.secondaryjoin, {}, check_fk
2784 )
2786 def _annotate_present_fks(self):
2787 if self.secondary is not None:
2788 secondarycols = util.column_set(self.secondary.c)
2789 else:
2790 secondarycols = set()
2792 def is_foreign(a, b):
2793 if isinstance(a, schema.Column) and isinstance(b, schema.Column):
2794 if a.references(b):
2795 return a
2796 elif b.references(a):
2797 return b
2799 if secondarycols:
2800 if a in secondarycols and b not in secondarycols:
2801 return a
2802 elif b in secondarycols and a not in secondarycols:
2803 return b
2805 def visit_binary(binary):
2806 if not isinstance(
2807 binary.left, sql.ColumnElement
2808 ) or not isinstance(binary.right, sql.ColumnElement):
2809 return
2811 if (
2812 "foreign" not in binary.left._annotations
2813 and "foreign" not in binary.right._annotations
2814 ):
2815 col = is_foreign(binary.left, binary.right)
2816 if col is not None:
2817 if col.compare(binary.left):
2818 binary.left = binary.left._annotate({"foreign": True})
2819 elif col.compare(binary.right):
2820 binary.right = binary.right._annotate(
2821 {"foreign": True}
2822 )
2824 self.primaryjoin = visitors.cloned_traverse(
2825 self.primaryjoin, {}, {"binary": visit_binary}
2826 )
2827 if self.secondaryjoin is not None:
2828 self.secondaryjoin = visitors.cloned_traverse(
2829 self.secondaryjoin, {}, {"binary": visit_binary}
2830 )
2832 def _refers_to_parent_table(self):
2833 """Return True if the join condition contains column
2834 comparisons where both columns are in both tables.
2836 """
2837 pt = self.parent_persist_selectable
2838 mt = self.child_persist_selectable
2839 result = [False]
2841 def visit_binary(binary):
2842 c, f = binary.left, binary.right
2843 if (
2844 isinstance(c, expression.ColumnClause)
2845 and isinstance(f, expression.ColumnClause)
2846 and pt.is_derived_from(c.table)
2847 and pt.is_derived_from(f.table)
2848 and mt.is_derived_from(c.table)
2849 and mt.is_derived_from(f.table)
2850 ):
2851 result[0] = True
2853 visitors.traverse(self.primaryjoin, {}, {"binary": visit_binary})
2854 return result[0]
2856 def _tables_overlap(self):
2857 """Return True if parent/child tables have some overlap."""
2859 return selectables_overlap(
2860 self.parent_persist_selectable, self.child_persist_selectable
2861 )
2863 def _annotate_remote(self):
2864 """Annotate the primaryjoin and secondaryjoin
2865 structures with 'remote' annotations marking columns
2866 considered as part of the 'remote' side.
2868 """
2869 if self._has_remote_annotations:
2870 return
2872 if self.secondary is not None:
2873 self._annotate_remote_secondary()
2874 elif self._local_remote_pairs or self._remote_side:
2875 self._annotate_remote_from_args()
2876 elif self._refers_to_parent_table():
2877 self._annotate_selfref(
2878 lambda col: "foreign" in col._annotations, False
2879 )
2880 elif self._tables_overlap():
2881 self._annotate_remote_with_overlap()
2882 else:
2883 self._annotate_remote_distinct_selectables()
2885 def _annotate_remote_secondary(self):
2886 """annotate 'remote' in primaryjoin, secondaryjoin
2887 when 'secondary' is present.
2889 """
2891 def repl(element):
2892 if self.secondary.c.contains_column(element):
2893 return element._annotate({"remote": True})
2895 self.primaryjoin = visitors.replacement_traverse(
2896 self.primaryjoin, {}, repl
2897 )
2898 self.secondaryjoin = visitors.replacement_traverse(
2899 self.secondaryjoin, {}, repl
2900 )
2902 def _annotate_selfref(self, fn, remote_side_given):
2903 """annotate 'remote' in primaryjoin, secondaryjoin
2904 when the relationship is detected as self-referential.
2906 """
2908 def visit_binary(binary):
2909 equated = binary.left.compare(binary.right)
2910 if isinstance(binary.left, expression.ColumnClause) and isinstance(
2911 binary.right, expression.ColumnClause
2912 ):
2913 # assume one to many - FKs are "remote"
2914 if fn(binary.left):
2915 binary.left = binary.left._annotate({"remote": True})
2916 if fn(binary.right) and not equated:
2917 binary.right = binary.right._annotate({"remote": True})
2918 elif not remote_side_given:
2919 self._warn_non_column_elements()
2921 self.primaryjoin = visitors.cloned_traverse(
2922 self.primaryjoin, {}, {"binary": visit_binary}
2923 )
2925 def _annotate_remote_from_args(self):
2926 """annotate 'remote' in primaryjoin, secondaryjoin
2927 when the 'remote_side' or '_local_remote_pairs'
2928 arguments are used.
2930 """
2931 if self._local_remote_pairs:
2932 if self._remote_side:
2933 raise sa_exc.ArgumentError(
2934 "remote_side argument is redundant "
2935 "against more detailed _local_remote_side "
2936 "argument."
2937 )
2939 remote_side = [r for (l, r) in self._local_remote_pairs]
2940 else:
2941 remote_side = self._remote_side
2943 if self._refers_to_parent_table():
2944 self._annotate_selfref(lambda col: col in remote_side, True)
2945 else:
2947 def repl(element):
2948 if element in remote_side:
2949 return element._annotate({"remote": True})
2951 self.primaryjoin = visitors.replacement_traverse(
2952 self.primaryjoin, {}, repl
2953 )
2955 def _annotate_remote_with_overlap(self):
2956 """annotate 'remote' in primaryjoin, secondaryjoin
2957 when the parent/child tables have some set of
2958 tables in common, though is not a fully self-referential
2959 relationship.
2961 """
2963 def visit_binary(binary):
2964 binary.left, binary.right = proc_left_right(
2965 binary.left, binary.right
2966 )
2967 binary.right, binary.left = proc_left_right(
2968 binary.right, binary.left
2969 )
2971 check_entities = (
2972 self.prop is not None and self.prop.mapper is not self.prop.parent
2973 )
2975 def proc_left_right(left, right):
2976 if isinstance(left, expression.ColumnClause) and isinstance(
2977 right, expression.ColumnClause
2978 ):
2979 if self.child_persist_selectable.c.contains_column(
2980 right
2981 ) and self.parent_persist_selectable.c.contains_column(left):
2982 right = right._annotate({"remote": True})
2983 elif (
2984 check_entities
2985 and right._annotations.get("parentmapper") is self.prop.mapper
2986 ):
2987 right = right._annotate({"remote": True})
2988 elif (
2989 check_entities
2990 and left._annotations.get("parentmapper") is self.prop.mapper
2991 ):
2992 left = left._annotate({"remote": True})
2993 else:
2994 self._warn_non_column_elements()
2996 return left, right
2998 self.primaryjoin = visitors.cloned_traverse(
2999 self.primaryjoin, {}, {"binary": visit_binary}
3000 )
3002 def _annotate_remote_distinct_selectables(self):
3003 """annotate 'remote' in primaryjoin, secondaryjoin
3004 when the parent/child tables are entirely
3005 separate.
3007 """
3009 def repl(element):
3010 if self.child_persist_selectable.c.contains_column(element) and (
3011 not self.parent_local_selectable.c.contains_column(element)
3012 or self.child_local_selectable.c.contains_column(element)
3013 ):
3014 return element._annotate({"remote": True})
3016 self.primaryjoin = visitors.replacement_traverse(
3017 self.primaryjoin, {}, repl
3018 )
3020 def _warn_non_column_elements(self):
3021 util.warn(
3022 "Non-simple column elements in primary "
3023 "join condition for property %s - consider using "
3024 "remote() annotations to mark the remote side." % self.prop
3025 )
3027 def _annotate_local(self):
3028 """Annotate the primaryjoin and secondaryjoin
3029 structures with 'local' annotations.
3031 This annotates all column elements found
3032 simultaneously in the parent table
3033 and the join condition that don't have a
3034 'remote' annotation set up from
3035 _annotate_remote() or user-defined.
3037 """
3038 if self._has_annotation(self.primaryjoin, "local"):
3039 return
3041 if self._local_remote_pairs:
3042 local_side = util.column_set(
3043 [l for (l, r) in self._local_remote_pairs]
3044 )
3045 else:
3046 local_side = util.column_set(self.parent_persist_selectable.c)
3048 def locals_(elem):
3049 if "remote" not in elem._annotations and elem in local_side:
3050 return elem._annotate({"local": True})
3052 self.primaryjoin = visitors.replacement_traverse(
3053 self.primaryjoin, {}, locals_
3054 )
3056 def _annotate_parentmapper(self):
3057 if self.prop is None:
3058 return
3060 def parentmappers_(elem):
3061 if "remote" in elem._annotations:
3062 return elem._annotate({"parentmapper": self.prop.mapper})
3063 elif "local" in elem._annotations:
3064 return elem._annotate({"parentmapper": self.prop.parent})
3066 self.primaryjoin = visitors.replacement_traverse(
3067 self.primaryjoin, {}, parentmappers_
3068 )
3070 def _check_remote_side(self):
3071 if not self.local_remote_pairs:
3072 raise sa_exc.ArgumentError(
3073 "Relationship %s could "
3074 "not determine any unambiguous local/remote column "
3075 "pairs based on join condition and remote_side "
3076 "arguments. "
3077 "Consider using the remote() annotation to "
3078 "accurately mark those elements of the join "
3079 "condition that are on the remote side of "
3080 "the relationship." % (self.prop,)
3081 )
3083 def _check_foreign_cols(self, join_condition, primary):
3084 """Check the foreign key columns collected and emit error
3085 messages."""
3087 can_sync = False
3089 foreign_cols = self._gather_columns_with_annotation(
3090 join_condition, "foreign"
3091 )
3093 has_foreign = bool(foreign_cols)
3095 if primary:
3096 can_sync = bool(self.synchronize_pairs)
3097 else:
3098 can_sync = bool(self.secondary_synchronize_pairs)
3100 if (
3101 self.support_sync
3102 and can_sync
3103 or (not self.support_sync and has_foreign)
3104 ):
3105 return
3107 # from here below is just determining the best error message
3108 # to report. Check for a join condition using any operator
3109 # (not just ==), perhaps they need to turn on "viewonly=True".
3110 if self.support_sync and has_foreign and not can_sync:
3111 err = (
3112 "Could not locate any simple equality expressions "
3113 "involving locally mapped foreign key columns for "
3114 "%s join condition "
3115 "'%s' on relationship %s."
3116 % (
3117 primary and "primary" or "secondary",
3118 join_condition,
3119 self.prop,
3120 )
3121 )
3122 err += (
3123 " Ensure that referencing columns are associated "
3124 "with a ForeignKey or ForeignKeyConstraint, or are "
3125 "annotated in the join condition with the foreign() "
3126 "annotation. To allow comparison operators other than "
3127 "'==', the relationship can be marked as viewonly=True."
3128 )
3130 raise sa_exc.ArgumentError(err)
3131 else:
3132 err = (
3133 "Could not locate any relevant foreign key columns "
3134 "for %s join condition '%s' on relationship %s."
3135 % (
3136 primary and "primary" or "secondary",
3137 join_condition,
3138 self.prop,
3139 )
3140 )
3141 err += (
3142 " Ensure that referencing columns are associated "
3143 "with a ForeignKey or ForeignKeyConstraint, or are "
3144 "annotated in the join condition with the foreign() "
3145 "annotation."
3146 )
3147 raise sa_exc.ArgumentError(err)
3149 def _determine_direction(self):
3150 """Determine if this relationship is one to many, many to one,
3151 many to many.
3153 """
3154 if self.secondaryjoin is not None:
3155 self.direction = MANYTOMANY
3156 else:
3157 parentcols = util.column_set(self.parent_persist_selectable.c)
3158 targetcols = util.column_set(self.child_persist_selectable.c)
3160 # fk collection which suggests ONETOMANY.
3161 onetomany_fk = targetcols.intersection(self.foreign_key_columns)
3163 # fk collection which suggests MANYTOONE.
3165 manytoone_fk = parentcols.intersection(self.foreign_key_columns)
3167 if onetomany_fk and manytoone_fk:
3168 # fks on both sides. test for overlap of local/remote
3169 # with foreign key.
3170 # we will gather columns directly from their annotations
3171 # without deannotating, so that we can distinguish on a column
3172 # that refers to itself.
3174 # 1. columns that are both remote and FK suggest
3175 # onetomany.
3176 onetomany_local = self._gather_columns_with_annotation(
3177 self.primaryjoin, "remote", "foreign"
3178 )
3180 # 2. columns that are FK but are not remote (e.g. local)
3181 # suggest manytoone.
3182 manytoone_local = set(
3183 [
3184 c
3185 for c in self._gather_columns_with_annotation(
3186 self.primaryjoin, "foreign"
3187 )
3188 if "remote" not in c._annotations
3189 ]
3190 )
3192 # 3. if both collections are present, remove columns that
3193 # refer to themselves. This is for the case of
3194 # and_(Me.id == Me.remote_id, Me.version == Me.version)
3195 if onetomany_local and manytoone_local:
3196 self_equated = self.remote_columns.intersection(
3197 self.local_columns
3198 )
3199 onetomany_local = onetomany_local.difference(self_equated)
3200 manytoone_local = manytoone_local.difference(self_equated)
3202 # at this point, if only one or the other collection is
3203 # present, we know the direction, otherwise it's still
3204 # ambiguous.
3206 if onetomany_local and not manytoone_local:
3207 self.direction = ONETOMANY
3208 elif manytoone_local and not onetomany_local:
3209 self.direction = MANYTOONE
3210 else:
3211 raise sa_exc.ArgumentError(
3212 "Can't determine relationship"
3213 " direction for relationship '%s' - foreign "
3214 "key columns within the join condition are present "
3215 "in both the parent and the child's mapped tables. "
3216 "Ensure that only those columns referring "
3217 "to a parent column are marked as foreign, "
3218 "either via the foreign() annotation or "
3219 "via the foreign_keys argument." % self.prop
3220 )
3221 elif onetomany_fk:
3222 self.direction = ONETOMANY
3223 elif manytoone_fk:
3224 self.direction = MANYTOONE
3225 else:
3226 raise sa_exc.ArgumentError(
3227 "Can't determine relationship "
3228 "direction for relationship '%s' - foreign "
3229 "key columns are present in neither the parent "
3230 "nor the child's mapped tables" % self.prop
3231 )
3233 def _deannotate_pairs(self, collection):
3234 """provide deannotation for the various lists of
3235 pairs, so that using them in hashes doesn't incur
3236 high-overhead __eq__() comparisons against
3237 original columns mapped.
3239 """
3240 return [(x._deannotate(), y._deannotate()) for x, y in collection]
3242 def _setup_pairs(self):
3243 sync_pairs = []
3244 lrp = util.OrderedSet([])
3245 secondary_sync_pairs = []
3247 def go(joincond, collection):
3248 def visit_binary(binary, left, right):
3249 if (
3250 "remote" in right._annotations
3251 and "remote" not in left._annotations
3252 and self.can_be_synced_fn(left)
3253 ):
3254 lrp.add((left, right))
3255 elif (
3256 "remote" in left._annotations
3257 and "remote" not in right._annotations
3258 and self.can_be_synced_fn(right)
3259 ):
3260 lrp.add((right, left))
3261 if binary.operator is operators.eq and self.can_be_synced_fn(
3262 left, right
3263 ):
3264 if "foreign" in right._annotations:
3265 collection.append((left, right))
3266 elif "foreign" in left._annotations:
3267 collection.append((right, left))
3269 visit_binary_product(visit_binary, joincond)
3271 for joincond, collection in [
3272 (self.primaryjoin, sync_pairs),
3273 (self.secondaryjoin, secondary_sync_pairs),
3274 ]:
3275 if joincond is None:
3276 continue
3277 go(joincond, collection)
3279 self.local_remote_pairs = self._deannotate_pairs(lrp)
3280 self.synchronize_pairs = self._deannotate_pairs(sync_pairs)
3281 self.secondary_synchronize_pairs = self._deannotate_pairs(
3282 secondary_sync_pairs
3283 )
3285 _track_overlapping_sync_targets = weakref.WeakKeyDictionary()
3287 def _warn_for_conflicting_sync_targets(self):
3288 if not self.support_sync:
3289 return
3291 # we would like to detect if we are synchronizing any column
3292 # pairs in conflict with another relationship that wishes to sync
3293 # an entirely different column to the same target. This is a
3294 # very rare edge case so we will try to minimize the memory/overhead
3295 # impact of this check
3296 for from_, to_ in [
3297 (from_, to_) for (from_, to_) in self.synchronize_pairs
3298 ] + [
3299 (from_, to_) for (from_, to_) in self.secondary_synchronize_pairs
3300 ]:
3301 # save ourselves a ton of memory and overhead by only
3302 # considering columns that are subject to a overlapping
3303 # FK constraints at the core level. This condition can arise
3304 # if multiple relationships overlap foreign() directly, but
3305 # we're going to assume it's typically a ForeignKeyConstraint-
3306 # level configuration that benefits from this warning.
3307 if len(to_.foreign_keys) < 2:
3308 continue
3310 if to_ not in self._track_overlapping_sync_targets:
3311 self._track_overlapping_sync_targets[
3312 to_
3313 ] = weakref.WeakKeyDictionary({self.prop: from_})
3314 else:
3315 other_props = []
3316 prop_to_from = self._track_overlapping_sync_targets[to_]
3318 for pr, fr_ in prop_to_from.items():
3319 if (
3320 pr.mapper in mapperlib._mapper_registry
3321 and (
3322 self.prop._persists_for(pr.parent)
3323 or pr._persists_for(self.prop.parent)
3324 )
3325 and fr_ is not from_
3326 and pr not in self.prop._reverse_property
3327 ):
3329 other_props.append((pr, fr_))
3331 if other_props:
3332 util.warn(
3333 "relationship '%s' will copy column %s to column %s, "
3334 "which conflicts with relationship(s): %s. "
3335 "Consider applying "
3336 "viewonly=True to read-only relationships, or provide "
3337 "a primaryjoin condition marking writable columns "
3338 "with the foreign() annotation."
3339 % (
3340 self.prop,
3341 from_,
3342 to_,
3343 ", ".join(
3344 "'%s' (copies %s to %s)" % (pr, fr_, to_)
3345 for (pr, fr_) in other_props
3346 ),
3347 )
3348 )
3349 self._track_overlapping_sync_targets[to_][self.prop] = from_
3351 @util.memoized_property
3352 def remote_columns(self):
3353 return self._gather_join_annotations("remote")
3355 @util.memoized_property
3356 def local_columns(self):
3357 return self._gather_join_annotations("local")
3359 @util.memoized_property
3360 def foreign_key_columns(self):
3361 return self._gather_join_annotations("foreign")
3363 def _gather_join_annotations(self, annotation):
3364 s = set(
3365 self._gather_columns_with_annotation(self.primaryjoin, annotation)
3366 )
3367 if self.secondaryjoin is not None:
3368 s.update(
3369 self._gather_columns_with_annotation(
3370 self.secondaryjoin, annotation
3371 )
3372 )
3373 return {x._deannotate() for x in s}
3375 def _gather_columns_with_annotation(self, clause, *annotation):
3376 annotation = set(annotation)
3377 return set(
3378 [
3379 col
3380 for col in visitors.iterate(clause, {})
3381 if annotation.issubset(col._annotations)
3382 ]
3383 )
3385 def join_targets(
3386 self, source_selectable, dest_selectable, aliased, single_crit=None
3387 ):
3388 """Given a source and destination selectable, create a
3389 join between them.
3391 This takes into account aliasing the join clause
3392 to reference the appropriate corresponding columns
3393 in the target objects, as well as the extra child
3394 criterion, equivalent column sets, etc.
3396 """
3397 # place a barrier on the destination such that
3398 # replacement traversals won't ever dig into it.
3399 # its internal structure remains fixed
3400 # regardless of context.
3401 dest_selectable = _shallow_annotate(
3402 dest_selectable, {"no_replacement_traverse": True}
3403 )
3405 primaryjoin, secondaryjoin, secondary = (
3406 self.primaryjoin,
3407 self.secondaryjoin,
3408 self.secondary,
3409 )
3411 # adjust the join condition for single table inheritance,
3412 # in the case that the join is to a subclass
3413 # this is analogous to the
3414 # "_adjust_for_single_table_inheritance()" method in Query.
3416 if single_crit is not None:
3417 if secondaryjoin is not None:
3418 secondaryjoin = secondaryjoin & single_crit
3419 else:
3420 primaryjoin = primaryjoin & single_crit
3422 if aliased:
3423 if secondary is not None:
3424 secondary = secondary.alias(flat=True)
3425 primary_aliasizer = ClauseAdapter(
3426 secondary, exclude_fn=_ColInAnnotations("local")
3427 )
3428 secondary_aliasizer = ClauseAdapter(
3429 dest_selectable, equivalents=self.child_equivalents
3430 ).chain(primary_aliasizer)
3431 if source_selectable is not None:
3432 primary_aliasizer = ClauseAdapter(
3433 secondary, exclude_fn=_ColInAnnotations("local")
3434 ).chain(
3435 ClauseAdapter(
3436 source_selectable,
3437 equivalents=self.parent_equivalents,
3438 )
3439 )
3441 secondaryjoin = secondary_aliasizer.traverse(secondaryjoin)
3442 else:
3443 primary_aliasizer = ClauseAdapter(
3444 dest_selectable,
3445 exclude_fn=_ColInAnnotations("local"),
3446 equivalents=self.child_equivalents,
3447 )
3448 if source_selectable is not None:
3449 primary_aliasizer.chain(
3450 ClauseAdapter(
3451 source_selectable,
3452 exclude_fn=_ColInAnnotations("remote"),
3453 equivalents=self.parent_equivalents,
3454 )
3455 )
3456 secondary_aliasizer = None
3458 primaryjoin = primary_aliasizer.traverse(primaryjoin)
3459 target_adapter = secondary_aliasizer or primary_aliasizer
3460 target_adapter.exclude_fn = None
3461 else:
3462 target_adapter = None
3463 return (
3464 primaryjoin,
3465 secondaryjoin,
3466 secondary,
3467 target_adapter,
3468 dest_selectable,
3469 )
3471 def create_lazy_clause(self, reverse_direction=False):
3472 binds = util.column_dict()
3473 equated_columns = util.column_dict()
3475 has_secondary = self.secondaryjoin is not None
3477 if has_secondary:
3478 lookup = collections.defaultdict(list)
3479 for l, r in self.local_remote_pairs:
3480 lookup[l].append((l, r))
3481 equated_columns[r] = l
3482 elif not reverse_direction:
3483 for l, r in self.local_remote_pairs:
3484 equated_columns[r] = l
3485 else:
3486 for l, r in self.local_remote_pairs:
3487 equated_columns[l] = r
3489 def col_to_bind(col):
3491 if (
3492 (not reverse_direction and "local" in col._annotations)
3493 or reverse_direction
3494 and (
3495 (has_secondary and col in lookup)
3496 or (not has_secondary and "remote" in col._annotations)
3497 )
3498 ):
3499 if col not in binds:
3500 binds[col] = sql.bindparam(
3501 None, None, type_=col.type, unique=True
3502 )
3503 return binds[col]
3504 return None
3506 lazywhere = self.primaryjoin
3507 if self.secondaryjoin is None or not reverse_direction:
3508 lazywhere = visitors.replacement_traverse(
3509 lazywhere, {}, col_to_bind
3510 )
3512 if self.secondaryjoin is not None:
3513 secondaryjoin = self.secondaryjoin
3514 if reverse_direction:
3515 secondaryjoin = visitors.replacement_traverse(
3516 secondaryjoin, {}, col_to_bind
3517 )
3518 lazywhere = sql.and_(lazywhere, secondaryjoin)
3520 bind_to_col = {binds[col].key: col for col in binds}
3522 return lazywhere, bind_to_col, equated_columns
3525class _ColInAnnotations(object):
3526 """Seralizable equivalent to:
3528 lambda c: "name" in c._annotations
3529 """
3531 def __init__(self, name):
3532 self.name = name
3534 def __call__(self, c):
3535 return self.name in c._annotations