12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689 |
- # orm/relationships.py
- # Copyright (C) 2005-2022 the SQLAlchemy authors and contributors
- # <see AUTHORS file>
- #
- # This module is part of SQLAlchemy and is released under
- # the MIT License: https://www.opensource.org/licenses/mit-license.php
- """Heuristics related to join conditions as used in
- :func:`_orm.relationship`.
- Provides the :class:`.JoinCondition` object, which encapsulates
- SQL annotation and aliasing behavior focused on the `primaryjoin`
- and `secondaryjoin` aspects of :func:`_orm.relationship`.
- """
- from __future__ import absolute_import
- import collections
- import re
- import weakref
- from . import attributes
- from .base import _is_mapped_class
- from .base import state_str
- from .interfaces import MANYTOMANY
- from .interfaces import MANYTOONE
- from .interfaces import ONETOMANY
- from .interfaces import PropComparator
- from .interfaces import StrategizedProperty
- from .util import _orm_annotate
- from .util import _orm_deannotate
- from .util import CascadeOptions
- from .. import exc as sa_exc
- from .. import log
- from .. import schema
- from .. import sql
- from .. import util
- from ..inspection import inspect
- from ..sql import coercions
- from ..sql import expression
- from ..sql import operators
- from ..sql import roles
- from ..sql import visitors
- from ..sql.util import _deep_deannotate
- from ..sql.util import _shallow_annotate
- from ..sql.util import adapt_criterion_to_null
- from ..sql.util import ClauseAdapter
- from ..sql.util import join_condition
- from ..sql.util import selectables_overlap
- from ..sql.util import visit_binary_product
- 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.
- .. seealso::
- :ref:`relationship_custom_foreign`
- :func:`.foreign`
- """
- return _annotate_columns(
- coercions.expect(roles.ColumnArgumentRole, 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.
- .. seealso::
- :ref:`relationship_custom_foreign`
- :func:`.remote`
- """
- return _annotate_columns(
- coercions.expect(roles.ColumnArgumentRole, expr), {"foreign": True}
- )
- @log.class_logger
- 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.
- .. seealso::
- :ref:`relationship_config_toplevel`
- """
- strategy_wildcard_key = "relationship"
- inherit_cache = True
- _links_to_entity = True
- _persistence_only = dict(
- passive_deletes=False,
- passive_updates=True,
- enable_typechecks=True,
- active_history=False,
- cascade_backrefs=True,
- )
- _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,
- overlaps=None,
- post_update=False,
- cascade=False,
- viewonly=False,
- lazy="select",
- collection_class=None,
- passive_deletes=_persistence_only["passive_deletes"],
- passive_updates=_persistence_only["passive_updates"],
- remote_side=None,
- enable_typechecks=_persistence_only["enable_typechecks"],
- join_depth=None,
- comparator_factory=None,
- single_parent=False,
- innerjoin=False,
- distinct_target_key=None,
- doc=None,
- active_history=_persistence_only["active_history"],
- cascade_backrefs=_persistence_only["cascade_backrefs"],
- load_on_pending=False,
- bake_queries=True,
- _local_remote_pairs=None,
- query_class=None,
- info=None,
- omit_join=None,
- sync_backref=None,
- _legacy_inactive_history_style=False,
- ):
- """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:`_orm.relationship`, used in a classical mapping::
- mapper(Parent, properties={
- 'children': relationship(Child)
- })
- Some arguments accepted by :func:`_orm.relationship`
- optionally accept a
- callable function, which when called produces the desired value.
- The callable is invoked by the parent :class:`_orm.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:`_orm.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 for related classes to be imported into the local module space
- before the dependent classes have been declared. It is still required
- that the modules in which these related classes appear are imported
- anywhere in the application at some point before the related mappings
- are actually used, else a lookup error will be raised when the
- :func:`_orm.relationship`
- attempts to resolve the string reference to the
- related class. An example of a string- resolved class is as
- follows::
- 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:`_orm.relationship`.
- :ref:`orm_tutorial_relationship` - ORM tutorial introduction.
- :param argument:
- A mapped class, or actual :class:`_orm.Mapper` instance,
- representing
- the target of the relationship.
- :paramref:`_orm.relationship.argument`
- may also be passed as a callable
- function which is evaluated at mapper initialization time, and may
- be passed as a string name when using Declarative.
- .. warning:: Prior to SQLAlchemy 1.3.16, this value is interpreted
- using Python's ``eval()`` function.
- **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**.
- See :ref:`declarative_relationship_eval` for details on
- declarative evaluation of :func:`_orm.relationship` arguments.
- .. versionchanged 1.3.16::
- The string evaluation of the main "argument" no longer accepts an
- open ended Python expression, instead only accepting a string
- class name or dotted package-qualified name.
- .. 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:`_schema.Table`.
- In less common circumstances, the argument may also be specified
- as an :class:`_expression.Alias` construct, or even a
- :class:`_expression.Join` construct.
- :paramref:`_orm.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:`_schema.Table`
- that is
- present in the :class:`_schema.MetaData`
- collection associated with the
- parent-mapped :class:`_schema.Table`.
- .. warning:: When passed as a Python-evaluable string, the
- argument is interpreted using Python's ``eval()`` function.
- **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**.
- See :ref:`declarative_relationship_eval` for details on
- declarative evaluation of :func:`_orm.relationship` arguments.
- The :paramref:`_orm.relationship.secondary` keyword argument is
- typically applied in the case where the intermediary
- :class:`_schema.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:`_orm.relationship.viewonly` flag so that this
- :func:`_orm.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:`_orm.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:`_orm.relationship` SQL
- conditions to be used.
- .. versionadded:: 0.9.2 :paramref:`_orm.relationship.secondary`
- works
- more effectively when referring to a :class:`_expression.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:`_orm.relationship.back_populates` - alternative form
- of backref specification.
- :func:`.backref` - allows control over :func:`_orm.relationship`
- configuration when using :paramref:`_orm.relationship.backref`.
- :param back_populates:
- Takes a string name and has the same meaning as
- :paramref:`_orm.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:`_orm.relationship.back_populates` to this relationship to
- ensure proper functioning.
- .. seealso::
- :ref:`relationships_backref` - Introductory documentation and
- examples.
- :paramref:`_orm.relationship.backref` - alternative form
- of backref specification.
- :param overlaps:
- A string name or comma-delimited set of names of other relationships
- on either this mapper, a descendant mapper, or a target mapper with
- which this relationship may write to the same foreign keys upon
- persistence. The only effect this has is to eliminate the
- warning that this relationship will conflict with another upon
- persistence. This is used for such relationships that are truly
- capable of conflicting with each other on write, but the application
- will ensure that no such conflicts occur.
- .. versionadded:: 1.4
- .. seealso::
- :ref:`error_qzyx` - usage example
- :param bake_queries=True:
- Enable :ref:`lambda caching <engine_lambda_caching>` for loader
- strategies, if applicable, which adds a performance gain to the
- construction of SQL constructs used by loader strategies, in addition
- to the usual SQL statement caching used throughout SQLAlchemy. This
- parameter currently applies only to the "lazy" and "selectin" loader
- strategies. There is generally no reason to set this parameter to
- False.
- .. versionchanged:: 1.4 Relationship loaders no longer use the
- previous "baked query" system of query caching. The "lazy"
- and "selectin" loaders make use of the "lambda cache" system
- for the construction of SQL constructs,
- as well as the usual SQL caching system that is throughout
- SQLAlchemy as of the 1.4 series.
- :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.
- .. deprecated:: 1.4 The
- :paramref:`_orm.relationship.cascade_backrefs`
- flag will default to False in all cases in SQLAlchemy 2.0.
- .. seealso::
- :ref:`backref_cascade` - Full discussion and examples on how
- the :paramref:`_orm.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.
- .. versionchanged:: 0.9.0 -
- :paramref:`_orm.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 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:`_orm.relationship`
- object's :paramref:`_orm.relationship.primaryjoin` condition.
- That is, if the :paramref:`_orm.relationship.primaryjoin`
- condition of this :func:`_orm.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:`_orm.relationship` is ``b.a_id``.
- In normal cases, the :paramref:`_orm.relationship.foreign_keys`
- parameter is **not required.** :func:`_orm.relationship` will
- automatically determine which columns in the
- :paramref:`_orm.relationship.primaryjoin` condition are to be
- considered "foreign key" columns based on those
- :class:`_schema.Column` objects that specify
- :class:`_schema.ForeignKey`,
- or are otherwise listed as referencing columns in a
- :class:`_schema.ForeignKeyConstraint` construct.
- :paramref:`_orm.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:`_orm.relationship`
- to consider just those columns specified
- here as "foreign".
- 2. The :class:`_schema.Table` being mapped does not actually have
- :class:`_schema.ForeignKey` or
- :class:`_schema.ForeignKeyConstraint`
- constructs present, often because the table
- was reflected from a database that does not support foreign key
- reflection (MySQL MyISAM).
- 3. The :paramref:`_orm.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:`_orm.relationship` construct will raise informative
- error messages that suggest the use of the
- :paramref:`_orm.relationship.foreign_keys` parameter when
- presented with an ambiguous condition. In typical cases,
- if :func:`_orm.relationship` doesn't raise any exceptions, the
- :paramref:`_orm.relationship.foreign_keys` parameter is usually
- not needed.
- :paramref:`_orm.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.
- .. warning:: When passed as a Python-evaluable string, the
- argument is interpreted using Python's ``eval()`` function.
- **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**.
- See :ref:`declarative_relationship_eval` for details on
- declarative evaluation of :func:`_orm.relationship` arguments.
- .. seealso::
- :ref:`relationship_foreign_keys`
- :ref:`relationship_custom_foreign`
- :func:`.foreign` - allows direct annotation of the "foreign"
- columns within a :paramref:`_orm.relationship.primaryjoin`
- condition.
- :param info: Optional data dictionary which will be populated into the
- :attr:`.MapperProperty.info` attribute of this object.
- :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:`_orm.joinedload.innerjoin`. See that flag
- for details on nested / unnested behaviors.
- .. seealso::
- :paramref:`_orm.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:`_orm.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.
- * ``selectin`` - items should be loaded "eagerly" as the parents
- are loaded, using one or more additional SQL statements, which
- issues a JOIN to the immediate parent object, specifying primary
- key identifiers using an IN clause.
- .. versionadded:: 1.2
- * ``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.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:`_orm.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:`_orm.relationship.order_by`
- is expected to refer to
- one of the :class:`_schema.Column`
- objects to which the target class is
- mapped, or the attribute itself bound to the target class which
- refers to the column.
- :paramref:`_orm.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.
- .. warning:: When passed as a Python-evaluable string, the
- argument is interpreted using Python's ``eval()`` function.
- **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**.
- See :ref:`declarative_relationship_eval` for details on
- declarative evaluation of :func:`_orm.relationship` arguments.
- :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 the parent
- object is deleted and 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.
- Additionally, the "nulling out" will still occur if the child
- object is de-associated with the parent.
- .. 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:`_orm.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:`_orm.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 the child object against the parent object, or in a
- many-to-many relationship the join of the parent 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:`_orm.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.
- .. warning:: When passed as a Python-evaluable string, the
- argument is interpreted using Python's ``eval()`` function.
- **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**.
- See :ref:`declarative_relationship_eval` for details on
- declarative evaluation of :func:`_orm.relationship` arguments.
- .. 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:`_orm.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.
- .. warning:: When passed as a Python-evaluable string, the
- argument is interpreted using Python's ``eval()`` function.
- **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**.
- See :ref:`declarative_relationship_eval` for details on
- declarative evaluation of :func:`_orm.relationship` arguments.
- .. seealso::
- :ref:`self_referential` - in-depth explanation of how
- :paramref:`_orm.relationship.remote_side`
- is used to configure self-referential relationships.
- :func:`.remote` - an annotation function that accomplishes the
- same purpose as :paramref:`_orm.relationship.remote_side`,
- typically
- when a custom :paramref:`_orm.relationship.primaryjoin` condition
- is used.
- :param query_class:
- A :class:`_query.Query`
- subclass that will be used internally by the
- ``AppenderQuery`` 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:`_orm.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.
- .. warning:: When passed as a Python-evaluable string, the
- argument is interpreted using Python's ``eval()`` function.
- **DO NOT PASS UNTRUSTED INPUT TO THIS STRING**.
- See :ref:`declarative_relationship_eval` for details on
- declarative evaluation of :func:`_orm.relationship` arguments.
- .. 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:`_orm.relationship` constructs which
- are many-to-one or many-to-many and also
- specify the ``delete-orphan`` cascade option. The
- :func:`_orm.relationship` construct itself will raise an error
- instructing when this option is required.
- .. seealso::
- :ref:`unitofwork_cascades` - includes detail on when the
- :paramref:`_orm.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:`_orm.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:`_orm.relationship.uselist`
- to
- False.
- The :paramref:`_orm.relationship.uselist`
- flag is also available on an
- existing :func:`_orm.relationship`
- construct as a read-only attribute,
- which can be used to determine if this :func:`_orm.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:`_orm.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:`_orm.relationship` which specifies
- :paramref:`_orm.relationship.viewonly` can work
- with a wider range of SQL operations within the
- :paramref:`_orm.relationship.primaryjoin` condition, including
- operations that feature the use of a variety of comparison operators
- as well as SQL functions such as :func:`_expression.cast`. The
- :paramref:`_orm.relationship.viewonly`
- flag is also of general use when defining any kind of
- :func:`_orm.relationship` that doesn't represent
- the full set of related objects, to prevent modifications of the
- collection from resulting in persistence operations.
- When using the :paramref:`_orm.relationship.viewonly` flag in
- conjunction with backrefs, the originating relationship for a
- particular state change will not produce state changes within the
- viewonly relationship. This is the behavior implied by
- :paramref:`_orm.relationship.sync_backref` being set to False.
- .. versionchanged:: 1.3.17 - the
- :paramref:`_orm.relationship.sync_backref` flag is set to False
- when using viewonly in conjunction with backrefs.
- .. seealso::
- :paramref:`_orm.relationship.sync_backref`
- :param sync_backref:
- A boolean that enables the events used to synchronize the in-Python
- attributes when this relationship is target of either
- :paramref:`_orm.relationship.backref` or
- :paramref:`_orm.relationship.back_populates`.
- Defaults to ``None``, which indicates that an automatic value should
- be selected based on the value of the
- :paramref:`_orm.relationship.viewonly` flag. When left at its
- default, changes in state will be back-populated only if neither
- sides of a relationship is viewonly.
- .. versionadded:: 1.3.17
- .. versionchanged:: 1.4 - A relationship that specifies
- :paramref:`_orm.relationship.viewonly` automatically implies
- that :paramref:`_orm.relationship.sync_backref` is ``False``.
- .. seealso::
- :paramref:`_orm.relationship.viewonly`
- :param omit_join:
- Allows manual control over the "selectin" automatic join
- optimization. Set to ``False`` to disable the "omit join" feature
- added in SQLAlchemy 1.3; or leave as ``None`` to leave automatic
- optimization in place.
- .. note:: This flag may only be set to ``False``. It is not
- necessary to set it to ``True`` as the "omit_join" optimization is
- automatically detected; if it is not detected, then the
- optimization is not supported.
- .. versionchanged:: 1.3.11 setting ``omit_join`` to True will now
- emit a warning as this was not the intended use of this flag.
- .. versionadded:: 1.3
- """
- 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
- if viewonly:
- self._warn_for_persistence_only_flags(
- passive_deletes=passive_deletes,
- passive_updates=passive_updates,
- enable_typechecks=enable_typechecks,
- active_history=active_history,
- cascade_backrefs=cascade_backrefs,
- )
- if viewonly and sync_backref:
- raise sa_exc.ArgumentError(
- "sync_backref and viewonly cannot both be True"
- )
- self.sync_backref = sync_backref
- 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._legacy_inactive_history_style = _legacy_inactive_history_style
- self.join_depth = join_depth
- if omit_join:
- util.warn(
- "setting omit_join to True is not supported; selectin "
- "loading of this relationship may not work correctly if this "
- "flag is set explicitly. omit_join optimization is "
- "automatically detected for conditions under which it is "
- "supported."
- )
- self.omit_join = omit_join
- self.local_remote_pairs = _local_remote_pairs
- 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()
- if overlaps:
- self._overlaps = set(re.split(r"\s*,\s*", overlaps))
- else:
- self._overlaps = ()
- if cascade is not False:
- self.cascade = cascade
- elif self.viewonly:
- self.cascade = "none"
- else:
- self.cascade = "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 _warn_for_persistence_only_flags(self, **kw):
- for k, v in kw.items():
- if v != self._persistence_only[k]:
- # we are warning here rather than warn deprecated as this is a
- # configuration mistake, and Python shows regular warnings more
- # aggressively than deprecation warnings by default. Unlike the
- # case of setting viewonly with cascade, the settings being
- # warned about here are not actively doing the wrong thing
- # against viewonly=True, so it is not as urgent to have these
- # raise an error.
- util.warn(
- "Setting %s on relationship() while also "
- "setting viewonly=True does not make sense, as a "
- "viewonly=True relationship does not perform persistence "
- "operations. This configuration may raise an error "
- "in a future release." % (k,)
- )
- 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.
- .. seealso::
- :class:`.PropComparator`
- :class:`.ColumnProperty.Comparator`
- :class:`.ColumnOperators`
- :ref:`types_operators`
- :attr:`.TypeEngine.comparator_factory`
- """
- _of_type = None
- _extra_criteria = ()
- def __init__(
- self,
- prop,
- parentmapper,
- adapt_to_entity=None,
- of_type=None,
- extra_criteria=(),
- ):
- """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
- self._extra_criteria = extra_criteria
- 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 entity(self):
- """The target entity referred to by this
- :class:`.RelationshipProperty.Comparator`.
- This is either a :class:`_orm.Mapper` or :class:`.AliasedInsp`
- object.
- This is the "target" or "remote" side of the
- :func:`_orm.relationship`.
- """
- # this is a relatively recent change made for
- # 1.4.27 as part of #7244.
- # TODO: shouldn't _of_type be inspected up front when received?
- if self._of_type is not None:
- return inspect(self._of_type)
- else:
- return self.property.entity
- @util.memoized_property
- def mapper(self):
- """The target :class:`_orm.Mapper` referred to by this
- :class:`.RelationshipProperty.Comparator`.
- This is the "target" or "remote" side of the
- :func:`_orm.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_entity = inspect(self._of_type)
- else:
- of_type_entity = None
- (
- pj,
- sj,
- source,
- dest,
- secondary,
- target_adapter,
- ) = self.property._create_joins(
- source_selectable=adapt_from,
- source_polymorphic=True,
- of_type_entity=of_type_entity,
- alias_secondary=True,
- extra_criteria=self._extra_criteria,
- )
- if sj is not None:
- return pj & sj
- else:
- return pj
- def of_type(self, cls):
- r"""Redefine this object in terms of a polymorphic subclass.
- See :meth:`.PropComparator.of_type` for an example.
- """
- return RelationshipProperty.Comparator(
- self.property,
- self._parententity,
- adapt_to_entity=self._adapt_to_entity,
- of_type=cls,
- extra_criteria=self._extra_criteria,
- )
- def and_(self, *other):
- """Add AND criteria.
- See :meth:`.PropComparator.and_` for an example.
- .. versionadded:: 1.4
- """
- return RelationshipProperty.Comparator(
- self.property,
- self._parententity,
- adapt_to_entity=self._adapt_to_entity,
- of_type=self._of_type,
- extra_criteria=self._extra_criteria + other,
- )
- 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._anonymous_fromclause()
- 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_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)
- if secondary is not None:
- ex = (
- sql.exists(1)
- .where(crit)
- .select_from(dest, secondary)
- .correlate_except(dest, secondary)
- )
- else:
- ex = (
- sql.exists(1)
- .where(crit)
- .select_from(dest)
- .correlate_except(dest)
- )
- 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:`_orm.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:`_orm.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.Query.outerjoin`
- as well as :ref:`ormtutorial_joins`
- for more details on constructing outer joins.
- kwargs may be ignored by this operator but are required for API
- conformance.
- """
- 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(local_col, state, remote_col):
- dict_ = state.dict
- return sql.bindparam(
- local_col.key,
- type_=local_col.type,
- unique=True,
- callable_=self.property._get_attr_w_warn_on_none(
- self.property.mapper, state, dict_, remote_col
- ),
- )
- 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):
- self.prop.parent._check_configure()
- return self.prop
- def _with_parent(self, instance, alias_secondary=True, from_entity=None):
- assert instance is not None
- adapt_source = None
- if from_entity is not None:
- insp = inspect(from_entity)
- if insp.is_aliased_class:
- adapt_source = insp._adapter.adapt_clause
- return self._optimized_compare(
- instance,
- value_is_parent=True,
- adapt_source=adapt_source,
- alias_secondary=alias_secondary,
- )
- def _optimized_compare(
- self,
- state,
- value_is_parent=False,
- adapt_source=None,
- alias_secondary=True,
- ):
- if state is not None:
- try:
- state = inspect(state)
- except sa_exc.NoInspectionAvailable:
- state = None
- if state is None or not getattr(state, "is_instance", False):
- raise sa_exc.ArgumentError(
- "Mapped instance expected for relationship "
- "comparison to object. Classes, queries and other "
- "SQL elements are not accepted in this context; for "
- "comparison with a subquery, "
- "use %s.has(**criteria)." % self
- )
- 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(
- mapper,
- state,
- dict_,
- bind_to_col[bindparam._identifying_key],
- )
- if self.secondary is not None and alias_secondary:
- criterion = ClauseAdapter(
- self.secondary._anonymous_fromclause()
- ).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, mapper, state, dict_, column):
- """Create the callable that is used in a many-to-one expression.
- E.g.::
- u1 = s.query(User).get(5)
- expr = Address.user == u1
- Above, the SQL should be "address.user_id = 5". The callable
- returned by this method produces the value "5" based on the identity
- of ``u1``.
- """
- # in this callable, we're trying to thread the needle through
- # a wide variety of scenarios, including:
- #
- # * the object hasn't been flushed yet and there's no value for
- # the attribute as of yet
- #
- # * the object hasn't been flushed yet but it has a user-defined
- # value
- #
- # * the object has a value but it's expired and not locally present
- #
- # * the object has a value but it's expired and not locally present,
- # and the object is also detached
- #
- # * The object hadn't been flushed yet, there was no value, but
- # later, the object has been expired and detached, and *now*
- # they're trying to evaluate it
- #
- # * the object had a value, but it was changed to a new value, and
- # then expired
- #
- # * the object had a value, but it was changed to a new value, and
- # then expired, then the object was detached
- #
- # * the object has a user-set value, but it's None and we don't do
- # the comparison correctly for that so warn
- #
- prop = mapper.get_property_by_column(column)
- # by invoking this method, InstanceState will track the last known
- # value for this key each time the attribute is to be expired.
- # this feature was added explicitly for use in this method.
- state._track_last_known_value(prop.key)
- def _go():
- last_known = to_return = state._last_known_values[prop.key]
- existing_is_available = last_known is not attributes.NO_VALUE
- # we support that the value may have changed. so here we
- # try to get the most recent value including re-fetching.
- # only if we can't get a value now due to detachment do we return
- # the last known value
- current_value = mapper._get_state_attr_by_column(
- state,
- dict_,
- column,
- passive=attributes.PASSIVE_OFF
- if state.persistent
- else attributes.PASSIVE_NO_FETCH ^ attributes.INIT_OK,
- )
- if current_value is attributes.NEVER_SET:
- if not existing_is_available:
- raise sa_exc.InvalidRequestError(
- "Can't resolve value for column %s on object "
- "%s; no value has been set for this column"
- % (column, state_str(state))
- )
- elif current_value is attributes.PASSIVE_NO_RESULT:
- if not existing_is_available:
- raise sa_exc.InvalidRequestError(
- "Can't resolve value for column %s on object "
- "%s; the object is detached and the value was "
- "expired" % (column, state_str(state))
- )
- else:
- to_return = current_value
- if to_return 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 to_return
- 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:
- impl = source_state.get_impl(self.key)
- instances_iterable = impl.get_collection(source_state, source_dict)
- # if this is a CollectionAttributeImpl, then empty should
- # be False, otherwise "self.key in source_dict" should not be
- # True
- assert not instances_iterable.empty if impl.collection else True
- 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 CollectionAttributeImpl 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_iterable:
- 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
- @property
- def _effective_sync_backref(self):
- if self.viewonly:
- return False
- else:
- return self.sync_backref is not False
- @staticmethod
- def _check_sync_backref(rel_a, rel_b):
- if rel_a.viewonly and rel_b.sync_backref:
- raise sa_exc.InvalidRequestError(
- "Relationship %s cannot specify sync_backref=True since %s "
- "includes viewonly=True." % (rel_b, rel_a)
- )
- if (
- rel_a.viewonly
- and not rel_b.viewonly
- and rel_b.sync_backref is not False
- ):
- rel_b.sync_backref = False
- def _add_reverse_property(self, key):
- other = self.mapper.get_property(key, _configure_mappers=False)
- if not isinstance(other, RelationshipProperty):
- raise sa_exc.InvalidRequestError(
- "back_populates on relationship '%s' refers to attribute '%s' "
- "that is not a relationship. The back_populates parameter "
- "should refer to the name of a relationship on the target "
- "class." % (self, other)
- )
- # viewonly and sync_backref cases
- # 1. self.viewonly==True and other.sync_backref==True -> error
- # 2. self.viewonly==True and other.viewonly==False and
- # other.sync_backref==None -> warn sync_backref=False, set to False
- self._check_sync_backref(self, other)
- # 3. other.viewonly==True and self.sync_backref==True -> error
- # 4. other.viewonly==True and self.viewonly==False and
- # self.sync_backref==None -> warn sync_backref=False, set to False
- self._check_sync_backref(other, self)
- 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
- @util.preload_module("sqlalchemy.orm.mapper")
- def entity(self):
- """Return the target mapped entity, which is an inspect() of the
- class or aliased class that is referred towards.
- """
- mapperlib = util.preloaded.orm_mapper
- if isinstance(self.argument, util.string_types):
- argument = self._clsregistry_resolve_name(self.argument)()
- elif callable(self.argument) and not isinstance(
- self.argument, (type, mapperlib.Mapper)
- ):
- argument = self.argument()
- else:
- argument = self.argument
- if isinstance(argument, type):
- return mapperlib.class_mapper(argument, configure=False)
- try:
- entity = inspect(argument)
- except sa_exc.NoInspectionAvailable:
- pass
- else:
- if hasattr(entity, "mapper"):
- return entity
- raise sa_exc.ArgumentError(
- "relationship '%s' expects "
- "a class or a mapper argument (received: %s)"
- % (self.key, type(argument))
- )
- @util.memoized_property
- def mapper(self):
- """Return the targeted :class:`_orm.Mapper` for this
- :class:`.RelationshipProperty`.
- This is a lazy-initializing static attribute.
- """
- return self.entity.mapper
- def do_init(self):
- self._check_conflicts()
- self._process_dependent_arguments()
- self._setup_registry_dependencies()
- 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 _setup_registry_dependencies(self):
- self.parent.mapper.registry._set_depends_on(
- self.entity.mapper.registry
- )
- 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 isinstance(attr_value, util.string_types):
- setattr(
- self,
- attr,
- self._clsregistry_resolve_arg(
- attr_value, favor_tables=attr == "secondary"
- )(),
- )
- elif callable(attr_value) and not _is_mapped_class(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(
- coercions.expect(
- roles.ColumnArgumentRole, val, argname=attr
- )
- ),
- )
- if self.secondary is not None and _is_mapped_class(self.secondary):
- raise sa_exc.ArgumentError(
- "secondary argument %s passed to to relationship() %s must "
- "be a Table object or other FROM clause; can't send a mapped "
- "class directly as rows in 'secondary' are persisted "
- "independently of a class that is mapped "
- "to that same table." % (self.secondary, self)
- )
- # 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 = tuple(
- coercions.expect(
- roles.ColumnArgumentRole, x, argname="order_by"
- )
- for x in util.to_list(self.order_by)
- )
- self._user_defined_foreign_keys = util.column_set(
- coercions.expect(
- roles.ColumnArgumentRole, x, argname="foreign_keys"
- )
- for x in util.to_column_set(self._user_defined_foreign_keys)
- )
- self.remote_side = util.column_set(
- coercions.expect(
- roles.ColumnArgumentRole, x, argname="remote_side"
- )
- for x in util.to_column_set(self.remote_side)
- )
- self.target = self.entity.persist_selectable
- def _setup_join_conditions(self):
- self._join_condition = jc = JoinCondition(
- parent_persist_selectable=self.parent.persist_selectable,
- child_persist_selectable=self.entity.persist_selectable,
- parent_local_selectable=self.parent.local_table,
- child_local_selectable=self.entity.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.primaryjoin
- self.secondaryjoin = jc.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
- @property
- def _clsregistry_resolve_arg(self):
- return self._clsregistry_resolvers[1]
- @property
- def _clsregistry_resolve_name(self):
- return self._clsregistry_resolvers[0]
- @util.memoized_property
- @util.preload_module("sqlalchemy.orm.clsregistry")
- def _clsregistry_resolvers(self):
- _resolver = util.preloaded.orm_clsregistry._resolver
- return _resolver(self.parent.class_, self)
- @util.preload_module("sqlalchemy.orm.mapper")
- def _check_conflicts(self):
- """Test that this relationship is legal, warn about
- inheritance conflicts."""
- mapperlib = util.preloaded.orm_mapper
- 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__,
- )
- )
- @property
- def cascade(self):
- """Return the current cascade setting for this
- :class:`.RelationshipProperty`.
- """
- return self._cascade
- @cascade.setter
- def cascade(self, cascade):
- self._set_cascade(cascade)
- def _set_cascade(self, cascade):
- cascade = CascadeOptions(cascade)
- if self.viewonly:
- non_viewonly = set(cascade).difference(
- CascadeOptions._viewonly_cascades
- )
- if non_viewonly:
- raise sa_exc.ArgumentError(
- 'Cascade settings "%s" apply to persistence operations '
- "and should not be combined with a viewonly=True "
- "relationship." % (", ".join(sorted(non_viewonly)))
- )
- if "mapper" in self.__dict__:
- self._check_cascade_settings(cascade)
- self._cascade = cascade
- if self._dependency_processor:
- self._dependency_processor.cascade = 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(
- "For %(direction)s relationship %(rel)s, delete-orphan "
- "cascade is normally "
- 'configured only on the "one" side of a one-to-many '
- "relationship, "
- 'and not on the "many" side of a many-to-one or many-to-many '
- "relationship. "
- "To force this relationship to allow a particular "
- '"%(relatedcls)s" object to be referred towards by only '
- 'a single "%(clsname)s" object at a time via the '
- "%(rel)s relationship, which "
- "would allow "
- "delete-orphan cascade to take place in this direction, set "
- "the single_parent=True flag."
- % {
- "rel": self,
- "direction": "many-to-one"
- if self.direction is MANYTOONE
- else "many-to-many",
- "clsname": self.parent.class_.__name__,
- "relatedcls": self.mapper.class_.__name__,
- },
- code="bbf0",
- )
- 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 _persists_for(self, mapper):
- """Return True if this property will persist values on behalf
- of the given mapper.
- """
- return (
- self.key in mapper.relationships
- and mapper.relationships[self.key] is self
- )
- 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.persist_selectable.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:`_orm.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)
- kwargs.setdefault("sync_backref", self.sync_backref)
- 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)
- @util.preload_module("sqlalchemy.orm.dependency")
- def _post_init(self):
- dependency = util.preloaded.orm_dependency
- 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_selectable=None,
- of_type_entity=None,
- alias_secondary=False,
- extra_criteria=(),
- ):
- aliased = False
- if alias_secondary and self.secondary is not None:
- aliased = True
- if source_selectable is None:
- if source_polymorphic and self.parent.with_polymorphic:
- source_selectable = self.parent._with_polymorphic_selectable
- if of_type_entity:
- dest_mapper = of_type_entity.mapper
- if dest_selectable is None:
- dest_selectable = of_type_entity.selectable
- aliased = True
- else:
- dest_mapper = self.mapper
- if dest_selectable is None:
- dest_selectable = self.entity.selectable
- if self.mapper.with_polymorphic:
- aliased = True
- if self._is_self_referential and source_selectable is None:
- dest_selectable = dest_selectable._anonymous_fromclause()
- aliased = True
- elif (
- dest_selectable is not self.mapper._with_polymorphic_selectable
- or self.mapper.with_polymorphic
- ):
- aliased = True
- single_crit = dest_mapper._single_table_criterion
- aliased = aliased or (
- source_selectable is not None
- and (
- source_selectable
- is not self.parent._with_polymorphic_selectable
- or source_selectable._is_subquery
- )
- )
- (
- primaryjoin,
- secondaryjoin,
- secondary,
- target_adapter,
- dest_selectable,
- ) = self._join_condition.join_targets(
- source_selectable,
- dest_selectable,
- aliased,
- single_crit,
- extra_criteria,
- )
- if source_selectable is None:
- source_selectable = self.parent.local_table
- if dest_selectable is None:
- dest_selectable = self.entity.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)
- clone = None # remove gc cycles
- return element
- class JoinCondition(object):
- def __init__(
- self,
- parent_persist_selectable,
- child_persist_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_persist_selectable = parent_persist_selectable
- self.parent_local_selectable = parent_local_selectable
- self.child_persist_selectable = child_persist_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._sanitize_joins()
- self._annotate_fks()
- self._annotate_remote()
- self._annotate_local()
- self._annotate_parentmapper()
- 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 _sanitize_joins(self):
- """remove the parententity annotation from our join conditions which
- can leak in here based on some declarative patterns and maybe others.
- We'd want to remove "parentmapper" also, but apparently there's
- an exotic use case in _join_fixture_inh_selfref_w_entity
- that relies upon it being present, see :ticket:`3364`.
- """
- self.primaryjoin = _deep_deannotate(
- self.primaryjoin, values=("parententity", "proxy_key")
- )
- if self.secondaryjoin is not None:
- self.secondaryjoin = _deep_deannotate(
- self.secondaryjoin, values=("parententity", "proxy_key")
- )
- 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_persist_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_persist_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_persist_selectable,
- self.child_persist_selectable,
- a_subset=self.parent_local_selectable,
- consider_as_foreign_keys=consider_as_foreign_keys,
- )
- except sa_exc.NoForeignKeysError as nfe:
- if self.secondary is not None:
- util.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)
- ),
- from_=nfe,
- )
- else:
- util.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
- ),
- from_=nfe,
- )
- except sa_exc.AmbiguousForeignKeysError as afe:
- if self.secondary is not None:
- util.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)
- ),
- from_=afe,
- )
- else:
- util.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
- ),
- from_=afe,
- )
- @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 = dict(element._annotations)
- del v["remote"]
- v["local"] = True
- return element._with_annotations(v)
- elif "local" in element._annotations:
- v = dict(element._annotations)
- 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_persist_selectable
- mt = self.child_persist_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_persist_selectable, self.child_persist_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):
- # use set() to avoid generating ``__eq__()`` expressions
- # against each element
- if element in set(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_persist_selectable.c.contains_column(
- right
- ) and self.parent_persist_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_persist_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_persist_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 _annotate_parentmapper(self):
- if self.prop is None:
- return
- def parentmappers_(elem):
- if "remote" in elem._annotations:
- return elem._annotate({"parentmapper": self.prop.mapper})
- elif "local" in elem._annotations:
- return elem._annotate({"parentmapper": self.prop.parent})
- self.primaryjoin = visitors.replacement_traverse(
- self.primaryjoin, {}, parentmappers_
- )
- 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_persist_selectable.c)
- targetcols = util.column_set(self.child_persist_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 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 (
- not pr.mapper._dispose_called
- and pr not in self.prop._reverse_property
- and pr.key not in self.prop._overlaps
- and self.prop.key not in pr._overlaps
- # note: the "__*" symbol is used internally by
- # SQLAlchemy as a general means of suppressing the
- # overlaps warning for some extension cases, however
- # this is not currently
- # a publicly supported symbol and may change at
- # any time.
- and "__*" not in self.prop._overlaps
- and "__*" not in pr._overlaps
- and not self.prop.parent.is_sibling(pr.parent)
- and not self.prop.mapper.is_sibling(pr.mapper)
- and not self.prop.parent.is_sibling(pr.mapper)
- and not self.prop.mapper.is_sibling(pr.parent)
- and (
- self.prop.key != pr.key
- or not self.prop.parent.common_parent(pr.parent)
- )
- ):
- 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. "
- "If this is not the intention, consider if these "
- "relationships should be linked with "
- "back_populates, or if viewonly=True should be "
- "applied to one or more if they are read-only. "
- "For the less common case that foreign key "
- "constraints are partially overlapping, the "
- "orm.foreign() "
- "annotation can be used to isolate the columns that "
- "should be written towards. To silence this "
- "warning, add the parameter 'overlaps=\"%s\"' to the "
- "'%s' relationship."
- % (
- self.prop,
- from_,
- to_,
- ", ".join(
- sorted(
- "'%s' (copies %s to %s)" % (pr, fr_, to_)
- for (pr, fr_) in other_props
- )
- ),
- ",".join(sorted(pr.key for pr, fr in other_props)),
- self.prop,
- ),
- code="qzyx",
- )
- 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")
- 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 {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,
- extra_criteria=(),
- ):
- """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 extra_criteria:
- if secondaryjoin is not None:
- secondaryjoin = secondaryjoin & sql.and_(*extra_criteria)
- else:
- primaryjoin = primaryjoin & sql.and_(*extra_criteria)
- if aliased:
- if secondary is not None:
- secondary = secondary._anonymous_fromclause(flat=True)
- primary_aliasizer = ClauseAdapter(
- secondary, exclude_fn=_ColInAnnotations("local")
- )
- secondary_aliasizer = ClauseAdapter(
- dest_selectable, equivalents=self.child_equivalents
- ).chain(primary_aliasizer)
- if source_selectable is not None:
- primary_aliasizer = ClauseAdapter(
- secondary, exclude_fn=_ColInAnnotations("local")
- ).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 = {binds[col].key: col for col in binds}
- return lazywhere, bind_to_col, equated_columns
- class _ColInAnnotations(object):
- """Serializable object that tests for a name in c._annotations."""
- __slots__ = ("name",)
- def __init__(self, name):
- self.name = name
- def __call__(self, c):
- return self.name in c._annotations
|