1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875 |
- # orm/relationships.py
- # Copyright (C) 2005-2017 the SQLAlchemy authors and contributors
- # <see AUTHORS file>
- #
- # This module is part of SQLAlchemy and is released under
- # the MIT License: http://www.opensource.org/licenses/mit-license.php
- """Heuristics related to join conditions as used in
- :func:`.relationship`.
- Provides the :class:`.JoinCondition` object, which encapsulates
- SQL annotation and aliasing behavior focused on the `primaryjoin`
- and `secondaryjoin` aspects of :func:`.relationship`.
- """
- from __future__ import absolute_import
- from .. import sql, util, exc as sa_exc, schema, log
- import weakref
- from .util import CascadeOptions, _orm_annotate, _orm_deannotate
- from . import dependency
- from . import attributes
- from ..sql.util import (
- ClauseAdapter,
- join_condition, _shallow_annotate, visit_binary_product,
- _deep_deannotate, selectables_overlap, adapt_criterion_to_null
- )
- from ..sql import operators, expression, visitors
- from .interfaces import (MANYTOMANY, MANYTOONE, ONETOMANY,
- StrategizedProperty, PropComparator)
- from ..inspection import inspect
- from . import mapper as mapperlib
- import collections
- def remote(expr):
- """Annotate a portion of a primaryjoin expression
- with a 'remote' annotation.
- See the section :ref:`relationship_custom_foreign` for a
- description of use.
- .. versionadded:: 0.8
- .. seealso::
- :ref:`relationship_custom_foreign`
- :func:`.foreign`
- """
- return _annotate_columns(expression._clause_element_as_expr(expr),
- {"remote": True})
- def foreign(expr):
- """Annotate a portion of a primaryjoin expression
- with a 'foreign' annotation.
- See the section :ref:`relationship_custom_foreign` for a
- description of use.
- .. versionadded:: 0.8
- .. seealso::
- :ref:`relationship_custom_foreign`
- :func:`.remote`
- """
- return _annotate_columns(expression._clause_element_as_expr(expr),
- {"foreign": True})
- @log.class_logger
- @util.langhelpers.dependency_for("sqlalchemy.orm.properties")
- class RelationshipProperty(StrategizedProperty):
- """Describes an object property that holds a single item or list
- of items that correspond to a related database table.
- Public constructor is the :func:`.orm.relationship` function.
- See also:
- :ref:`relationship_config_toplevel`
- """
- strategy_wildcard_key = 'relationship'
- _dependency_processor = None
- def __init__(self, argument,
- secondary=None, primaryjoin=None,
- secondaryjoin=None,
- foreign_keys=None,
- uselist=None,
- order_by=False,
- backref=None,
- back_populates=None,
- post_update=False,
- cascade=False, extension=None,
- viewonly=False, lazy=True,
- collection_class=None, passive_deletes=False,
- passive_updates=True, remote_side=None,
- enable_typechecks=True, join_depth=None,
- comparator_factory=None,
- single_parent=False, innerjoin=False,
- distinct_target_key=None,
- doc=None,
- active_history=False,
- cascade_backrefs=True,
- load_on_pending=False,
- bake_queries=True,
- _local_remote_pairs=None,
- query_class=None,
- info=None):
- """Provide a relationship between two mapped classes.
- This corresponds to a parent-child or associative table relationship.
- The constructed class is an instance of
- :class:`.RelationshipProperty`.
- A typical :func:`.relationship`, used in a classical mapping::
- mapper(Parent, properties={
- 'children': relationship(Child)
- })
- Some arguments accepted by :func:`.relationship` optionally accept a
- callable function, which when called produces the desired value.
- The callable is invoked by the parent :class:`.Mapper` at "mapper
- initialization" time, which happens only when mappers are first used,
- and is assumed to be after all mappings have been constructed. This
- can be used to resolve order-of-declaration and other dependency
- issues, such as if ``Child`` is declared below ``Parent`` in the same
- file::
- mapper(Parent, properties={
- "children":relationship(lambda: Child,
- order_by=lambda: Child.id)
- })
- When using the :ref:`declarative_toplevel` extension, the Declarative
- initializer allows string arguments to be passed to
- :func:`.relationship`. These string arguments are converted into
- callables that evaluate the string as Python code, using the
- Declarative class-registry as a namespace. This allows the lookup of
- related classes to be automatic via their string name, and removes the
- need to import related classes at all into the local module space::
- from sqlalchemy.ext.declarative import declarative_base
- Base = declarative_base()
- class Parent(Base):
- __tablename__ = 'parent'
- id = Column(Integer, primary_key=True)
- children = relationship("Child", order_by="Child.id")
- .. seealso::
- :ref:`relationship_config_toplevel` - Full introductory and
- reference documentation for :func:`.relationship`.
- :ref:`orm_tutorial_relationship` - ORM tutorial introduction.
- :param argument:
- a mapped class, or actual :class:`.Mapper` instance, representing
- the target of the relationship.
- :paramref:`~.relationship.argument` may also be passed as a callable
- function which is evaluated at mapper initialization time, and may
- be passed as a Python-evaluable string when using Declarative.
- .. seealso::
- :ref:`declarative_configuring_relationships` - further detail
- on relationship configuration when using Declarative.
- :param secondary:
- for a many-to-many relationship, specifies the intermediary
- table, and is typically an instance of :class:`.Table`.
- In less common circumstances, the argument may also be specified
- as an :class:`.Alias` construct, or even a :class:`.Join` construct.
- :paramref:`~.relationship.secondary` may
- also be passed as a callable function which is evaluated at
- mapper initialization time. When using Declarative, it may also
- be a string argument noting the name of a :class:`.Table` that is
- present in the :class:`.MetaData` collection associated with the
- parent-mapped :class:`.Table`.
- The :paramref:`~.relationship.secondary` keyword argument is
- typically applied in the case where the intermediary :class:`.Table`
- is not otherwise expressed in any direct class mapping. If the
- "secondary" table is also explicitly mapped elsewhere (e.g. as in
- :ref:`association_pattern`), one should consider applying the
- :paramref:`~.relationship.viewonly` flag so that this
- :func:`.relationship` is not used for persistence operations which
- may conflict with those of the association object pattern.
- .. seealso::
- :ref:`relationships_many_to_many` - Reference example of "many
- to many".
- :ref:`orm_tutorial_many_to_many` - ORM tutorial introduction to
- many-to-many relationships.
- :ref:`self_referential_many_to_many` - Specifics on using
- many-to-many in a self-referential case.
- :ref:`declarative_many_to_many` - Additional options when using
- Declarative.
- :ref:`association_pattern` - an alternative to
- :paramref:`~.relationship.secondary` when composing association
- table relationships, allowing additional attributes to be
- specified on the association table.
- :ref:`composite_secondary_join` - a lesser-used pattern which
- in some cases can enable complex :func:`.relationship` SQL
- conditions to be used.
- .. versionadded:: 0.9.2 :paramref:`~.relationship.secondary` works
- more effectively when referring to a :class:`.Join` instance.
- :param active_history=False:
- When ``True``, indicates that the "previous" value for a
- many-to-one reference should be loaded when replaced, if
- not already loaded. Normally, history tracking logic for
- simple many-to-ones only needs to be aware of the "new"
- value in order to perform a flush. This flag is available
- for applications that make use of
- :func:`.attributes.get_history` which also need to know
- the "previous" value of the attribute.
- :param backref:
- indicates the string name of a property to be placed on the related
- mapper's class that will handle this relationship in the other
- direction. The other property will be created automatically
- when the mappers are configured. Can also be passed as a
- :func:`.backref` object to control the configuration of the
- new relationship.
- .. seealso::
- :ref:`relationships_backref` - Introductory documentation and
- examples.
- :paramref:`~.relationship.back_populates` - alternative form
- of backref specification.
- :func:`.backref` - allows control over :func:`.relationship`
- configuration when using :paramref:`~.relationship.backref`.
- :param back_populates:
- Takes a string name and has the same meaning as
- :paramref:`~.relationship.backref`, except the complementing
- property is **not** created automatically, and instead must be
- configured explicitly on the other mapper. The complementing
- property should also indicate
- :paramref:`~.relationship.back_populates` to this relationship to
- ensure proper functioning.
- .. seealso::
- :ref:`relationships_backref` - Introductory documentation and
- examples.
- :paramref:`~.relationship.backref` - alternative form
- of backref specification.
- :param bake_queries=True:
- Use the :class:`.BakedQuery` cache to cache the construction of SQL
- used in lazy loads, when the :func:`.bake_lazy_loaders` function has
- first been called. Defaults to True and is intended to provide an
- "opt out" flag per-relationship when the baked query cache system is
- in use.
- .. warning::
- This flag **only** has an effect when the application-wide
- :func:`.bake_lazy_loaders` function has been called. It
- defaults to True so is an "opt out" flag.
- Setting this flag to False when baked queries are otherwise in
- use might be to reduce
- ORM memory use for this :func:`.relationship`, or to work around
- unresolved stability issues observed within the baked query
- cache system.
- .. versionadded:: 1.0.0
- .. seealso::
- :ref:`baked_toplevel`
- :param cascade:
- a comma-separated list of cascade rules which determines how
- Session operations should be "cascaded" from parent to child.
- This defaults to ``False``, which means the default cascade
- should be used - this default cascade is ``"save-update, merge"``.
- The available cascades are ``save-update``, ``merge``,
- ``expunge``, ``delete``, ``delete-orphan``, and ``refresh-expire``.
- An additional option, ``all`` indicates shorthand for
- ``"save-update, merge, refresh-expire,
- expunge, delete"``, and is often used as in ``"all, delete-orphan"``
- to indicate that related objects should follow along with the
- parent object in all cases, and be deleted when de-associated.
- .. seealso::
- :ref:`unitofwork_cascades` - Full detail on each of the available
- cascade options.
- :ref:`tutorial_delete_cascade` - Tutorial example describing
- a delete cascade.
- :param cascade_backrefs=True:
- a boolean value indicating if the ``save-update`` cascade should
- operate along an assignment event intercepted by a backref.
- When set to ``False``, the attribute managed by this relationship
- will not cascade an incoming transient object into the session of a
- persistent parent, if the event is received via backref.
- .. seealso::
- :ref:`backref_cascade` - Full discussion and examples on how
- the :paramref:`~.relationship.cascade_backrefs` option is used.
- :param collection_class:
- a class or callable that returns a new list-holding object. will
- be used in place of a plain list for storing elements.
- .. seealso::
- :ref:`custom_collections` - Introductory documentation and
- examples.
- :param comparator_factory:
- a class which extends :class:`.RelationshipProperty.Comparator`
- which provides custom SQL clause generation for comparison
- operations.
- .. seealso::
- :class:`.PropComparator` - some detail on redefining comparators
- at this level.
- :ref:`custom_comparators` - Brief intro to this feature.
- :param distinct_target_key=None:
- Indicate if a "subquery" eager load should apply the DISTINCT
- keyword to the innermost SELECT statement. When left as ``None``,
- the DISTINCT keyword will be applied in those cases when the target
- columns do not comprise the full primary key of the target table.
- When set to ``True``, the DISTINCT keyword is applied to the
- innermost SELECT unconditionally.
- It may be desirable to set this flag to False when the DISTINCT is
- reducing performance of the innermost subquery beyond that of what
- duplicate innermost rows may be causing.
- .. versionadded:: 0.8.3 -
- :paramref:`~.relationship.distinct_target_key` allows the
- subquery eager loader to apply a DISTINCT modifier to the
- innermost SELECT.
- .. versionchanged:: 0.9.0 -
- :paramref:`~.relationship.distinct_target_key` now defaults to
- ``None``, so that the feature enables itself automatically for
- those cases where the innermost query targets a non-unique
- key.
- .. seealso::
- :ref:`loading_toplevel` - includes an introduction to subquery
- eager loading.
- :param doc:
- docstring which will be applied to the resulting descriptor.
- :param extension:
- an :class:`.AttributeExtension` instance, or list of extensions,
- which will be prepended to the list of attribute listeners for
- the resulting descriptor placed on the class.
- .. deprecated:: 0.7 Please see :class:`.AttributeEvents`.
- :param foreign_keys:
- a list of columns which are to be used as "foreign key"
- columns, or columns which refer to the value in a remote
- column, within the context of this :func:`.relationship`
- object's :paramref:`~.relationship.primaryjoin` condition.
- That is, if the :paramref:`~.relationship.primaryjoin`
- condition of this :func:`.relationship` is ``a.id ==
- b.a_id``, and the values in ``b.a_id`` are required to be
- present in ``a.id``, then the "foreign key" column of this
- :func:`.relationship` is ``b.a_id``.
- In normal cases, the :paramref:`~.relationship.foreign_keys`
- parameter is **not required.** :func:`.relationship` will
- automatically determine which columns in the
- :paramref:`~.relationship.primaryjoin` conditition are to be
- considered "foreign key" columns based on those
- :class:`.Column` objects that specify :class:`.ForeignKey`,
- or are otherwise listed as referencing columns in a
- :class:`.ForeignKeyConstraint` construct.
- :paramref:`~.relationship.foreign_keys` is only needed when:
- 1. There is more than one way to construct a join from the local
- table to the remote table, as there are multiple foreign key
- references present. Setting ``foreign_keys`` will limit the
- :func:`.relationship` to consider just those columns specified
- here as "foreign".
- .. versionchanged:: 0.8
- A multiple-foreign key join ambiguity can be resolved by
- setting the :paramref:`~.relationship.foreign_keys`
- parameter alone, without the need to explicitly set
- :paramref:`~.relationship.primaryjoin` as well.
- 2. The :class:`.Table` being mapped does not actually have
- :class:`.ForeignKey` or :class:`.ForeignKeyConstraint`
- constructs present, often because the table
- was reflected from a database that does not support foreign key
- reflection (MySQL MyISAM).
- 3. The :paramref:`~.relationship.primaryjoin` argument is used to
- construct a non-standard join condition, which makes use of
- columns or expressions that do not normally refer to their
- "parent" column, such as a join condition expressed by a
- complex comparison using a SQL function.
- The :func:`.relationship` construct will raise informative
- error messages that suggest the use of the
- :paramref:`~.relationship.foreign_keys` parameter when
- presented with an ambiguous condition. In typical cases,
- if :func:`.relationship` doesn't raise any exceptions, the
- :paramref:`~.relationship.foreign_keys` parameter is usually
- not needed.
- :paramref:`~.relationship.foreign_keys` may also be passed as a
- callable function which is evaluated at mapper initialization time,
- and may be passed as a Python-evaluable string when using
- Declarative.
- .. seealso::
- :ref:`relationship_foreign_keys`
- :ref:`relationship_custom_foreign`
- :func:`.foreign` - allows direct annotation of the "foreign"
- columns within a :paramref:`~.relationship.primaryjoin` condition.
- .. versionadded:: 0.8
- The :func:`.foreign` annotation can also be applied
- directly to the :paramref:`~.relationship.primaryjoin`
- expression, which is an alternate, more specific system of
- describing which columns in a particular
- :paramref:`~.relationship.primaryjoin` should be considered
- "foreign".
- :param info: Optional data dictionary which will be populated into the
- :attr:`.MapperProperty.info` attribute of this object.
- .. versionadded:: 0.8
- :param innerjoin=False:
- when ``True``, joined eager loads will use an inner join to join
- against related tables instead of an outer join. The purpose
- of this option is generally one of performance, as inner joins
- generally perform better than outer joins.
- This flag can be set to ``True`` when the relationship references an
- object via many-to-one using local foreign keys that are not
- nullable, or when the reference is one-to-one or a collection that
- is guaranteed to have one or at least one entry.
- The option supports the same "nested" and "unnested" options as
- that of :paramref:`.joinedload.innerjoin`. See that flag
- for details on nested / unnested behaviors.
- .. seealso::
- :paramref:`.joinedload.innerjoin` - the option as specified by
- loader option, including detail on nesting behavior.
- :ref:`what_kind_of_loading` - Discussion of some details of
- various loader options.
- :param join_depth:
- when non-``None``, an integer value indicating how many levels
- deep "eager" loaders should join on a self-referring or cyclical
- relationship. The number counts how many times the same Mapper
- shall be present in the loading condition along a particular join
- branch. When left at its default of ``None``, eager loaders
- will stop chaining when they encounter a the same target mapper
- which is already higher up in the chain. This option applies
- both to joined- and subquery- eager loaders.
- .. seealso::
- :ref:`self_referential_eager_loading` - Introductory documentation
- and examples.
- :param lazy='select': specifies
- how the related items should be loaded. Default value is
- ``select``. Values include:
- * ``select`` - items should be loaded lazily when the property is
- first accessed, using a separate SELECT statement, or identity map
- fetch for simple many-to-one references.
- * ``immediate`` - items should be loaded as the parents are loaded,
- using a separate SELECT statement, or identity map fetch for
- simple many-to-one references.
- * ``joined`` - items should be loaded "eagerly" in the same query as
- that of the parent, using a JOIN or LEFT OUTER JOIN. Whether
- the join is "outer" or not is determined by the
- :paramref:`~.relationship.innerjoin` parameter.
- * ``subquery`` - items should be loaded "eagerly" as the parents are
- loaded, using one additional SQL statement, which issues a JOIN to
- a subquery of the original statement, for each collection
- requested.
- * ``noload`` - no loading should occur at any time. This is to
- support "write-only" attributes, or attributes which are
- populated in some manner specific to the application.
- * ``raise`` - lazy loading is disallowed; accessing
- the attribute, if its value were not already loaded via eager
- loading, will raise an :exc:`~sqlalchemy.exc.InvalidRequestError`.
- This strategy can be used when objects are to be detached from
- their attached :class:`.Session` after they are loaded.
- .. versionadded:: 1.1
- * ``raise_on_sql`` - lazy loading that emits SQL is disallowed;
- accessing the attribute, if its value were not already loaded via
- eager loading, will raise an
- :exc:`~sqlalchemy.exc.InvalidRequestError`, **if the lazy load
- needs to emit SQL**. If the lazy load can pull the related value
- from the identity map or determine that it should be None, the
- value is loaded. This strategy can be used when objects will
- remain associated with the attached :class:`.Session`, however
- additional SELECT statements should be blocked.
- .. versionadded:: 1.1
- * ``dynamic`` - the attribute will return a pre-configured
- :class:`.Query` object for all read
- operations, onto which further filtering operations can be
- applied before iterating the results. See
- the section :ref:`dynamic_relationship` for more details.
- * True - a synonym for 'select'
- * False - a synonym for 'joined'
- * None - a synonym for 'noload'
- .. seealso::
- :doc:`/orm/loading_relationships` - Full documentation on relationship loader
- configuration.
- :ref:`dynamic_relationship` - detail on the ``dynamic`` option.
- :ref:`collections_noload_raiseload` - notes on "noload" and "raise"
- :param load_on_pending=False:
- Indicates loading behavior for transient or pending parent objects.
- When set to ``True``, causes the lazy-loader to
- issue a query for a parent object that is not persistent, meaning it
- has never been flushed. This may take effect for a pending object
- when autoflush is disabled, or for a transient object that has been
- "attached" to a :class:`.Session` but is not part of its pending
- collection.
- The :paramref:`~.relationship.load_on_pending` flag does not improve
- behavior when the ORM is used normally - object references should be
- constructed at the object level, not at the foreign key level, so
- that they are present in an ordinary way before a flush proceeds.
- This flag is not not intended for general use.
- .. seealso::
- :meth:`.Session.enable_relationship_loading` - this method
- establishes "load on pending" behavior for the whole object, and
- also allows loading on objects that remain transient or
- detached.
- :param order_by:
- indicates the ordering that should be applied when loading these
- items. :paramref:`~.relationship.order_by` is expected to refer to
- one of the :class:`.Column` objects to which the target class is
- mapped, or the attribute itself bound to the target class which
- refers to the column.
- :paramref:`~.relationship.order_by` may also be passed as a callable
- function which is evaluated at mapper initialization time, and may
- be passed as a Python-evaluable string when using Declarative.
- :param passive_deletes=False:
- Indicates loading behavior during delete operations.
- A value of True indicates that unloaded child items should not
- be loaded during a delete operation on the parent. Normally,
- when a parent item is deleted, all child items are loaded so
- that they can either be marked as deleted, or have their
- foreign key to the parent set to NULL. Marking this flag as
- True usually implies an ON DELETE <CASCADE|SET NULL> rule is in
- place which will handle updating/deleting child rows on the
- database side.
- Additionally, setting the flag to the string value 'all' will
- disable the "nulling out" of the child foreign keys, when there
- is no delete or delete-orphan cascade enabled. This is
- typically used when a triggering or error raise scenario is in
- place on the database side. Note that the foreign key
- attributes on in-session child objects will not be changed
- after a flush occurs so this is a very special use-case
- setting.
- .. seealso::
- :ref:`passive_deletes` - Introductory documentation
- and examples.
- :param passive_updates=True:
- Indicates the persistence behavior to take when a referenced
- primary key value changes in place, indicating that the referencing
- foreign key columns will also need their value changed.
- When True, it is assumed that ``ON UPDATE CASCADE`` is configured on
- the foreign key in the database, and that the database will
- handle propagation of an UPDATE from a source column to
- dependent rows. When False, the SQLAlchemy :func:`.relationship`
- construct will attempt to emit its own UPDATE statements to
- modify related targets. However note that SQLAlchemy **cannot**
- emit an UPDATE for more than one level of cascade. Also,
- setting this flag to False is not compatible in the case where
- the database is in fact enforcing referential integrity, unless
- those constraints are explicitly "deferred", if the target backend
- supports it.
- It is highly advised that an application which is employing
- mutable primary keys keeps ``passive_updates`` set to True,
- and instead uses the referential integrity features of the database
- itself in order to handle the change efficiently and fully.
- .. seealso::
- :ref:`passive_updates` - Introductory documentation and
- examples.
- :paramref:`.mapper.passive_updates` - a similar flag which
- takes effect for joined-table inheritance mappings.
- :param post_update:
- this indicates that the relationship should be handled by a
- second UPDATE statement after an INSERT or before a
- DELETE. Currently, it also will issue an UPDATE after the
- instance was UPDATEd as well, although this technically should
- be improved. This flag is used to handle saving bi-directional
- dependencies between two individual rows (i.e. each row
- references the other), where it would otherwise be impossible to
- INSERT or DELETE both rows fully since one row exists before the
- other. Use this flag when a particular mapping arrangement will
- incur two rows that are dependent on each other, such as a table
- that has a one-to-many relationship to a set of child rows, and
- also has a column that references a single child row within that
- list (i.e. both tables contain a foreign key to each other). If
- a flush operation returns an error that a "cyclical
- dependency" was detected, this is a cue that you might want to
- use :paramref:`~.relationship.post_update` to "break" the cycle.
- .. seealso::
- :ref:`post_update` - Introductory documentation and examples.
- :param primaryjoin:
- a SQL expression that will be used as the primary
- join of this child object against the parent object, or in a
- many-to-many relationship the join of the primary object to the
- association table. By default, this value is computed based on the
- foreign key relationships of the parent and child tables (or
- association table).
- :paramref:`~.relationship.primaryjoin` may also be passed as a
- callable function which is evaluated at mapper initialization time,
- and may be passed as a Python-evaluable string when using
- Declarative.
- .. seealso::
- :ref:`relationship_primaryjoin`
- :param remote_side:
- used for self-referential relationships, indicates the column or
- list of columns that form the "remote side" of the relationship.
- :paramref:`.relationship.remote_side` may also be passed as a
- callable function which is evaluated at mapper initialization time,
- and may be passed as a Python-evaluable string when using
- Declarative.
- .. versionchanged:: 0.8
- The :func:`.remote` annotation can also be applied
- directly to the ``primaryjoin`` expression, which is an
- alternate, more specific system of describing which columns in a
- particular ``primaryjoin`` should be considered "remote".
- .. seealso::
- :ref:`self_referential` - in-depth explanation of how
- :paramref:`~.relationship.remote_side`
- is used to configure self-referential relationships.
- :func:`.remote` - an annotation function that accomplishes the
- same purpose as :paramref:`~.relationship.remote_side`, typically
- when a custom :paramref:`~.relationship.primaryjoin` condition
- is used.
- :param query_class:
- a :class:`.Query` subclass that will be used as the base of the
- "appender query" returned by a "dynamic" relationship, that
- is, a relationship that specifies ``lazy="dynamic"`` or was
- otherwise constructed using the :func:`.orm.dynamic_loader`
- function.
- .. seealso::
- :ref:`dynamic_relationship` - Introduction to "dynamic"
- relationship loaders.
- :param secondaryjoin:
- a SQL expression that will be used as the join of
- an association table to the child object. By default, this value is
- computed based on the foreign key relationships of the association
- and child tables.
- :paramref:`~.relationship.secondaryjoin` may also be passed as a
- callable function which is evaluated at mapper initialization time,
- and may be passed as a Python-evaluable string when using
- Declarative.
- .. seealso::
- :ref:`relationship_primaryjoin`
- :param single_parent:
- when True, installs a validator which will prevent objects
- from being associated with more than one parent at a time.
- This is used for many-to-one or many-to-many relationships that
- should be treated either as one-to-one or one-to-many. Its usage
- is optional, except for :func:`.relationship` constructs which
- are many-to-one or many-to-many and also
- specify the ``delete-orphan`` cascade option. The
- :func:`.relationship` construct itself will raise an error
- instructing when this option is required.
- .. seealso::
- :ref:`unitofwork_cascades` - includes detail on when the
- :paramref:`~.relationship.single_parent` flag may be appropriate.
- :param uselist:
- a boolean that indicates if this property should be loaded as a
- list or a scalar. In most cases, this value is determined
- automatically by :func:`.relationship` at mapper configuration
- time, based on the type and direction
- of the relationship - one to many forms a list, many to one
- forms a scalar, many to many is a list. If a scalar is desired
- where normally a list would be present, such as a bi-directional
- one-to-one relationship, set :paramref:`~.relationship.uselist` to
- False.
- The :paramref:`~.relationship.uselist` flag is also available on an
- existing :func:`.relationship` construct as a read-only attribute,
- which can be used to determine if this :func:`.relationship` deals
- with collections or scalar attributes::
- >>> User.addresses.property.uselist
- True
- .. seealso::
- :ref:`relationships_one_to_one` - Introduction to the "one to
- one" relationship pattern, which is typically when the
- :paramref:`~.relationship.uselist` flag is needed.
- :param viewonly=False:
- when set to True, the relationship is used only for loading objects,
- and not for any persistence operation. A :func:`.relationship`
- which specifies :paramref:`~.relationship.viewonly` can work
- with a wider range of SQL operations within the
- :paramref:`~.relationship.primaryjoin` condition, including
- operations that feature the use of a variety of comparison operators
- as well as SQL functions such as :func:`~.sql.expression.cast`. The
- :paramref:`~.relationship.viewonly` flag is also of general use when
- defining any kind of :func:`~.relationship` that doesn't represent
- the full set of related objects, to prevent modifications of the
- collection from resulting in persistence operations.
- """
- super(RelationshipProperty, self).__init__()
- self.uselist = uselist
- self.argument = argument
- self.secondary = secondary
- self.primaryjoin = primaryjoin
- self.secondaryjoin = secondaryjoin
- self.post_update = post_update
- self.direction = None
- self.viewonly = viewonly
- self.lazy = lazy
- self.single_parent = single_parent
- self._user_defined_foreign_keys = foreign_keys
- self.collection_class = collection_class
- self.passive_deletes = passive_deletes
- self.cascade_backrefs = cascade_backrefs
- self.passive_updates = passive_updates
- self.remote_side = remote_side
- self.enable_typechecks = enable_typechecks
- self.query_class = query_class
- self.innerjoin = innerjoin
- self.distinct_target_key = distinct_target_key
- self.doc = doc
- self.active_history = active_history
- self.join_depth = join_depth
- self.local_remote_pairs = _local_remote_pairs
- self.extension = extension
- self.bake_queries = bake_queries
- self.load_on_pending = load_on_pending
- self.comparator_factory = comparator_factory or \
- RelationshipProperty.Comparator
- self.comparator = self.comparator_factory(self, None)
- util.set_creation_order(self)
- if info is not None:
- self.info = info
- self.strategy_key = (("lazy", self.lazy), )
- self._reverse_property = set()
- self.cascade = cascade if cascade is not False \
- else "save-update, merge"
- self.order_by = order_by
- self.back_populates = back_populates
- if self.back_populates:
- if backref:
- raise sa_exc.ArgumentError(
- "backref and back_populates keyword arguments "
- "are mutually exclusive")
- self.backref = None
- else:
- self.backref = backref
- def instrument_class(self, mapper):
- attributes.register_descriptor(
- mapper.class_,
- self.key,
- comparator=self.comparator_factory(self, mapper),
- parententity=mapper,
- doc=self.doc,
- )
- class Comparator(PropComparator):
- """Produce boolean, comparison, and other operators for
- :class:`.RelationshipProperty` attributes.
- See the documentation for :class:`.PropComparator` for a brief
- overview of ORM level operator definition.
- See also:
- :class:`.PropComparator`
- :class:`.ColumnProperty.Comparator`
- :class:`.ColumnOperators`
- :ref:`types_operators`
- :attr:`.TypeEngine.comparator_factory`
- """
- _of_type = None
- def __init__(
- self, prop, parentmapper, adapt_to_entity=None, of_type=None):
- """Construction of :class:`.RelationshipProperty.Comparator`
- is internal to the ORM's attribute mechanics.
- """
- self.prop = prop
- self._parententity = parentmapper
- self._adapt_to_entity = adapt_to_entity
- if of_type:
- self._of_type = of_type
- def adapt_to_entity(self, adapt_to_entity):
- return self.__class__(self.property, self._parententity,
- adapt_to_entity=adapt_to_entity,
- of_type=self._of_type)
- @util.memoized_property
- def mapper(self):
- """The target :class:`.Mapper` referred to by this
- :class:`.RelationshipProperty.Comparator`.
- This is the "target" or "remote" side of the
- :func:`.relationship`.
- """
- return self.property.mapper
- @util.memoized_property
- def _parententity(self):
- return self.property.parent
- def _source_selectable(self):
- if self._adapt_to_entity:
- return self._adapt_to_entity.selectable
- else:
- return self.property.parent._with_polymorphic_selectable
- def __clause_element__(self):
- adapt_from = self._source_selectable()
- if self._of_type:
- of_type = inspect(self._of_type).mapper
- else:
- of_type = None
- pj, sj, source, dest, \
- secondary, target_adapter = self.property._create_joins(
- source_selectable=adapt_from,
- source_polymorphic=True,
- of_type=of_type)
- if sj is not None:
- return pj & sj
- else:
- return pj
- def of_type(self, cls):
- """Produce a construct that represents a particular 'subtype' of
- attribute for the parent class.
- Currently this is usable in conjunction with :meth:`.Query.join`
- and :meth:`.Query.outerjoin`.
- """
- return RelationshipProperty.Comparator(
- self.property,
- self._parententity,
- adapt_to_entity=self._adapt_to_entity,
- of_type=cls)
- def in_(self, other):
- """Produce an IN clause - this is not implemented
- for :func:`~.orm.relationship`-based attributes at this time.
- """
- raise NotImplementedError('in_() not yet supported for '
- 'relationships. For a simple '
- 'many-to-one, use in_() against '
- 'the set of foreign key values.')
- __hash__ = None
- def __eq__(self, other):
- """Implement the ``==`` operator.
- In a many-to-one context, such as::
- MyClass.some_prop == <some object>
- this will typically produce a
- clause such as::
- mytable.related_id == <some id>
- Where ``<some id>`` is the primary key of the given
- object.
- The ``==`` operator provides partial functionality for non-
- many-to-one comparisons:
- * Comparisons against collections are not supported.
- Use :meth:`~.RelationshipProperty.Comparator.contains`.
- * Compared to a scalar one-to-many, will produce a
- clause that compares the target columns in the parent to
- the given target.
- * Compared to a scalar many-to-many, an alias
- of the association table will be rendered as
- well, forming a natural join that is part of the
- main body of the query. This will not work for
- queries that go beyond simple AND conjunctions of
- comparisons, such as those which use OR. Use
- explicit joins, outerjoins, or
- :meth:`~.RelationshipProperty.Comparator.has` for
- more comprehensive non-many-to-one scalar
- membership tests.
- * Comparisons against ``None`` given in a one-to-many
- or many-to-many context produce a NOT EXISTS clause.
- """
- if isinstance(other, (util.NoneType, expression.Null)):
- if self.property.direction in [ONETOMANY, MANYTOMANY]:
- return ~self._criterion_exists()
- else:
- return _orm_annotate(self.property._optimized_compare(
- None, adapt_source=self.adapter))
- elif self.property.uselist:
- raise sa_exc.InvalidRequestError(
- "Can't compare a collection to an object or collection; "
- "use contains() to test for membership.")
- else:
- return _orm_annotate(
- self.property._optimized_compare(
- other, adapt_source=self.adapter))
- def _criterion_exists(self, criterion=None, **kwargs):
- if getattr(self, '_of_type', None):
- info = inspect(self._of_type)
- target_mapper, to_selectable, is_aliased_class = \
- info.mapper, info.selectable, info.is_aliased_class
- if self.property._is_self_referential and not \
- is_aliased_class:
- to_selectable = to_selectable.alias()
- single_crit = target_mapper._single_table_criterion
- if single_crit is not None:
- if criterion is not None:
- criterion = single_crit & criterion
- else:
- criterion = single_crit
- else:
- is_aliased_class = False
- to_selectable = None
- if self.adapter:
- source_selectable = self._source_selectable()
- else:
- source_selectable = None
- pj, sj, source, dest, secondary, target_adapter = \
- self.property._create_joins(
- dest_polymorphic=True,
- dest_selectable=to_selectable,
- source_selectable=source_selectable)
- for k in kwargs:
- crit = getattr(self.property.mapper.class_, k) == kwargs[k]
- if criterion is None:
- criterion = crit
- else:
- criterion = criterion & crit
- # annotate the *local* side of the join condition, in the case
- # of pj + sj this is the full primaryjoin, in the case of just
- # pj its the local side of the primaryjoin.
- if sj is not None:
- j = _orm_annotate(pj) & sj
- else:
- j = _orm_annotate(pj, exclude=self.property.remote_side)
- if criterion is not None and target_adapter and not \
- is_aliased_class:
- # limit this adapter to annotated only?
- criterion = target_adapter.traverse(criterion)
- # only have the "joined left side" of what we
- # return be subject to Query adaption. The right
- # side of it is used for an exists() subquery and
- # should not correlate or otherwise reach out
- # to anything in the enclosing query.
- if criterion is not None:
- criterion = criterion._annotate(
- {'no_replacement_traverse': True})
- crit = j & sql.True_._ifnone(criterion)
- ex = sql.exists([1], crit, from_obj=dest).correlate_except(dest)
- if secondary is not None:
- ex = ex.correlate_except(secondary)
- return ex
- def any(self, criterion=None, **kwargs):
- """Produce an expression that tests a collection against
- particular criterion, using EXISTS.
- An expression like::
- session.query(MyClass).filter(
- MyClass.somereference.any(SomeRelated.x==2)
- )
- Will produce a query like::
- SELECT * FROM my_table WHERE
- EXISTS (SELECT 1 FROM related WHERE related.my_id=my_table.id
- AND related.x=2)
- Because :meth:`~.RelationshipProperty.Comparator.any` uses
- a correlated subquery, its performance is not nearly as
- good when compared against large target tables as that of
- using a join.
- :meth:`~.RelationshipProperty.Comparator.any` is particularly
- useful for testing for empty collections::
- session.query(MyClass).filter(
- ~MyClass.somereference.any()
- )
- will produce::
- SELECT * FROM my_table WHERE
- NOT EXISTS (SELECT 1 FROM related WHERE
- related.my_id=my_table.id)
- :meth:`~.RelationshipProperty.Comparator.any` is only
- valid for collections, i.e. a :func:`.relationship`
- that has ``uselist=True``. For scalar references,
- use :meth:`~.RelationshipProperty.Comparator.has`.
- """
- if not self.property.uselist:
- raise sa_exc.InvalidRequestError(
- "'any()' not implemented for scalar "
- "attributes. Use has()."
- )
- return self._criterion_exists(criterion, **kwargs)
- def has(self, criterion=None, **kwargs):
- """Produce an expression that tests a scalar reference against
- particular criterion, using EXISTS.
- An expression like::
- session.query(MyClass).filter(
- MyClass.somereference.has(SomeRelated.x==2)
- )
- Will produce a query like::
- SELECT * FROM my_table WHERE
- EXISTS (SELECT 1 FROM related WHERE
- related.id==my_table.related_id AND related.x=2)
- Because :meth:`~.RelationshipProperty.Comparator.has` uses
- a correlated subquery, its performance is not nearly as
- good when compared against large target tables as that of
- using a join.
- :meth:`~.RelationshipProperty.Comparator.has` is only
- valid for scalar references, i.e. a :func:`.relationship`
- that has ``uselist=False``. For collection references,
- use :meth:`~.RelationshipProperty.Comparator.any`.
- """
- if self.property.uselist:
- raise sa_exc.InvalidRequestError(
- "'has()' not implemented for collections. "
- "Use any().")
- return self._criterion_exists(criterion, **kwargs)
- def contains(self, other, **kwargs):
- """Return a simple expression that tests a collection for
- containment of a particular item.
- :meth:`~.RelationshipProperty.Comparator.contains` is
- only valid for a collection, i.e. a
- :func:`~.orm.relationship` that implements
- one-to-many or many-to-many with ``uselist=True``.
- When used in a simple one-to-many context, an
- expression like::
- MyClass.contains(other)
- Produces a clause like::
- mytable.id == <some id>
- Where ``<some id>`` is the value of the foreign key
- attribute on ``other`` which refers to the primary
- key of its parent object. From this it follows that
- :meth:`~.RelationshipProperty.Comparator.contains` is
- very useful when used with simple one-to-many
- operations.
- For many-to-many operations, the behavior of
- :meth:`~.RelationshipProperty.Comparator.contains`
- has more caveats. The association table will be
- rendered in the statement, producing an "implicit"
- join, that is, includes multiple tables in the FROM
- clause which are equated in the WHERE clause::
- query(MyClass).filter(MyClass.contains(other))
- Produces a query like::
- SELECT * FROM my_table, my_association_table AS
- my_association_table_1 WHERE
- my_table.id = my_association_table_1.parent_id
- AND my_association_table_1.child_id = <some id>
- Where ``<some id>`` would be the primary key of
- ``other``. From the above, it is clear that
- :meth:`~.RelationshipProperty.Comparator.contains`
- will **not** work with many-to-many collections when
- used in queries that move beyond simple AND
- conjunctions, such as multiple
- :meth:`~.RelationshipProperty.Comparator.contains`
- expressions joined by OR. In such cases subqueries or
- explicit "outer joins" will need to be used instead.
- See :meth:`~.RelationshipProperty.Comparator.any` for
- a less-performant alternative using EXISTS, or refer
- to :meth:`.Query.outerjoin` as well as :ref:`ormtutorial_joins`
- for more details on constructing outer joins.
- """
- if not self.property.uselist:
- raise sa_exc.InvalidRequestError(
- "'contains' not implemented for scalar "
- "attributes. Use ==")
- clause = self.property._optimized_compare(
- other, adapt_source=self.adapter)
- if self.property.secondaryjoin is not None:
- clause.negation_clause = \
- self.__negated_contains_or_equals(other)
- return clause
- def __negated_contains_or_equals(self, other):
- if self.property.direction == MANYTOONE:
- state = attributes.instance_state(other)
- def state_bindparam(x, state, col):
- dict_ = state.dict
- return sql.bindparam(
- x, unique=True,
- callable_=self.property._get_attr_w_warn_on_none(
- col,
- self.property.mapper._get_state_attr_by_column,
- state, dict_, col, passive=attributes.PASSIVE_OFF
- )
- )
- def adapt(col):
- if self.adapter:
- return self.adapter(col)
- else:
- return col
- if self.property._use_get:
- return sql.and_(*[
- sql.or_(
- adapt(x) != state_bindparam(adapt(x), state, y),
- adapt(x) == None)
- for (x, y) in self.property.local_remote_pairs])
- criterion = sql.and_(*[
- x == y for (x, y) in
- zip(
- self.property.mapper.primary_key,
- self.property.mapper.primary_key_from_instance(other)
- )
- ])
- return ~self._criterion_exists(criterion)
- def __ne__(self, other):
- """Implement the ``!=`` operator.
- In a many-to-one context, such as::
- MyClass.some_prop != <some object>
- This will typically produce a clause such as::
- mytable.related_id != <some id>
- Where ``<some id>`` is the primary key of the
- given object.
- The ``!=`` operator provides partial functionality for non-
- many-to-one comparisons:
- * Comparisons against collections are not supported.
- Use
- :meth:`~.RelationshipProperty.Comparator.contains`
- in conjunction with :func:`~.expression.not_`.
- * Compared to a scalar one-to-many, will produce a
- clause that compares the target columns in the parent to
- the given target.
- * Compared to a scalar many-to-many, an alias
- of the association table will be rendered as
- well, forming a natural join that is part of the
- main body of the query. This will not work for
- queries that go beyond simple AND conjunctions of
- comparisons, such as those which use OR. Use
- explicit joins, outerjoins, or
- :meth:`~.RelationshipProperty.Comparator.has` in
- conjunction with :func:`~.expression.not_` for
- more comprehensive non-many-to-one scalar
- membership tests.
- * Comparisons against ``None`` given in a one-to-many
- or many-to-many context produce an EXISTS clause.
- """
- if isinstance(other, (util.NoneType, expression.Null)):
- if self.property.direction == MANYTOONE:
- return _orm_annotate(~self.property._optimized_compare(
- None, adapt_source=self.adapter))
- else:
- return self._criterion_exists()
- elif self.property.uselist:
- raise sa_exc.InvalidRequestError(
- "Can't compare a collection"
- " to an object or collection; use "
- "contains() to test for membership.")
- else:
- return _orm_annotate(self.__negated_contains_or_equals(other))
- @util.memoized_property
- def property(self):
- if mapperlib.Mapper._new_mappers:
- mapperlib.Mapper._configure_all()
- return self.prop
- def _with_parent(self, instance, alias_secondary=True):
- assert instance is not None
- return self._optimized_compare(
- instance, value_is_parent=True, alias_secondary=alias_secondary)
- def _optimized_compare(self, state, value_is_parent=False,
- adapt_source=None,
- alias_secondary=True):
- if state is not None:
- state = attributes.instance_state(state)
- reverse_direction = not value_is_parent
- if state is None:
- return self._lazy_none_clause(
- reverse_direction,
- adapt_source=adapt_source)
- if not reverse_direction:
- criterion, bind_to_col = \
- self._lazy_strategy._lazywhere, \
- self._lazy_strategy._bind_to_col
- else:
- criterion, bind_to_col = \
- self._lazy_strategy._rev_lazywhere, \
- self._lazy_strategy._rev_bind_to_col
- if reverse_direction:
- mapper = self.mapper
- else:
- mapper = self.parent
- dict_ = attributes.instance_dict(state.obj())
- def visit_bindparam(bindparam):
- if bindparam._identifying_key in bind_to_col:
- bindparam.callable = self._get_attr_w_warn_on_none(
- bind_to_col[bindparam._identifying_key],
- mapper._get_state_attr_by_column,
- state, dict_,
- bind_to_col[bindparam._identifying_key],
- passive=attributes.PASSIVE_OFF)
- if self.secondary is not None and alias_secondary:
- criterion = ClauseAdapter(
- self.secondary.alias()).\
- traverse(criterion)
- criterion = visitors.cloned_traverse(
- criterion, {}, {'bindparam': visit_bindparam})
- if adapt_source:
- criterion = adapt_source(criterion)
- return criterion
- def _get_attr_w_warn_on_none(self, column, fn, *arg, **kw):
- def _go():
- value = fn(*arg, **kw)
- if value is None:
- util.warn(
- "Got None for value of column %s; this is unsupported "
- "for a relationship comparison and will not "
- "currently produce an IS comparison "
- "(but may in a future release)" % column)
- return value
- return _go
- def _lazy_none_clause(self, reverse_direction=False, adapt_source=None):
- if not reverse_direction:
- criterion, bind_to_col = \
- self._lazy_strategy._lazywhere, \
- self._lazy_strategy._bind_to_col
- else:
- criterion, bind_to_col = \
- self._lazy_strategy._rev_lazywhere, \
- self._lazy_strategy._rev_bind_to_col
- criterion = adapt_criterion_to_null(criterion, bind_to_col)
- if adapt_source:
- criterion = adapt_source(criterion)
- return criterion
- def __str__(self):
- return str(self.parent.class_.__name__) + "." + self.key
- def merge(self,
- session,
- source_state,
- source_dict,
- dest_state,
- dest_dict,
- load, _recursive, _resolve_conflict_map):
- if load:
- for r in self._reverse_property:
- if (source_state, r) in _recursive:
- return
- if "merge" not in self._cascade:
- return
- if self.key not in source_dict:
- return
- if self.uselist:
- instances = source_state.get_impl(self.key).\
- get(source_state, source_dict)
- if hasattr(instances, '_sa_adapter'):
- # convert collections to adapters to get a true iterator
- instances = instances._sa_adapter
- if load:
- # for a full merge, pre-load the destination collection,
- # so that individual _merge of each item pulls from identity
- # map for those already present.
- # also assumes CollectionAttrbiuteImpl behavior of loading
- # "old" list in any case
- dest_state.get_impl(self.key).get(dest_state, dest_dict)
- dest_list = []
- for current in instances:
- current_state = attributes.instance_state(current)
- current_dict = attributes.instance_dict(current)
- _recursive[(current_state, self)] = True
- obj = session._merge(
- current_state, current_dict,
- load=load, _recursive=_recursive,
- _resolve_conflict_map=_resolve_conflict_map)
- if obj is not None:
- dest_list.append(obj)
- if not load:
- coll = attributes.init_state_collection(dest_state,
- dest_dict, self.key)
- for c in dest_list:
- coll.append_without_event(c)
- else:
- dest_state.get_impl(self.key).set(
- dest_state, dest_dict, dest_list,
- _adapt=False)
- else:
- current = source_dict[self.key]
- if current is not None:
- current_state = attributes.instance_state(current)
- current_dict = attributes.instance_dict(current)
- _recursive[(current_state, self)] = True
- obj = session._merge(
- current_state, current_dict,
- load=load, _recursive=_recursive,
- _resolve_conflict_map=_resolve_conflict_map)
- else:
- obj = None
- if not load:
- dest_dict[self.key] = obj
- else:
- dest_state.get_impl(self.key).set(dest_state,
- dest_dict, obj, None)
- def _value_as_iterable(self, state, dict_, key,
- passive=attributes.PASSIVE_OFF):
- """Return a list of tuples (state, obj) for the given
- key.
- returns an empty list if the value is None/empty/PASSIVE_NO_RESULT
- """
- impl = state.manager[key].impl
- x = impl.get(state, dict_, passive=passive)
- if x is attributes.PASSIVE_NO_RESULT or x is None:
- return []
- elif hasattr(impl, 'get_collection'):
- return [
- (attributes.instance_state(o), o) for o in
- impl.get_collection(state, dict_, x, passive=passive)
- ]
- else:
- return [(attributes.instance_state(x), x)]
- def cascade_iterator(self, type_, state, dict_,
- visited_states, halt_on=None):
- # assert type_ in self._cascade
- # only actively lazy load on the 'delete' cascade
- if type_ != 'delete' or self.passive_deletes:
- passive = attributes.PASSIVE_NO_INITIALIZE
- else:
- passive = attributes.PASSIVE_OFF
- if type_ == 'save-update':
- tuples = state.manager[self.key].impl.\
- get_all_pending(state, dict_)
- else:
- tuples = self._value_as_iterable(state, dict_, self.key,
- passive=passive)
- skip_pending = type_ == 'refresh-expire' and 'delete-orphan' \
- not in self._cascade
- for instance_state, c in tuples:
- if instance_state in visited_states:
- continue
- if c is None:
- # would like to emit a warning here, but
- # would not be consistent with collection.append(None)
- # current behavior of silently skipping.
- # see [ticket:2229]
- continue
- instance_dict = attributes.instance_dict(c)
- if halt_on and halt_on(instance_state):
- continue
- if skip_pending and not instance_state.key:
- continue
- instance_mapper = instance_state.manager.mapper
- if not instance_mapper.isa(self.mapper.class_manager.mapper):
- raise AssertionError("Attribute '%s' on class '%s' "
- "doesn't handle objects "
- "of type '%s'" % (
- self.key,
- self.parent.class_,
- c.__class__
- ))
- visited_states.add(instance_state)
- yield c, instance_mapper, instance_state, instance_dict
- def _add_reverse_property(self, key):
- other = self.mapper.get_property(key, _configure_mappers=False)
- self._reverse_property.add(other)
- other._reverse_property.add(self)
- if not other.mapper.common_parent(self.parent):
- raise sa_exc.ArgumentError(
- 'reverse_property %r on '
- 'relationship %s references relationship %s, which '
- 'does not reference mapper %s' %
- (key, self, other, self.parent))
- if self.direction in (ONETOMANY, MANYTOONE) and self.direction \
- == other.direction:
- raise sa_exc.ArgumentError(
- '%s and back-reference %s are '
- 'both of the same direction %r. Did you mean to '
- 'set remote_side on the many-to-one side ?' %
- (other, self, self.direction))
- @util.memoized_property
- def mapper(self):
- """Return the targeted :class:`.Mapper` for this
- :class:`.RelationshipProperty`.
- This is a lazy-initializing static attribute.
- """
- if util.callable(self.argument) and \
- not isinstance(self.argument, (type, mapperlib.Mapper)):
- argument = self.argument()
- else:
- argument = self.argument
- if isinstance(argument, type):
- mapper_ = mapperlib.class_mapper(argument,
- configure=False)
- elif isinstance(self.argument, mapperlib.Mapper):
- mapper_ = argument
- else:
- raise sa_exc.ArgumentError(
- "relationship '%s' expects "
- "a class or a mapper argument (received: %s)"
- % (self.key, type(argument)))
- return mapper_
- @util.memoized_property
- @util.deprecated("0.7", "Use .target")
- def table(self):
- """Return the selectable linked to this
- :class:`.RelationshipProperty` object's target
- :class:`.Mapper`.
- """
- return self.target
- def do_init(self):
- self._check_conflicts()
- self._process_dependent_arguments()
- self._setup_join_conditions()
- self._check_cascade_settings(self._cascade)
- self._post_init()
- self._generate_backref()
- self._join_condition._warn_for_conflicting_sync_targets()
- super(RelationshipProperty, self).do_init()
- self._lazy_strategy = self._get_strategy((("lazy", "select"),))
- def _process_dependent_arguments(self):
- """Convert incoming configuration arguments to their
- proper form.
- Callables are resolved, ORM annotations removed.
- """
- # accept callables for other attributes which may require
- # deferred initialization. This technique is used
- # by declarative "string configs" and some recipes.
- for attr in (
- 'order_by', 'primaryjoin', 'secondaryjoin',
- 'secondary', '_user_defined_foreign_keys', 'remote_side',
- ):
- attr_value = getattr(self, attr)
- if util.callable(attr_value):
- setattr(self, attr, attr_value())
- # remove "annotations" which are present if mapped class
- # descriptors are used to create the join expression.
- for attr in 'primaryjoin', 'secondaryjoin':
- val = getattr(self, attr)
- if val is not None:
- setattr(self, attr, _orm_deannotate(
- expression._only_column_elements(val, attr))
- )
- # ensure expressions in self.order_by, foreign_keys,
- # remote_side are all columns, not strings.
- if self.order_by is not False and self.order_by is not None:
- self.order_by = [
- expression._only_column_elements(x, "order_by")
- for x in
- util.to_list(self.order_by)]
- self._user_defined_foreign_keys = \
- util.column_set(
- expression._only_column_elements(x, "foreign_keys")
- for x in util.to_column_set(
- self._user_defined_foreign_keys
- ))
- self.remote_side = \
- util.column_set(
- expression._only_column_elements(x, "remote_side")
- for x in
- util.to_column_set(self.remote_side))
- self.target = self.mapper.mapped_table
- def _setup_join_conditions(self):
- self._join_condition = jc = JoinCondition(
- parent_selectable=self.parent.mapped_table,
- child_selectable=self.mapper.mapped_table,
- parent_local_selectable=self.parent.local_table,
- child_local_selectable=self.mapper.local_table,
- primaryjoin=self.primaryjoin,
- secondary=self.secondary,
- secondaryjoin=self.secondaryjoin,
- parent_equivalents=self.parent._equivalent_columns,
- child_equivalents=self.mapper._equivalent_columns,
- consider_as_foreign_keys=self._user_defined_foreign_keys,
- local_remote_pairs=self.local_remote_pairs,
- remote_side=self.remote_side,
- self_referential=self._is_self_referential,
- prop=self,
- support_sync=not self.viewonly,
- can_be_synced_fn=self._columns_are_mapped
- )
- self.primaryjoin = jc.deannotated_primaryjoin
- self.secondaryjoin = jc.deannotated_secondaryjoin
- self.direction = jc.direction
- self.local_remote_pairs = jc.local_remote_pairs
- self.remote_side = jc.remote_columns
- self.local_columns = jc.local_columns
- self.synchronize_pairs = jc.synchronize_pairs
- self._calculated_foreign_keys = jc.foreign_key_columns
- self.secondary_synchronize_pairs = jc.secondary_synchronize_pairs
- def _check_conflicts(self):
- """Test that this relationship is legal, warn about
- inheritance conflicts."""
- if self.parent.non_primary and not mapperlib.class_mapper(
- self.parent.class_,
- configure=False).has_property(self.key):
- raise sa_exc.ArgumentError(
- "Attempting to assign a new "
- "relationship '%s' to a non-primary mapper on "
- "class '%s'. New relationships can only be added "
- "to the primary mapper, i.e. the very first mapper "
- "created for class '%s' " %
- (self.key, self.parent.class_.__name__,
- self.parent.class_.__name__))
- def _get_cascade(self):
- """Return the current cascade setting for this
- :class:`.RelationshipProperty`.
- """
- return self._cascade
- def _set_cascade(self, cascade):
- cascade = CascadeOptions(cascade)
- if 'mapper' in self.__dict__:
- self._check_cascade_settings(cascade)
- self._cascade = cascade
- if self._dependency_processor:
- self._dependency_processor.cascade = cascade
- cascade = property(_get_cascade, _set_cascade)
- def _check_cascade_settings(self, cascade):
- if cascade.delete_orphan and not self.single_parent \
- and (self.direction is MANYTOMANY or self.direction
- is MANYTOONE):
- raise sa_exc.ArgumentError(
- 'On %s, delete-orphan cascade is not supported '
- 'on a many-to-many or many-to-one relationship '
- 'when single_parent is not set. Set '
- 'single_parent=True on the relationship().'
- % self)
- if self.direction is MANYTOONE and self.passive_deletes:
- util.warn("On %s, 'passive_deletes' is normally configured "
- "on one-to-many, one-to-one, many-to-many "
- "relationships only."
- % self)
- if self.passive_deletes == 'all' and \
- ("delete" in cascade or
- "delete-orphan" in cascade):
- raise sa_exc.ArgumentError(
- "On %s, can't set passive_deletes='all' in conjunction "
- "with 'delete' or 'delete-orphan' cascade" % self)
- if cascade.delete_orphan:
- self.mapper.primary_mapper()._delete_orphans.append(
- (self.key, self.parent.class_)
- )
- def _columns_are_mapped(self, *cols):
- """Return True if all columns in the given collection are
- mapped by the tables referenced by this :class:`.Relationship`.
- """
- for c in cols:
- if self.secondary is not None \
- and self.secondary.c.contains_column(c):
- continue
- if not self.parent.mapped_table.c.contains_column(c) and \
- not self.target.c.contains_column(c):
- return False
- return True
- def _generate_backref(self):
- """Interpret the 'backref' instruction to create a
- :func:`.relationship` complementary to this one."""
- if self.parent.non_primary:
- return
- if self.backref is not None and not self.back_populates:
- if isinstance(self.backref, util.string_types):
- backref_key, kwargs = self.backref, {}
- else:
- backref_key, kwargs = self.backref
- mapper = self.mapper.primary_mapper()
- if not mapper.concrete:
- check = set(mapper.iterate_to_root()).\
- union(mapper.self_and_descendants)
- for m in check:
- if m.has_property(backref_key) and not m.concrete:
- raise sa_exc.ArgumentError(
- "Error creating backref "
- "'%s' on relationship '%s': property of that "
- "name exists on mapper '%s'" %
- (backref_key, self, m))
- # determine primaryjoin/secondaryjoin for the
- # backref. Use the one we had, so that
- # a custom join doesn't have to be specified in
- # both directions.
- if self.secondary is not None:
- # for many to many, just switch primaryjoin/
- # secondaryjoin. use the annotated
- # pj/sj on the _join_condition.
- pj = kwargs.pop(
- 'primaryjoin',
- self._join_condition.secondaryjoin_minus_local)
- sj = kwargs.pop(
- 'secondaryjoin',
- self._join_condition.primaryjoin_minus_local)
- else:
- pj = kwargs.pop(
- 'primaryjoin',
- self._join_condition.primaryjoin_reverse_remote)
- sj = kwargs.pop('secondaryjoin', None)
- if sj:
- raise sa_exc.InvalidRequestError(
- "Can't assign 'secondaryjoin' on a backref "
- "against a non-secondary relationship."
- )
- foreign_keys = kwargs.pop('foreign_keys',
- self._user_defined_foreign_keys)
- parent = self.parent.primary_mapper()
- kwargs.setdefault('viewonly', self.viewonly)
- kwargs.setdefault('post_update', self.post_update)
- kwargs.setdefault('passive_updates', self.passive_updates)
- self.back_populates = backref_key
- relationship = RelationshipProperty(
- parent, self.secondary,
- pj, sj,
- foreign_keys=foreign_keys,
- back_populates=self.key,
- **kwargs)
- mapper._configure_property(backref_key, relationship)
- if self.back_populates:
- self._add_reverse_property(self.back_populates)
- def _post_init(self):
- if self.uselist is None:
- self.uselist = self.direction is not MANYTOONE
- if not self.viewonly:
- self._dependency_processor = \
- dependency.DependencyProcessor.from_relationship(self)
- @util.memoized_property
- def _use_get(self):
- """memoize the 'use_get' attribute of this RelationshipLoader's
- lazyloader."""
- strategy = self._lazy_strategy
- return strategy.use_get
- @util.memoized_property
- def _is_self_referential(self):
- return self.mapper.common_parent(self.parent)
- def _create_joins(self, source_polymorphic=False,
- source_selectable=None, dest_polymorphic=False,
- dest_selectable=None, of_type=None):
- if source_selectable is None:
- if source_polymorphic and self.parent.with_polymorphic:
- source_selectable = self.parent._with_polymorphic_selectable
- aliased = False
- if dest_selectable is None:
- if dest_polymorphic and self.mapper.with_polymorphic:
- dest_selectable = self.mapper._with_polymorphic_selectable
- aliased = True
- else:
- dest_selectable = self.mapper.mapped_table
- if self._is_self_referential and source_selectable is None:
- dest_selectable = dest_selectable.alias()
- aliased = True
- else:
- aliased = True
- dest_mapper = of_type or self.mapper
- single_crit = dest_mapper._single_table_criterion
- aliased = aliased or (source_selectable is not None)
- primaryjoin, secondaryjoin, secondary, target_adapter, dest_selectable = \
- self._join_condition.join_targets(
- source_selectable, dest_selectable, aliased, single_crit
- )
- if source_selectable is None:
- source_selectable = self.parent.local_table
- if dest_selectable is None:
- dest_selectable = self.mapper.local_table
- return (primaryjoin, secondaryjoin, source_selectable,
- dest_selectable, secondary, target_adapter)
- def _annotate_columns(element, annotations):
- def clone(elem):
- if isinstance(elem, expression.ColumnClause):
- elem = elem._annotate(annotations.copy())
- elem._copy_internals(clone=clone)
- return elem
- if element is not None:
- element = clone(element)
- return element
- class JoinCondition(object):
- def __init__(self,
- parent_selectable,
- child_selectable,
- parent_local_selectable,
- child_local_selectable,
- primaryjoin=None,
- secondary=None,
- secondaryjoin=None,
- parent_equivalents=None,
- child_equivalents=None,
- consider_as_foreign_keys=None,
- local_remote_pairs=None,
- remote_side=None,
- self_referential=False,
- prop=None,
- support_sync=True,
- can_be_synced_fn=lambda *c: True
- ):
- self.parent_selectable = parent_selectable
- self.parent_local_selectable = parent_local_selectable
- self.child_selectable = child_selectable
- self.child_local_selectable = child_local_selectable
- self.parent_equivalents = parent_equivalents
- self.child_equivalents = child_equivalents
- self.primaryjoin = primaryjoin
- self.secondaryjoin = secondaryjoin
- self.secondary = secondary
- self.consider_as_foreign_keys = consider_as_foreign_keys
- self._local_remote_pairs = local_remote_pairs
- self._remote_side = remote_side
- self.prop = prop
- self.self_referential = self_referential
- self.support_sync = support_sync
- self.can_be_synced_fn = can_be_synced_fn
- self._determine_joins()
- self._annotate_fks()
- self._annotate_remote()
- self._annotate_local()
- self._setup_pairs()
- self._check_foreign_cols(self.primaryjoin, True)
- if self.secondaryjoin is not None:
- self._check_foreign_cols(self.secondaryjoin, False)
- self._determine_direction()
- self._check_remote_side()
- self._log_joins()
- def _log_joins(self):
- if self.prop is None:
- return
- log = self.prop.logger
- log.info('%s setup primary join %s', self.prop,
- self.primaryjoin)
- log.info('%s setup secondary join %s', self.prop,
- self.secondaryjoin)
- log.info('%s synchronize pairs [%s]', self.prop,
- ','.join('(%s => %s)' % (l, r) for (l, r) in
- self.synchronize_pairs))
- log.info('%s secondary synchronize pairs [%s]', self.prop,
- ','.join('(%s => %s)' % (l, r) for (l, r) in
- self.secondary_synchronize_pairs or []))
- log.info('%s local/remote pairs [%s]', self.prop,
- ','.join('(%s / %s)' % (l, r) for (l, r) in
- self.local_remote_pairs))
- log.info('%s remote columns [%s]', self.prop,
- ','.join('%s' % col for col in self.remote_columns)
- )
- log.info('%s local columns [%s]', self.prop,
- ','.join('%s' % col for col in self.local_columns)
- )
- log.info('%s relationship direction %s', self.prop,
- self.direction)
- def _determine_joins(self):
- """Determine the 'primaryjoin' and 'secondaryjoin' attributes,
- if not passed to the constructor already.
- This is based on analysis of the foreign key relationships
- between the parent and target mapped selectables.
- """
- if self.secondaryjoin is not None and self.secondary is None:
- raise sa_exc.ArgumentError(
- "Property %s specified with secondary "
- "join condition but "
- "no secondary argument" % self.prop)
- # find a join between the given mapper's mapped table and
- # the given table. will try the mapper's local table first
- # for more specificity, then if not found will try the more
- # general mapped table, which in the case of inheritance is
- # a join.
- try:
- consider_as_foreign_keys = self.consider_as_foreign_keys or None
- if self.secondary is not None:
- if self.secondaryjoin is None:
- self.secondaryjoin = \
- join_condition(
- self.child_selectable,
- self.secondary,
- a_subset=self.child_local_selectable,
- consider_as_foreign_keys=consider_as_foreign_keys
- )
- if self.primaryjoin is None:
- self.primaryjoin = \
- join_condition(
- self.parent_selectable,
- self.secondary,
- a_subset=self.parent_local_selectable,
- consider_as_foreign_keys=consider_as_foreign_keys
- )
- else:
- if self.primaryjoin is None:
- self.primaryjoin = \
- join_condition(
- self.parent_selectable,
- self.child_selectable,
- a_subset=self.parent_local_selectable,
- consider_as_foreign_keys=consider_as_foreign_keys
- )
- except sa_exc.NoForeignKeysError:
- if self.secondary is not None:
- raise sa_exc.NoForeignKeysError(
- "Could not determine join "
- "condition between parent/child tables on "
- "relationship %s - there are no foreign keys "
- "linking these tables via secondary table '%s'. "
- "Ensure that referencing columns are associated "
- "with a ForeignKey or ForeignKeyConstraint, or "
- "specify 'primaryjoin' and 'secondaryjoin' "
- "expressions." % (self.prop, self.secondary))
- else:
- raise sa_exc.NoForeignKeysError(
- "Could not determine join "
- "condition between parent/child tables on "
- "relationship %s - there are no foreign keys "
- "linking these tables. "
- "Ensure that referencing columns are associated "
- "with a ForeignKey or ForeignKeyConstraint, or "
- "specify a 'primaryjoin' expression." % self.prop)
- except sa_exc.AmbiguousForeignKeysError:
- if self.secondary is not None:
- raise sa_exc.AmbiguousForeignKeysError(
- "Could not determine join "
- "condition between parent/child tables on "
- "relationship %s - there are multiple foreign key "
- "paths linking the tables via secondary table '%s'. "
- "Specify the 'foreign_keys' "
- "argument, providing a list of those columns which "
- "should be counted as containing a foreign key "
- "reference from the secondary table to each of the "
- "parent and child tables."
- % (self.prop, self.secondary))
- else:
- raise sa_exc.AmbiguousForeignKeysError(
- "Could not determine join "
- "condition between parent/child tables on "
- "relationship %s - there are multiple foreign key "
- "paths linking the tables. Specify the "
- "'foreign_keys' argument, providing a list of those "
- "columns which should be counted as containing a "
- "foreign key reference to the parent table."
- % self.prop)
- @property
- def primaryjoin_minus_local(self):
- return _deep_deannotate(self.primaryjoin, values=("local", "remote"))
- @property
- def secondaryjoin_minus_local(self):
- return _deep_deannotate(self.secondaryjoin,
- values=("local", "remote"))
- @util.memoized_property
- def primaryjoin_reverse_remote(self):
- """Return the primaryjoin condition suitable for the
- "reverse" direction.
- If the primaryjoin was delivered here with pre-existing
- "remote" annotations, the local/remote annotations
- are reversed. Otherwise, the local/remote annotations
- are removed.
- """
- if self._has_remote_annotations:
- def replace(element):
- if "remote" in element._annotations:
- v = element._annotations.copy()
- del v['remote']
- v['local'] = True
- return element._with_annotations(v)
- elif "local" in element._annotations:
- v = element._annotations.copy()
- del v['local']
- v['remote'] = True
- return element._with_annotations(v)
- return visitors.replacement_traverse(
- self.primaryjoin, {}, replace)
- else:
- if self._has_foreign_annotations:
- # TODO: coverage
- return _deep_deannotate(self.primaryjoin,
- values=("local", "remote"))
- else:
- return _deep_deannotate(self.primaryjoin)
- def _has_annotation(self, clause, annotation):
- for col in visitors.iterate(clause, {}):
- if annotation in col._annotations:
- return True
- else:
- return False
- @util.memoized_property
- def _has_foreign_annotations(self):
- return self._has_annotation(self.primaryjoin, "foreign")
- @util.memoized_property
- def _has_remote_annotations(self):
- return self._has_annotation(self.primaryjoin, "remote")
- def _annotate_fks(self):
- """Annotate the primaryjoin and secondaryjoin
- structures with 'foreign' annotations marking columns
- considered as foreign.
- """
- if self._has_foreign_annotations:
- return
- if self.consider_as_foreign_keys:
- self._annotate_from_fk_list()
- else:
- self._annotate_present_fks()
- def _annotate_from_fk_list(self):
- def check_fk(col):
- if col in self.consider_as_foreign_keys:
- return col._annotate({"foreign": True})
- self.primaryjoin = visitors.replacement_traverse(
- self.primaryjoin,
- {},
- check_fk
- )
- if self.secondaryjoin is not None:
- self.secondaryjoin = visitors.replacement_traverse(
- self.secondaryjoin,
- {},
- check_fk
- )
- def _annotate_present_fks(self):
- if self.secondary is not None:
- secondarycols = util.column_set(self.secondary.c)
- else:
- secondarycols = set()
- def is_foreign(a, b):
- if isinstance(a, schema.Column) and \
- isinstance(b, schema.Column):
- if a.references(b):
- return a
- elif b.references(a):
- return b
- if secondarycols:
- if a in secondarycols and b not in secondarycols:
- return a
- elif b in secondarycols and a not in secondarycols:
- return b
- def visit_binary(binary):
- if not isinstance(binary.left, sql.ColumnElement) or \
- not isinstance(binary.right, sql.ColumnElement):
- return
- if "foreign" not in binary.left._annotations and \
- "foreign" not in binary.right._annotations:
- col = is_foreign(binary.left, binary.right)
- if col is not None:
- if col.compare(binary.left):
- binary.left = binary.left._annotate(
- {"foreign": True})
- elif col.compare(binary.right):
- binary.right = binary.right._annotate(
- {"foreign": True})
- self.primaryjoin = visitors.cloned_traverse(
- self.primaryjoin,
- {},
- {"binary": visit_binary}
- )
- if self.secondaryjoin is not None:
- self.secondaryjoin = visitors.cloned_traverse(
- self.secondaryjoin,
- {},
- {"binary": visit_binary}
- )
- def _refers_to_parent_table(self):
- """Return True if the join condition contains column
- comparisons where both columns are in both tables.
- """
- pt = self.parent_selectable
- mt = self.child_selectable
- result = [False]
- def visit_binary(binary):
- c, f = binary.left, binary.right
- if (
- isinstance(c, expression.ColumnClause) and
- isinstance(f, expression.ColumnClause) and
- pt.is_derived_from(c.table) and
- pt.is_derived_from(f.table) and
- mt.is_derived_from(c.table) and
- mt.is_derived_from(f.table)
- ):
- result[0] = True
- visitors.traverse(
- self.primaryjoin,
- {},
- {"binary": visit_binary}
- )
- return result[0]
- def _tables_overlap(self):
- """Return True if parent/child tables have some overlap."""
- return selectables_overlap(
- self.parent_selectable, self.child_selectable)
- def _annotate_remote(self):
- """Annotate the primaryjoin and secondaryjoin
- structures with 'remote' annotations marking columns
- considered as part of the 'remote' side.
- """
- if self._has_remote_annotations:
- return
- if self.secondary is not None:
- self._annotate_remote_secondary()
- elif self._local_remote_pairs or self._remote_side:
- self._annotate_remote_from_args()
- elif self._refers_to_parent_table():
- self._annotate_selfref(lambda col: "foreign" in col._annotations, False)
- elif self._tables_overlap():
- self._annotate_remote_with_overlap()
- else:
- self._annotate_remote_distinct_selectables()
- def _annotate_remote_secondary(self):
- """annotate 'remote' in primaryjoin, secondaryjoin
- when 'secondary' is present.
- """
- def repl(element):
- if self.secondary.c.contains_column(element):
- return element._annotate({"remote": True})
- self.primaryjoin = visitors.replacement_traverse(
- self.primaryjoin, {}, repl)
- self.secondaryjoin = visitors.replacement_traverse(
- self.secondaryjoin, {}, repl)
- def _annotate_selfref(self, fn, remote_side_given):
- """annotate 'remote' in primaryjoin, secondaryjoin
- when the relationship is detected as self-referential.
- """
- def visit_binary(binary):
- equated = binary.left.compare(binary.right)
- if isinstance(binary.left, expression.ColumnClause) and \
- isinstance(binary.right, expression.ColumnClause):
- # assume one to many - FKs are "remote"
- if fn(binary.left):
- binary.left = binary.left._annotate({"remote": True})
- if fn(binary.right) and not equated:
- binary.right = binary.right._annotate(
- {"remote": True})
- elif not remote_side_given:
- self._warn_non_column_elements()
- self.primaryjoin = visitors.cloned_traverse(
- self.primaryjoin, {},
- {"binary": visit_binary})
- def _annotate_remote_from_args(self):
- """annotate 'remote' in primaryjoin, secondaryjoin
- when the 'remote_side' or '_local_remote_pairs'
- arguments are used.
- """
- if self._local_remote_pairs:
- if self._remote_side:
- raise sa_exc.ArgumentError(
- "remote_side argument is redundant "
- "against more detailed _local_remote_side "
- "argument.")
- remote_side = [r for (l, r) in self._local_remote_pairs]
- else:
- remote_side = self._remote_side
- if self._refers_to_parent_table():
- self._annotate_selfref(lambda col: col in remote_side, True)
- else:
- def repl(element):
- if element in remote_side:
- return element._annotate({"remote": True})
- self.primaryjoin = visitors.replacement_traverse(
- self.primaryjoin, {}, repl)
- def _annotate_remote_with_overlap(self):
- """annotate 'remote' in primaryjoin, secondaryjoin
- when the parent/child tables have some set of
- tables in common, though is not a fully self-referential
- relationship.
- """
- def visit_binary(binary):
- binary.left, binary.right = proc_left_right(binary.left,
- binary.right)
- binary.right, binary.left = proc_left_right(binary.right,
- binary.left)
- check_entities = self.prop is not None and \
- self.prop.mapper is not self.prop.parent
- def proc_left_right(left, right):
- if isinstance(left, expression.ColumnClause) and \
- isinstance(right, expression.ColumnClause):
- if self.child_selectable.c.contains_column(right) and \
- self.parent_selectable.c.contains_column(left):
- right = right._annotate({"remote": True})
- elif check_entities and \
- right._annotations.get('parentmapper') is self.prop.mapper:
- right = right._annotate({"remote": True})
- elif check_entities and \
- left._annotations.get('parentmapper') is self.prop.mapper:
- left = left._annotate({"remote": True})
- else:
- self._warn_non_column_elements()
- return left, right
- self.primaryjoin = visitors.cloned_traverse(
- self.primaryjoin, {},
- {"binary": visit_binary})
- def _annotate_remote_distinct_selectables(self):
- """annotate 'remote' in primaryjoin, secondaryjoin
- when the parent/child tables are entirely
- separate.
- """
- def repl(element):
- if self.child_selectable.c.contains_column(element) and \
- (not self.parent_local_selectable.c.
- contains_column(element) or
- self.child_local_selectable.c.
- contains_column(element)):
- return element._annotate({"remote": True})
- self.primaryjoin = visitors.replacement_traverse(
- self.primaryjoin, {}, repl)
- def _warn_non_column_elements(self):
- util.warn(
- "Non-simple column elements in primary "
- "join condition for property %s - consider using "
- "remote() annotations to mark the remote side."
- % self.prop
- )
- def _annotate_local(self):
- """Annotate the primaryjoin and secondaryjoin
- structures with 'local' annotations.
- This annotates all column elements found
- simultaneously in the parent table
- and the join condition that don't have a
- 'remote' annotation set up from
- _annotate_remote() or user-defined.
- """
- if self._has_annotation(self.primaryjoin, "local"):
- return
- if self._local_remote_pairs:
- local_side = util.column_set([l for (l, r)
- in self._local_remote_pairs])
- else:
- local_side = util.column_set(self.parent_selectable.c)
- def locals_(elem):
- if "remote" not in elem._annotations and \
- elem in local_side:
- return elem._annotate({"local": True})
- self.primaryjoin = visitors.replacement_traverse(
- self.primaryjoin, {}, locals_
- )
- def _check_remote_side(self):
- if not self.local_remote_pairs:
- raise sa_exc.ArgumentError(
- 'Relationship %s could '
- 'not determine any unambiguous local/remote column '
- 'pairs based on join condition and remote_side '
- 'arguments. '
- 'Consider using the remote() annotation to '
- 'accurately mark those elements of the join '
- 'condition that are on the remote side of '
- 'the relationship.' % (self.prop, ))
- def _check_foreign_cols(self, join_condition, primary):
- """Check the foreign key columns collected and emit error
- messages."""
- can_sync = False
- foreign_cols = self._gather_columns_with_annotation(
- join_condition, "foreign")
- has_foreign = bool(foreign_cols)
- if primary:
- can_sync = bool(self.synchronize_pairs)
- else:
- can_sync = bool(self.secondary_synchronize_pairs)
- if self.support_sync and can_sync or \
- (not self.support_sync and has_foreign):
- return
- # from here below is just determining the best error message
- # to report. Check for a join condition using any operator
- # (not just ==), perhaps they need to turn on "viewonly=True".
- if self.support_sync and has_foreign and not can_sync:
- err = "Could not locate any simple equality expressions "\
- "involving locally mapped foreign key columns for "\
- "%s join condition "\
- "'%s' on relationship %s." % (
- primary and 'primary' or 'secondary',
- join_condition,
- self.prop
- )
- err += \
- " Ensure that referencing columns are associated "\
- "with a ForeignKey or ForeignKeyConstraint, or are "\
- "annotated in the join condition with the foreign() "\
- "annotation. To allow comparison operators other than "\
- "'==', the relationship can be marked as viewonly=True."
- raise sa_exc.ArgumentError(err)
- else:
- err = "Could not locate any relevant foreign key columns "\
- "for %s join condition '%s' on relationship %s." % (
- primary and 'primary' or 'secondary',
- join_condition,
- self.prop
- )
- err += \
- ' Ensure that referencing columns are associated '\
- 'with a ForeignKey or ForeignKeyConstraint, or are '\
- 'annotated in the join condition with the foreign() '\
- 'annotation.'
- raise sa_exc.ArgumentError(err)
- def _determine_direction(self):
- """Determine if this relationship is one to many, many to one,
- many to many.
- """
- if self.secondaryjoin is not None:
- self.direction = MANYTOMANY
- else:
- parentcols = util.column_set(self.parent_selectable.c)
- targetcols = util.column_set(self.child_selectable.c)
- # fk collection which suggests ONETOMANY.
- onetomany_fk = targetcols.intersection(
- self.foreign_key_columns)
- # fk collection which suggests MANYTOONE.
- manytoone_fk = parentcols.intersection(
- self.foreign_key_columns)
- if onetomany_fk and manytoone_fk:
- # fks on both sides. test for overlap of local/remote
- # with foreign key.
- # we will gather columns directly from their annotations
- # without deannotating, so that we can distinguish on a column
- # that refers to itself.
- # 1. columns that are both remote and FK suggest
- # onetomany.
- onetomany_local = self._gather_columns_with_annotation(
- self.primaryjoin, "remote", "foreign")
- # 2. columns that are FK but are not remote (e.g. local)
- # suggest manytoone.
- manytoone_local = set([c for c in
- self._gather_columns_with_annotation(
- self.primaryjoin,
- "foreign")
- if "remote" not in c._annotations])
- # 3. if both collections are present, remove columns that
- # refer to themselves. This is for the case of
- # and_(Me.id == Me.remote_id, Me.version == Me.version)
- if onetomany_local and manytoone_local:
- self_equated = self.remote_columns.intersection(
- self.local_columns
- )
- onetomany_local = onetomany_local.difference(self_equated)
- manytoone_local = manytoone_local.difference(self_equated)
- # at this point, if only one or the other collection is
- # present, we know the direction, otherwise it's still
- # ambiguous.
- if onetomany_local and not manytoone_local:
- self.direction = ONETOMANY
- elif manytoone_local and not onetomany_local:
- self.direction = MANYTOONE
- else:
- raise sa_exc.ArgumentError(
- "Can't determine relationship"
- " direction for relationship '%s' - foreign "
- "key columns within the join condition are present "
- "in both the parent and the child's mapped tables. "
- "Ensure that only those columns referring "
- "to a parent column are marked as foreign, "
- "either via the foreign() annotation or "
- "via the foreign_keys argument." % self.prop)
- elif onetomany_fk:
- self.direction = ONETOMANY
- elif manytoone_fk:
- self.direction = MANYTOONE
- else:
- raise sa_exc.ArgumentError(
- "Can't determine relationship "
- "direction for relationship '%s' - foreign "
- "key columns are present in neither the parent "
- "nor the child's mapped tables" % self.prop)
- def _deannotate_pairs(self, collection):
- """provide deannotation for the various lists of
- pairs, so that using them in hashes doesn't incur
- high-overhead __eq__() comparisons against
- original columns mapped.
- """
- return [(x._deannotate(), y._deannotate())
- for x, y in collection]
- def _setup_pairs(self):
- sync_pairs = []
- lrp = util.OrderedSet([])
- secondary_sync_pairs = []
- def go(joincond, collection):
- def visit_binary(binary, left, right):
- if "remote" in right._annotations and \
- "remote" not in left._annotations and \
- self.can_be_synced_fn(left):
- lrp.add((left, right))
- elif "remote" in left._annotations and \
- "remote" not in right._annotations and \
- self.can_be_synced_fn(right):
- lrp.add((right, left))
- if binary.operator is operators.eq and \
- self.can_be_synced_fn(left, right):
- if "foreign" in right._annotations:
- collection.append((left, right))
- elif "foreign" in left._annotations:
- collection.append((right, left))
- visit_binary_product(visit_binary, joincond)
- for joincond, collection in [
- (self.primaryjoin, sync_pairs),
- (self.secondaryjoin, secondary_sync_pairs)
- ]:
- if joincond is None:
- continue
- go(joincond, collection)
- self.local_remote_pairs = self._deannotate_pairs(lrp)
- self.synchronize_pairs = self._deannotate_pairs(sync_pairs)
- self.secondary_synchronize_pairs = \
- self._deannotate_pairs(secondary_sync_pairs)
- _track_overlapping_sync_targets = weakref.WeakKeyDictionary()
- def _warn_for_conflicting_sync_targets(self):
- if not self.support_sync:
- return
- # we would like to detect if we are synchronizing any column
- # pairs in conflict with another relationship that wishes to sync
- # an entirely different column to the same target. This is a
- # very rare edge case so we will try to minimize the memory/overhead
- # impact of this check
- for from_, to_ in [
- (from_, to_) for (from_, to_) in self.synchronize_pairs
- ] + [
- (from_, to_) for (from_, to_) in self.secondary_synchronize_pairs
- ]:
- # save ourselves a ton of memory and overhead by only
- # considering columns that are subject to a overlapping
- # FK constraints at the core level. This condition can arise
- # if multiple relationships overlap foreign() directly, but
- # we're going to assume it's typically a ForeignKeyConstraint-
- # level configuration that benefits from this warning.
- if len(to_.foreign_keys) < 2:
- continue
- if to_ not in self._track_overlapping_sync_targets:
- self._track_overlapping_sync_targets[to_] = \
- weakref.WeakKeyDictionary({self.prop: from_})
- else:
- other_props = []
- prop_to_from = self._track_overlapping_sync_targets[to_]
- for pr, fr_ in prop_to_from.items():
- if pr.mapper in mapperlib._mapper_registry and \
- fr_ is not from_ and \
- pr not in self.prop._reverse_property:
- other_props.append((pr, fr_))
- if other_props:
- util.warn(
- "relationship '%s' will copy column %s to column %s, "
- "which conflicts with relationship(s): %s. "
- "Consider applying "
- "viewonly=True to read-only relationships, or provide "
- "a primaryjoin condition marking writable columns "
- "with the foreign() annotation." % (
- self.prop,
- from_, to_,
- ", ".join(
- "'%s' (copies %s to %s)" % (pr, fr_, to_)
- for (pr, fr_) in other_props)
- )
- )
- self._track_overlapping_sync_targets[to_][self.prop] = from_
- @util.memoized_property
- def remote_columns(self):
- return self._gather_join_annotations("remote")
- @util.memoized_property
- def local_columns(self):
- return self._gather_join_annotations("local")
- @util.memoized_property
- def foreign_key_columns(self):
- return self._gather_join_annotations("foreign")
- @util.memoized_property
- def deannotated_primaryjoin(self):
- return _deep_deannotate(self.primaryjoin)
- @util.memoized_property
- def deannotated_secondaryjoin(self):
- if self.secondaryjoin is not None:
- return _deep_deannotate(self.secondaryjoin)
- else:
- return None
- def _gather_join_annotations(self, annotation):
- s = set(
- self._gather_columns_with_annotation(
- self.primaryjoin, annotation)
- )
- if self.secondaryjoin is not None:
- s.update(
- self._gather_columns_with_annotation(
- self.secondaryjoin, annotation)
- )
- return set([x._deannotate() for x in s])
- def _gather_columns_with_annotation(self, clause, *annotation):
- annotation = set(annotation)
- return set([
- col for col in visitors.iterate(clause, {})
- if annotation.issubset(col._annotations)
- ])
- def join_targets(self, source_selectable,
- dest_selectable,
- aliased,
- single_crit=None):
- """Given a source and destination selectable, create a
- join between them.
- This takes into account aliasing the join clause
- to reference the appropriate corresponding columns
- in the target objects, as well as the extra child
- criterion, equivalent column sets, etc.
- """
- # place a barrier on the destination such that
- # replacement traversals won't ever dig into it.
- # its internal structure remains fixed
- # regardless of context.
- dest_selectable = _shallow_annotate(
- dest_selectable,
- {'no_replacement_traverse': True})
- primaryjoin, secondaryjoin, secondary = self.primaryjoin, \
- self.secondaryjoin, self.secondary
- # adjust the join condition for single table inheritance,
- # in the case that the join is to a subclass
- # this is analogous to the
- # "_adjust_for_single_table_inheritance()" method in Query.
- if single_crit is not None:
- if secondaryjoin is not None:
- secondaryjoin = secondaryjoin & single_crit
- else:
- primaryjoin = primaryjoin & single_crit
- if aliased:
- if secondary is not None:
- secondary = secondary.alias(flat=True)
- primary_aliasizer = ClauseAdapter(secondary)
- secondary_aliasizer = \
- ClauseAdapter(dest_selectable,
- equivalents=self.child_equivalents).\
- chain(primary_aliasizer)
- if source_selectable is not None:
- primary_aliasizer = \
- ClauseAdapter(secondary).\
- chain(ClauseAdapter(
- source_selectable,
- equivalents=self.parent_equivalents))
- secondaryjoin = \
- secondary_aliasizer.traverse(secondaryjoin)
- else:
- primary_aliasizer = ClauseAdapter(
- dest_selectable,
- exclude_fn=_ColInAnnotations("local"),
- equivalents=self.child_equivalents)
- if source_selectable is not None:
- primary_aliasizer.chain(
- ClauseAdapter(source_selectable,
- exclude_fn=_ColInAnnotations("remote"),
- equivalents=self.parent_equivalents))
- secondary_aliasizer = None
- primaryjoin = primary_aliasizer.traverse(primaryjoin)
- target_adapter = secondary_aliasizer or primary_aliasizer
- target_adapter.exclude_fn = None
- else:
- target_adapter = None
- return primaryjoin, secondaryjoin, secondary, \
- target_adapter, dest_selectable
- def create_lazy_clause(self, reverse_direction=False):
- binds = util.column_dict()
- equated_columns = util.column_dict()
- has_secondary = self.secondaryjoin is not None
- if has_secondary:
- lookup = collections.defaultdict(list)
- for l, r in self.local_remote_pairs:
- lookup[l].append((l, r))
- equated_columns[r] = l
- elif not reverse_direction:
- for l, r in self.local_remote_pairs:
- equated_columns[r] = l
- else:
- for l, r in self.local_remote_pairs:
- equated_columns[l] = r
- def col_to_bind(col):
- if (
- (not reverse_direction and 'local' in col._annotations) or
- reverse_direction and (
- (has_secondary and col in lookup) or
- (not has_secondary and 'remote' in col._annotations)
- )
- ):
- if col not in binds:
- binds[col] = sql.bindparam(
- None, None, type_=col.type, unique=True)
- return binds[col]
- return None
- lazywhere = self.primaryjoin
- if self.secondaryjoin is None or not reverse_direction:
- lazywhere = visitors.replacement_traverse(
- lazywhere, {}, col_to_bind)
- if self.secondaryjoin is not None:
- secondaryjoin = self.secondaryjoin
- if reverse_direction:
- secondaryjoin = visitors.replacement_traverse(
- secondaryjoin, {}, col_to_bind)
- lazywhere = sql.and_(lazywhere, secondaryjoin)
- bind_to_col = dict((binds[col].key, col) for col in binds)
- # this is probably not necessary
- lazywhere = _deep_deannotate(lazywhere)
- return lazywhere, bind_to_col, equated_columns
- class _ColInAnnotations(object):
- """Seralizable equivalent to:
- lambda c: "name" in c._annotations
- """
- def __init__(self, name):
- self.name = name
- def __call__(self, c):
- return self.name in c._annotations
|