diff options
author | cyfraeviolae <cyfraeviolae> | 2024-04-03 03:17:55 -0400 |
---|---|---|
committer | cyfraeviolae <cyfraeviolae> | 2024-04-03 03:17:55 -0400 |
commit | 12cf076118570eebbff08c6b3090e0d4798447a1 (patch) | |
tree | 3ba25e17e3c3a5e82316558ba3864b955919ff72 /venv/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py | |
parent | c45662ff3923b34614ddcc8feb9195541166dcc5 (diff) |
no venv
Diffstat (limited to 'venv/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py')
-rw-r--r-- | venv/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py | 3500 |
1 files changed, 0 insertions, 3500 deletions
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py deleted file mode 100644 index b5e33ff..0000000 --- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py +++ /dev/null @@ -1,3500 +0,0 @@ -# orm/relationships.py -# Copyright (C) 2005-2024 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 annotations - -import collections -from collections import abc -import dataclasses -import inspect as _py_inspect -import itertools -import re -import typing -from typing import Any -from typing import Callable -from typing import cast -from typing import Collection -from typing import Dict -from typing import FrozenSet -from typing import Generic -from typing import Iterable -from typing import Iterator -from typing import List -from typing import NamedTuple -from typing import NoReturn -from typing import Optional -from typing import Sequence -from typing import Set -from typing import Tuple -from typing import Type -from typing import TypeVar -from typing import Union -import weakref - -from . import attributes -from . import strategy_options -from ._typing import insp_is_aliased_class -from ._typing import is_has_collection_adapter -from .base import _DeclarativeMapped -from .base import _is_mapped_class -from .base import class_mapper -from .base import DynamicMapped -from .base import LoaderCallableStatus -from .base import PassiveFlag -from .base import state_str -from .base import WriteOnlyMapped -from .interfaces import _AttributeOptions -from .interfaces import _IntrospectsAnnotations -from .interfaces import MANYTOMANY -from .interfaces import MANYTOONE -from .interfaces import ONETOMANY -from .interfaces import PropComparator -from .interfaces import RelationshipDirection -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 Exists -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._typing import _ColumnExpressionArgument -from ..sql._typing import _HasClauseElement -from ..sql.annotation import _safe_annotate -from ..sql.elements import ColumnClause -from ..sql.elements import ColumnElement -from ..sql.util import _deep_annotate -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 -from ..util.typing import de_optionalize_union_types -from ..util.typing import Literal -from ..util.typing import resolve_name_to_real_class_name - -if typing.TYPE_CHECKING: - from ._typing import _EntityType - from ._typing import _ExternalEntityType - from ._typing import _IdentityKeyType - from ._typing import _InstanceDict - from ._typing import _InternalEntityType - from ._typing import _O - from ._typing import _RegistryType - from .base import Mapped - from .clsregistry import _class_resolver - from .clsregistry import _ModNS - from .decl_base import _ClassScanMapperConfig - from .dependency import DependencyProcessor - from .mapper import Mapper - from .query import Query - from .session import Session - from .state import InstanceState - from .strategies import LazyLoader - from .util import AliasedClass - from .util import AliasedInsp - from ..sql._typing import _CoreAdapterProto - from ..sql._typing import _EquivalentColumnMap - from ..sql._typing import _InfoType - from ..sql.annotation import _AnnotationDict - from ..sql.annotation import SupportsAnnotations - from ..sql.elements import BinaryExpression - from ..sql.elements import BindParameter - from ..sql.elements import ClauseElement - from ..sql.schema import Table - from ..sql.selectable import FromClause - from ..util.typing import _AnnotationScanType - from ..util.typing import RODescriptorReference - -_T = TypeVar("_T", bound=Any) -_T1 = TypeVar("_T1", bound=Any) -_T2 = TypeVar("_T2", bound=Any) - -_PT = TypeVar("_PT", bound=Any) - -_PT2 = TypeVar("_PT2", bound=Any) - - -_RelationshipArgumentType = Union[ - str, - Type[_T], - Callable[[], Type[_T]], - "Mapper[_T]", - "AliasedClass[_T]", - Callable[[], "Mapper[_T]"], - Callable[[], "AliasedClass[_T]"], -] - -_LazyLoadArgumentType = Literal[ - "select", - "joined", - "selectin", - "subquery", - "raise", - "raise_on_sql", - "noload", - "immediate", - "write_only", - "dynamic", - True, - False, - None, -] - - -_RelationshipJoinConditionArgument = Union[ - str, _ColumnExpressionArgument[bool] -] -_RelationshipSecondaryArgument = Union[ - "FromClause", str, Callable[[], "FromClause"] -] -_ORMOrderByArgument = Union[ - Literal[False], - str, - _ColumnExpressionArgument[Any], - Callable[[], _ColumnExpressionArgument[Any]], - Callable[[], Iterable[_ColumnExpressionArgument[Any]]], - Iterable[Union[str, _ColumnExpressionArgument[Any]]], -] -ORMBackrefArgument = Union[str, Tuple[str, Dict[str, Any]]] - -_ORMColCollectionElement = Union[ - ColumnClause[Any], - _HasClauseElement[Any], - roles.DMLColumnRole, - "Mapped[Any]", -] -_ORMColCollectionArgument = Union[ - str, - Sequence[_ORMColCollectionElement], - Callable[[], Sequence[_ORMColCollectionElement]], - Callable[[], _ORMColCollectionElement], - _ORMColCollectionElement, -] - - -_CEA = TypeVar("_CEA", bound=_ColumnExpressionArgument[Any]) - -_CE = TypeVar("_CE", bound="ColumnElement[Any]") - - -_ColumnPairIterable = Iterable[Tuple[ColumnElement[Any], ColumnElement[Any]]] - -_ColumnPairs = Sequence[Tuple[ColumnElement[Any], ColumnElement[Any]]] - -_MutableColumnPairs = List[Tuple[ColumnElement[Any], ColumnElement[Any]]] - - -def remote(expr: _CEA) -> _CEA: - """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( # type: ignore - coercions.expect(roles.ColumnArgumentRole, expr), {"remote": True} - ) - - -def foreign(expr: _CEA) -> _CEA: - """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( # type: ignore - coercions.expect(roles.ColumnArgumentRole, expr), {"foreign": True} - ) - - -@dataclasses.dataclass -class _RelationshipArg(Generic[_T1, _T2]): - """stores a user-defined parameter value that must be resolved and - parsed later at mapper configuration time. - - """ - - __slots__ = "name", "argument", "resolved" - name: str - argument: _T1 - resolved: Optional[_T2] - - def _is_populated(self) -> bool: - return self.argument is not None - - def _resolve_against_registry( - self, clsregistry_resolver: Callable[[str, bool], _class_resolver] - ) -> None: - attr_value = self.argument - - if isinstance(attr_value, str): - self.resolved = clsregistry_resolver( - attr_value, self.name == "secondary" - )() - elif callable(attr_value) and not _is_mapped_class(attr_value): - self.resolved = attr_value() - else: - self.resolved = attr_value - - -_RelationshipOrderByArg = Union[Literal[False], Tuple[ColumnElement[Any], ...]] - - -class _RelationshipArgs(NamedTuple): - """stores user-passed parameters that are resolved at mapper configuration - time. - - """ - - secondary: _RelationshipArg[ - Optional[_RelationshipSecondaryArgument], - Optional[FromClause], - ] - primaryjoin: _RelationshipArg[ - Optional[_RelationshipJoinConditionArgument], - Optional[ColumnElement[Any]], - ] - secondaryjoin: _RelationshipArg[ - Optional[_RelationshipJoinConditionArgument], - Optional[ColumnElement[Any]], - ] - order_by: _RelationshipArg[_ORMOrderByArgument, _RelationshipOrderByArg] - foreign_keys: _RelationshipArg[ - Optional[_ORMColCollectionArgument], Set[ColumnElement[Any]] - ] - remote_side: _RelationshipArg[ - Optional[_ORMColCollectionArgument], Set[ColumnElement[Any]] - ] - - -@log.class_logger -class RelationshipProperty( - _IntrospectsAnnotations, StrategizedProperty[_T], log.Identified -): - """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 = strategy_options._RELATIONSHIP_TOKEN - inherit_cache = True - """:meta private:""" - - _links_to_entity = True - _is_relationship = True - - _overlaps: Sequence[str] - - _lazy_strategy: LazyLoader - - _persistence_only = dict( - passive_deletes=False, - passive_updates=True, - enable_typechecks=True, - active_history=False, - cascade_backrefs=False, - ) - - _dependency_processor: Optional[DependencyProcessor] = None - - primaryjoin: ColumnElement[bool] - secondaryjoin: Optional[ColumnElement[bool]] - secondary: Optional[FromClause] - _join_condition: JoinCondition - order_by: _RelationshipOrderByArg - - _user_defined_foreign_keys: Set[ColumnElement[Any]] - _calculated_foreign_keys: Set[ColumnElement[Any]] - - remote_side: Set[ColumnElement[Any]] - local_columns: Set[ColumnElement[Any]] - - synchronize_pairs: _ColumnPairs - secondary_synchronize_pairs: Optional[_ColumnPairs] - - local_remote_pairs: Optional[_ColumnPairs] - - direction: RelationshipDirection - - _init_args: _RelationshipArgs - - def __init__( - self, - argument: Optional[_RelationshipArgumentType[_T]] = None, - secondary: Optional[_RelationshipSecondaryArgument] = None, - *, - uselist: Optional[bool] = None, - collection_class: Optional[ - Union[Type[Collection[Any]], Callable[[], Collection[Any]]] - ] = None, - primaryjoin: Optional[_RelationshipJoinConditionArgument] = None, - secondaryjoin: Optional[_RelationshipJoinConditionArgument] = None, - back_populates: Optional[str] = None, - order_by: _ORMOrderByArgument = False, - backref: Optional[ORMBackrefArgument] = None, - overlaps: Optional[str] = None, - post_update: bool = False, - cascade: str = "save-update, merge", - viewonly: bool = False, - attribute_options: Optional[_AttributeOptions] = None, - lazy: _LazyLoadArgumentType = "select", - passive_deletes: Union[Literal["all"], bool] = False, - passive_updates: bool = True, - active_history: bool = False, - enable_typechecks: bool = True, - foreign_keys: Optional[_ORMColCollectionArgument] = None, - remote_side: Optional[_ORMColCollectionArgument] = None, - join_depth: Optional[int] = None, - comparator_factory: Optional[ - Type[RelationshipProperty.Comparator[Any]] - ] = None, - single_parent: bool = False, - innerjoin: bool = False, - distinct_target_key: Optional[bool] = None, - load_on_pending: bool = False, - query_class: Optional[Type[Query[Any]]] = None, - info: Optional[_InfoType] = None, - omit_join: Literal[None, False] = None, - sync_backref: Optional[bool] = None, - doc: Optional[str] = None, - bake_queries: Literal[True] = True, - cascade_backrefs: Literal[False] = False, - _local_remote_pairs: Optional[_ColumnPairs] = None, - _legacy_inactive_history_style: bool = False, - ): - super().__init__(attribute_options=attribute_options) - - self.uselist = uselist - self.argument = argument - - self._init_args = _RelationshipArgs( - _RelationshipArg("secondary", secondary, None), - _RelationshipArg("primaryjoin", primaryjoin, None), - _RelationshipArg("secondaryjoin", secondaryjoin, None), - _RelationshipArg("order_by", order_by, None), - _RelationshipArg("foreign_keys", foreign_keys, None), - _RelationshipArg("remote_side", remote_side, None), - ) - - self.post_update = post_update - 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.collection_class = collection_class - self.passive_deletes = passive_deletes - - if cascade_backrefs: - raise sa_exc.ArgumentError( - "The 'cascade_backrefs' parameter passed to " - "relationship() may only be set to False." - ) - - self.passive_updates = passive_updates - 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.load_on_pending = load_on_pending - self.comparator_factory = ( - comparator_factory or RelationshipProperty.Comparator - ) - util.set_creation_order(self) - - if info is not None: - self.info.update(info) - - self.strategy_key = (("lazy", self.lazy),) - - self._reverse_property: Set[RelationshipProperty[Any]] = set() - - if overlaps: - self._overlaps = set(re.split(r"\s*,\s*", overlaps)) # type: ignore # noqa: E501 - else: - self._overlaps = () - - # mypy ignoring the @property setter - self.cascade = cascade # type: ignore - - 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: Any) -> None: - 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: Mapper[Any]) -> None: - attributes.register_descriptor( - mapper.class_, - self.key, - comparator=self.comparator_factory(self, mapper), - parententity=mapper, - doc=self.doc, - ) - - class Comparator(util.MemoizedSlots, PropComparator[_PT]): - """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` - - """ - - __slots__ = ( - "entity", - "mapper", - "property", - "_of_type", - "_extra_criteria", - ) - - prop: RODescriptorReference[RelationshipProperty[_PT]] - _of_type: Optional[_EntityType[_PT]] - - def __init__( - self, - prop: RelationshipProperty[_PT], - parentmapper: _InternalEntityType[Any], - adapt_to_entity: Optional[AliasedInsp[Any]] = None, - of_type: Optional[_EntityType[_PT]] = None, - extra_criteria: Tuple[ColumnElement[bool], ...] = (), - ): - """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 - else: - self._of_type = None - self._extra_criteria = extra_criteria - - def adapt_to_entity( - self, adapt_to_entity: AliasedInsp[Any] - ) -> RelationshipProperty.Comparator[Any]: - return self.__class__( - self.prop, - self._parententity, - adapt_to_entity=adapt_to_entity, - of_type=self._of_type, - ) - - entity: _InternalEntityType[_PT] - """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`. - - """ - - mapper: Mapper[_PT] - """The target :class:`_orm.Mapper` referred to by this - :class:`.RelationshipProperty.Comparator`. - - This is the "target" or "remote" side of the - :func:`_orm.relationship`. - - """ - - def _memoized_attr_entity(self) -> _InternalEntityType[_PT]: - if self._of_type: - return inspect(self._of_type) # type: ignore - else: - return self.prop.entity - - def _memoized_attr_mapper(self) -> Mapper[_PT]: - return self.entity.mapper - - def _source_selectable(self) -> FromClause: - if self._adapt_to_entity: - return self._adapt_to_entity.selectable - else: - return self.property.parent._with_polymorphic_selectable - - def __clause_element__(self) -> ColumnElement[bool]: - 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.prop._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, class_: _EntityType[Any]) -> PropComparator[_PT]: - r"""Redefine this object in terms of a polymorphic subclass. - - See :meth:`.PropComparator.of_type` for an example. - - - """ - return RelationshipProperty.Comparator( - self.prop, - self._parententity, - adapt_to_entity=self._adapt_to_entity, - of_type=class_, - extra_criteria=self._extra_criteria, - ) - - def and_( - self, *criteria: _ColumnExpressionArgument[bool] - ) -> PropComparator[Any]: - """Add AND criteria. - - See :meth:`.PropComparator.and_` for an example. - - .. versionadded:: 1.4 - - """ - exprs = tuple( - coercions.expect(roles.WhereHavingRole, clause) - for clause in util.coerce_generator_arg(criteria) - ) - - return RelationshipProperty.Comparator( - self.prop, - self._parententity, - adapt_to_entity=self._adapt_to_entity, - of_type=self._of_type, - extra_criteria=self._extra_criteria + exprs, - ) - - def in_(self, other: Any) -> NoReturn: - """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." - ) - - # https://github.com/python/mypy/issues/4266 - __hash__ = None # type: ignore - - def __eq__(self, other: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501 - """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:`~.Relationship.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:`~.Relationship.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 other is None or isinstance(other, 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: Optional[_ColumnExpressionArgument[bool]] = None, - **kwargs: Any, - ) -> Exists: - where_criteria = ( - coercions.expect(roles.WhereHavingRole, criterion) - if criterion is not None - else None - ) - - if getattr(self, "_of_type", None): - info: Optional[_InternalEntityType[Any]] = inspect( - self._of_type - ) - assert info is not None - 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 where_criteria is not None: - where_criteria = single_crit & where_criteria - else: - where_criteria = 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 where_criteria is None: - where_criteria = crit - else: - where_criteria = where_criteria & 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 ( - where_criteria is not None - and target_adapter - and not is_aliased_class - ): - # limit this adapter to annotated only? - where_criteria = target_adapter.traverse(where_criteria) - - # 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 where_criteria is not None: - where_criteria = where_criteria._annotate( - {"no_replacement_traverse": True} - ) - - crit = j & sql.True_._ifnone(where_criteria) - - 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: Optional[_ColumnExpressionArgument[bool]] = None, - **kwargs: Any, - ) -> ColumnElement[bool]: - """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:`~.Relationship.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:`~.Relationship.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:`~.Relationship.Comparator.any` is only - valid for collections, i.e. a :func:`_orm.relationship` - that has ``uselist=True``. For scalar references, - use :meth:`~.Relationship.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: Optional[_ColumnExpressionArgument[bool]] = None, - **kwargs: Any, - ) -> ColumnElement[bool]: - """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:`~.Relationship.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:`~.Relationship.Comparator.has` is only - valid for scalar references, i.e. a :func:`_orm.relationship` - that has ``uselist=False``. For collection references, - use :meth:`~.Relationship.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: _ColumnExpressionArgument[Any], **kwargs: Any - ) -> ColumnElement[bool]: - """Return a simple expression that tests a collection for - containment of a particular item. - - :meth:`~.Relationship.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:`~.Relationship.Comparator.contains` is - very useful when used with simple one-to-many - operations. - - For many-to-many operations, the behavior of - :meth:`~.Relationship.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:`~.Relationship.Comparator.contains` - will **not** work with many-to-many collections when - used in queries that move beyond simple AND - conjunctions, such as multiple - :meth:`~.Relationship.Comparator.contains` - expressions joined by OR. In such cases subqueries or - explicit "outer joins" will need to be used instead. - See :meth:`~.Relationship.Comparator.any` for - a less-performant alternative using EXISTS, or refer - to :meth:`_query.Query.outerjoin` - as well as :ref:`orm_queryguide_joins` - for more details on constructing outer joins. - - kwargs may be ignored by this operator but are required for API - conformance. - """ - if not self.prop.uselist: - raise sa_exc.InvalidRequestError( - "'contains' not implemented for scalar " - "attributes. Use ==" - ) - - clause = self.prop._optimized_compare( - other, adapt_source=self.adapter - ) - - if self.prop.secondaryjoin is not None: - clause.negation_clause = self.__negated_contains_or_equals( - other - ) - - return clause - - def __negated_contains_or_equals( - self, other: Any - ) -> ColumnElement[bool]: - if self.prop.direction == MANYTOONE: - state = attributes.instance_state(other) - - def state_bindparam( - local_col: ColumnElement[Any], - state: InstanceState[Any], - remote_col: ColumnElement[Any], - ) -> BindParameter[Any]: - dict_ = state.dict - return sql.bindparam( - local_col.key, - type_=local_col.type, - unique=True, - callable_=self.prop._get_attr_w_warn_on_none( - self.prop.mapper, state, dict_, remote_col - ), - ) - - def adapt(col: _CE) -> _CE: - 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: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501 - """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:`~.Relationship.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:`~.Relationship.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 other is None or isinstance(other, 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)) - - def _memoized_attr_property(self) -> RelationshipProperty[_PT]: - self.prop.parent._check_configure() - return self.prop - - def _with_parent( - self, - instance: object, - alias_secondary: bool = True, - from_entity: Optional[_EntityType[Any]] = None, - ) -> ColumnElement[bool]: - assert instance is not None - adapt_source: Optional[_CoreAdapterProto] = None - if from_entity is not None: - insp: Optional[_InternalEntityType[Any]] = inspect(from_entity) - assert insp is not None - if insp_is_aliased_class(insp): - 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: Any, - value_is_parent: bool = False, - adapt_source: Optional[_CoreAdapterProto] = None, - alias_secondary: bool = True, - ) -> ColumnElement[bool]: - 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: BindParameter[Any]) -> None: - 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: Mapper[Any], - state: InstanceState[Any], - dict_: _InstanceDict, - column: ColumnElement[Any], - ) -> Callable[[], Any]: - """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) - - lkv_fixed = state._last_known_values - - def _go() -> Any: - assert lkv_fixed is not None - last_known = to_return = lkv_fixed[prop.key] - existing_is_available = ( - last_known is not LoaderCallableStatus.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=( - PassiveFlag.PASSIVE_OFF - if state.persistent - else PassiveFlag.PASSIVE_NO_FETCH ^ PassiveFlag.INIT_OK - ), - ) - - if current_value is LoaderCallableStatus.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 LoaderCallableStatus.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: bool = False, - adapt_source: Optional[_CoreAdapterProto] = None, - ) -> ColumnElement[bool]: - 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) -> str: - return str(self.parent.class_.__name__) + "." + self.key - - def merge( - self, - session: Session, - source_state: InstanceState[Any], - source_dict: _InstanceDict, - dest_state: InstanceState[Any], - dest_dict: _InstanceDict, - load: bool, - _recursive: Dict[Any, object], - _resolve_conflict_map: Dict[_IdentityKeyType[Any], object], - ) -> None: - 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) - - assert is_has_collection_adapter(impl) - 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, passive=PassiveFlag.PASSIVE_MERGE - ) - - 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_impl = dest_state.get_impl(self.key) - assert is_has_collection_adapter(dest_impl) - dest_impl.set( - dest_state, - dest_dict, - dest_list, - _adapt=False, - passive=PassiveFlag.PASSIVE_MERGE, - ) - 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: InstanceState[_O], - dict_: _InstanceDict, - key: str, - passive: PassiveFlag = PassiveFlag.PASSIVE_OFF, - ) -> Sequence[Tuple[InstanceState[_O], _O]]: - """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 LoaderCallableStatus.PASSIVE_NO_RESULT or x is None: - return [] - elif is_has_collection_adapter(impl): - 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_: str, - state: InstanceState[Any], - dict_: _InstanceDict, - visited_states: Set[InstanceState[Any]], - halt_on: Optional[Callable[[InstanceState[Any]], bool]] = None, - ) -> Iterator[Tuple[Any, Mapper[Any], InstanceState[Any], _InstanceDict]]: - # assert type_ in self._cascade - - # only actively lazy load on the 'delete' cascade - if type_ != "delete" or self.passive_deletes: - passive = PassiveFlag.PASSIVE_NO_INITIALIZE - else: - passive = PassiveFlag.PASSIVE_OFF | PassiveFlag.NO_RAISE - - 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 - - assert instance_state is not None - 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) -> bool: - if self.viewonly: - return False - else: - return self.sync_backref is not False - - @staticmethod - def _check_sync_backref( - rel_a: RelationshipProperty[Any], rel_b: RelationshipProperty[Any] - ) -> None: - 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: str) -> None: - 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) - - other._setup_entity() - - 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 ( - other._configure_started - and self.direction in (ONETOMANY, MANYTOONE) - and self.direction == other.direction - ): - raise sa_exc.ArgumentError( - "%s and back-reference %s are " - "both of the same direction %r. Did you mean to " - "set remote_side on the many-to-one side ?" - % (other, self, self.direction) - ) - - @util.memoized_property - def entity(self) -> _InternalEntityType[_T]: - """Return the target mapped entity, which is an inspect() of the - class or aliased class that is referenced by this - :class:`.RelationshipProperty`. - - """ - self.parent._check_configure() - return self.entity - - @util.memoized_property - def mapper(self) -> Mapper[_T]: - """Return the targeted :class:`_orm.Mapper` for this - :class:`.RelationshipProperty`. - - """ - return self.entity.mapper - - def do_init(self) -> None: - self._check_conflicts() - self._process_dependent_arguments() - self._setup_entity() - 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().do_init() - self._lazy_strategy = cast( - "LazyLoader", self._get_strategy((("lazy", "select"),)) - ) - - def _setup_registry_dependencies(self) -> None: - self.parent.mapper.registry._set_depends_on( - self.entity.mapper.registry - ) - - def _process_dependent_arguments(self) -> None: - """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. - init_args = self._init_args - - for attr in ( - "order_by", - "primaryjoin", - "secondaryjoin", - "secondary", - "foreign_keys", - "remote_side", - ): - rel_arg = getattr(init_args, attr) - - rel_arg._resolve_against_registry(self._clsregistry_resolvers[1]) - - # remove "annotations" which are present if mapped class - # descriptors are used to create the join expression. - for attr in "primaryjoin", "secondaryjoin": - rel_arg = getattr(init_args, attr) - val = rel_arg.resolved - if val is not None: - rel_arg.resolved = _orm_deannotate( - coercions.expect( - roles.ColumnArgumentRole, val, argname=attr - ) - ) - - secondary = init_args.secondary.resolved - if secondary is not None and _is_mapped_class(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." % (secondary, self) - ) - - # ensure expressions in self.order_by, foreign_keys, - # remote_side are all columns, not strings. - if ( - init_args.order_by.resolved is not False - and init_args.order_by.resolved is not None - ): - self.order_by = tuple( - coercions.expect( - roles.ColumnArgumentRole, x, argname="order_by" - ) - for x in util.to_list(init_args.order_by.resolved) - ) - else: - self.order_by = False - - self._user_defined_foreign_keys = util.column_set( - coercions.expect( - roles.ColumnArgumentRole, x, argname="foreign_keys" - ) - for x in util.to_column_set(init_args.foreign_keys.resolved) - ) - - self.remote_side = util.column_set( - coercions.expect( - roles.ColumnArgumentRole, x, argname="remote_side" - ) - for x in util.to_column_set(init_args.remote_side.resolved) - ) - - def declarative_scan( - self, - decl_scan: _ClassScanMapperConfig, - registry: _RegistryType, - cls: Type[Any], - originating_module: Optional[str], - key: str, - mapped_container: Optional[Type[Mapped[Any]]], - annotation: Optional[_AnnotationScanType], - extracted_mapped_annotation: Optional[_AnnotationScanType], - is_dataclass_field: bool, - ) -> None: - argument = extracted_mapped_annotation - - if extracted_mapped_annotation is None: - if self.argument is None: - self._raise_for_required(key, cls) - else: - return - - argument = extracted_mapped_annotation - assert originating_module is not None - - if mapped_container is not None: - is_write_only = issubclass(mapped_container, WriteOnlyMapped) - is_dynamic = issubclass(mapped_container, DynamicMapped) - if is_write_only: - self.lazy = "write_only" - self.strategy_key = (("lazy", self.lazy),) - elif is_dynamic: - self.lazy = "dynamic" - self.strategy_key = (("lazy", self.lazy),) - else: - is_write_only = is_dynamic = False - - argument = de_optionalize_union_types(argument) - - if hasattr(argument, "__origin__"): - arg_origin = argument.__origin__ - if isinstance(arg_origin, type) and issubclass( - arg_origin, abc.Collection - ): - if self.collection_class is None: - if _py_inspect.isabstract(arg_origin): - raise sa_exc.ArgumentError( - f"Collection annotation type {arg_origin} cannot " - "be instantiated; please provide an explicit " - "'collection_class' parameter " - "(e.g. list, set, etc.) to the " - "relationship() function to accompany this " - "annotation" - ) - - self.collection_class = arg_origin - - elif not is_write_only and not is_dynamic: - self.uselist = False - - if argument.__args__: # type: ignore - if isinstance(arg_origin, type) and issubclass( - arg_origin, typing.Mapping - ): - type_arg = argument.__args__[-1] # type: ignore - else: - type_arg = argument.__args__[0] # type: ignore - if hasattr(type_arg, "__forward_arg__"): - str_argument = type_arg.__forward_arg__ - - argument = resolve_name_to_real_class_name( - str_argument, originating_module - ) - else: - argument = type_arg - else: - raise sa_exc.ArgumentError( - f"Generic alias {argument} requires an argument" - ) - elif hasattr(argument, "__forward_arg__"): - argument = argument.__forward_arg__ - - argument = resolve_name_to_real_class_name( - argument, originating_module - ) - - if ( - self.collection_class is None - and not is_write_only - and not is_dynamic - ): - self.uselist = False - - # ticket #8759 - # if a lead argument was given to relationship(), like - # `relationship("B")`, use that, don't replace it with class we - # found in the annotation. The declarative_scan() method call here is - # still useful, as we continue to derive collection type and do - # checking of the annotation in any case. - if self.argument is None: - self.argument = cast("_RelationshipArgumentType[_T]", argument) - - @util.preload_module("sqlalchemy.orm.mapper") - def _setup_entity(self, __argument: Any = None) -> None: - if "entity" in self.__dict__: - return - - mapperlib = util.preloaded.orm_mapper - - if __argument: - argument = __argument - else: - argument = self.argument - - resolved_argument: _ExternalEntityType[Any] - - if isinstance(argument, str): - # we might want to cleanup clsregistry API to make this - # more straightforward - resolved_argument = cast( - "_ExternalEntityType[Any]", - self._clsregistry_resolve_name(argument)(), - ) - elif callable(argument) and not isinstance( - argument, (type, mapperlib.Mapper) - ): - resolved_argument = argument() - else: - resolved_argument = argument - - entity: _InternalEntityType[Any] - - if isinstance(resolved_argument, type): - entity = class_mapper(resolved_argument, configure=False) - else: - try: - entity = inspect(resolved_argument) - except sa_exc.NoInspectionAvailable: - entity = None # type: ignore - - if not hasattr(entity, "mapper"): - raise sa_exc.ArgumentError( - "relationship '%s' expects " - "a class or a mapper argument (received: %s)" - % (self.key, type(resolved_argument)) - ) - - self.entity = entity - self.target = self.entity.persist_selectable - - def _setup_join_conditions(self) -> None: - 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._init_args.primaryjoin.resolved, - secondary=self._init_args.secondary.resolved, - secondaryjoin=self._init_args.secondaryjoin.resolved, - 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.secondary = jc.secondary - 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, - ) -> Callable[[str, bool], _class_resolver]: - return self._clsregistry_resolvers[1] - - @property - def _clsregistry_resolve_name( - self, - ) -> Callable[[str], Callable[[], Union[Type[Any], Table, _ModNS]]]: - return self._clsregistry_resolvers[0] - - @util.memoized_property - @util.preload_module("sqlalchemy.orm.clsregistry") - def _clsregistry_resolvers( - self, - ) -> Tuple[ - Callable[[str], Callable[[], Union[Type[Any], Table, _ModNS]]], - Callable[[str, bool], _class_resolver], - ]: - _resolver = util.preloaded.orm_clsregistry._resolver - - return _resolver(self.parent.class_, self) - - def _check_conflicts(self) -> None: - """Test that this relationship is legal, warn about - inheritance conflicts.""" - if self.parent.non_primary and not 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) -> CascadeOptions: - """Return the current cascade setting for this - :class:`.RelationshipProperty`. - """ - return self._cascade - - @cascade.setter - def cascade(self, cascade: Union[str, CascadeOptions]) -> None: - self._set_cascade(cascade) - - def _set_cascade(self, cascade_arg: Union[str, CascadeOptions]) -> None: - cascade = CascadeOptions(cascade_arg) - - if self.viewonly: - cascade = CascadeOptions( - cascade.intersection(CascadeOptions._viewonly_cascades) - ) - - 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: CascadeOptions) -> None: - 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 referenced 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: Mapper[Any]) -> bool: - """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: ColumnElement[Any]) -> bool: - """Return True if all columns in the given collection are - mapped by the tables referenced by this :class:`.RelationshipProperty`. - - """ - - secondary = self._init_args.secondary.resolved - for c in cols: - if secondary is not None and 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) -> None: - """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: - kwargs: Dict[str, Any] - if isinstance(self.backref, str): - 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, - primaryjoin=pj, - secondaryjoin=sj, - foreign_keys=foreign_keys, - back_populates=self.key, - **kwargs, - ) - mapper._configure_property( - backref_key, relationship, warn_for_existing=True - ) - - if self.back_populates: - self._add_reverse_property(self.back_populates) - - @util.preload_module("sqlalchemy.orm.dependency") - def _post_init(self) -> None: - dependency = util.preloaded.orm_dependency - - if self.uselist is None: - self.uselist = self.direction is not MANYTOONE - if not self.viewonly: - self._dependency_processor = ( # type: ignore - dependency.DependencyProcessor.from_relationship - )(self) - - @util.memoized_property - def _use_get(self) -> bool: - """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) -> bool: - return self.mapper.common_parent(self.parent) - - def _create_joins( - self, - source_polymorphic: bool = False, - source_selectable: Optional[FromClause] = None, - dest_selectable: Optional[FromClause] = None, - of_type_entity: Optional[_InternalEntityType[Any]] = None, - alias_secondary: bool = False, - extra_criteria: Tuple[ColumnElement[bool], ...] = (), - ) -> Tuple[ - ColumnElement[bool], - Optional[ColumnElement[bool]], - FromClause, - FromClause, - Optional[FromClause], - Optional[ClauseAdapter], - ]: - 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: _CE, annotations: _AnnotationDict) -> _CE: - def clone(elem: _CE) -> _CE: - if isinstance(elem, expression.ColumnClause): - elem = elem._annotate(annotations.copy()) # type: ignore - elem._copy_internals(clone=clone) - return elem - - if element is not None: - element = clone(element) - clone = None # type: ignore # remove gc cycles - return element - - -class JoinCondition: - primaryjoin_initial: Optional[ColumnElement[bool]] - primaryjoin: ColumnElement[bool] - secondaryjoin: Optional[ColumnElement[bool]] - secondary: Optional[FromClause] - prop: RelationshipProperty[Any] - - synchronize_pairs: _ColumnPairs - secondary_synchronize_pairs: _ColumnPairs - direction: RelationshipDirection - - parent_persist_selectable: FromClause - child_persist_selectable: FromClause - parent_local_selectable: FromClause - child_local_selectable: FromClause - - _local_remote_pairs: Optional[_ColumnPairs] - - def __init__( - self, - parent_persist_selectable: FromClause, - child_persist_selectable: FromClause, - parent_local_selectable: FromClause, - child_local_selectable: FromClause, - *, - primaryjoin: Optional[ColumnElement[bool]] = None, - secondary: Optional[FromClause] = None, - secondaryjoin: Optional[ColumnElement[bool]] = None, - parent_equivalents: Optional[_EquivalentColumnMap] = None, - child_equivalents: Optional[_EquivalentColumnMap] = None, - consider_as_foreign_keys: Any = None, - local_remote_pairs: Optional[_ColumnPairs] = None, - remote_side: Any = None, - self_referential: Any = False, - prop: RelationshipProperty[Any], - support_sync: bool = True, - can_be_synced_fn: Callable[..., bool] = 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_initial = 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() - assert self.primaryjoin is not None - - 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) -> None: - 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) -> None: - """remove the parententity annotation from our join conditions which - can leak in here based on some declarative patterns and maybe others. - - "parentmapper" is relied upon both by the ORM evaluator as well as - the 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) -> None: - """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_initial 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: - self.primaryjoin = self.primaryjoin_initial - else: - if self.primaryjoin_initial 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, - ) - else: - self.primaryjoin = self.primaryjoin_initial - except sa_exc.NoForeignKeysError as nfe: - if self.secondary is not None: - raise sa_exc.NoForeignKeysError( - "Could not determine join " - "condition between parent/child tables on " - "relationship %s - there are no foreign keys " - "linking these tables via secondary table '%s'. " - "Ensure that referencing columns are associated " - "with a ForeignKey or ForeignKeyConstraint, or " - "specify 'primaryjoin' and 'secondaryjoin' " - "expressions." % (self.prop, self.secondary) - ) from nfe - else: - raise sa_exc.NoForeignKeysError( - "Could not determine join " - "condition between parent/child tables on " - "relationship %s - there are no foreign keys " - "linking these tables. " - "Ensure that referencing columns are associated " - "with a ForeignKey or ForeignKeyConstraint, or " - "specify a 'primaryjoin' expression." % self.prop - ) from nfe - except sa_exc.AmbiguousForeignKeysError as afe: - if self.secondary is not None: - raise sa_exc.AmbiguousForeignKeysError( - "Could not determine join " - "condition between parent/child tables on " - "relationship %s - there are multiple foreign key " - "paths linking the tables via secondary table '%s'. " - "Specify the 'foreign_keys' " - "argument, providing a list of those columns which " - "should be counted as containing a foreign key " - "reference from the secondary table to each of the " - "parent and child tables." % (self.prop, self.secondary) - ) from afe - else: - raise sa_exc.AmbiguousForeignKeysError( - "Could not determine join " - "condition between parent/child tables on " - "relationship %s - there are multiple foreign key " - "paths linking the tables. Specify the " - "'foreign_keys' argument, providing a list of those " - "columns which should be counted as containing a " - "foreign key reference to the parent table." % self.prop - ) from afe - - @property - def primaryjoin_minus_local(self) -> ColumnElement[bool]: - return _deep_deannotate(self.primaryjoin, values=("local", "remote")) - - @property - def secondaryjoin_minus_local(self) -> ColumnElement[bool]: - assert self.secondaryjoin is not None - return _deep_deannotate(self.secondaryjoin, values=("local", "remote")) - - @util.memoized_property - def primaryjoin_reverse_remote(self) -> ColumnElement[bool]: - """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: _CE, **kw: Any) -> Optional[_CE]: - 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 None - - 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: ClauseElement, annotation: str) -> bool: - for col in visitors.iterate(clause, {}): - if annotation in col._annotations: - return True - else: - return False - - @util.memoized_property - def _has_foreign_annotations(self) -> bool: - return self._has_annotation(self.primaryjoin, "foreign") - - @util.memoized_property - def _has_remote_annotations(self) -> bool: - return self._has_annotation(self.primaryjoin, "remote") - - def _annotate_fks(self) -> None: - """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) -> None: - def check_fk(element: _CE, **kw: Any) -> Optional[_CE]: - if element in self.consider_as_foreign_keys: - return element._annotate({"foreign": True}) - return None - - 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) -> None: - if self.secondary is not None: - secondarycols = util.column_set(self.secondary.c) - else: - secondarycols = set() - - def is_foreign( - a: ColumnElement[Any], b: ColumnElement[Any] - ) -> Optional[ColumnElement[Any]]: - 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 - - return None - - def visit_binary(binary: BinaryExpression[Any]) -> None: - 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) -> bool: - """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: BinaryExpression[Any]) -> None: - nonlocal result - 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 = True - - visitors.traverse(self.primaryjoin, {}, {"binary": visit_binary}) - return result - - def _tables_overlap(self) -> bool: - """Return True if parent/child tables have some overlap.""" - - return selectables_overlap( - self.parent_persist_selectable, self.child_persist_selectable - ) - - def _annotate_remote(self) -> None: - """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) -> None: - """annotate 'remote' in primaryjoin, secondaryjoin - when 'secondary' is present. - - """ - - assert self.secondary is not None - fixed_secondary = self.secondary - - def repl(element: _CE, **kw: Any) -> Optional[_CE]: - if fixed_secondary.c.contains_column(element): - return element._annotate({"remote": True}) - return None - - self.primaryjoin = visitors.replacement_traverse( - self.primaryjoin, {}, repl - ) - - assert self.secondaryjoin is not None - self.secondaryjoin = visitors.replacement_traverse( - self.secondaryjoin, {}, repl - ) - - def _annotate_selfref( - self, fn: Callable[[ColumnElement[Any]], bool], remote_side_given: bool - ) -> None: - """annotate 'remote' in primaryjoin, secondaryjoin - when the relationship is detected as self-referential. - - """ - - def visit_binary(binary: BinaryExpression[Any]) -> None: - 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) -> None: - """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: _CE, **kw: Any) -> Optional[_CE]: - # use set() to avoid generating ``__eq__()`` expressions - # against each element - if element in set(remote_side): - return element._annotate({"remote": True}) - return None - - self.primaryjoin = visitors.replacement_traverse( - self.primaryjoin, {}, repl - ) - - def _annotate_remote_with_overlap(self) -> None: - """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: BinaryExpression[Any]) -> None: - 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: ColumnElement[Any], right: ColumnElement[Any] - ) -> Tuple[ColumnElement[Any], ColumnElement[Any]]: - 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) -> None: - """annotate 'remote' in primaryjoin, secondaryjoin - when the parent/child tables are entirely - separate. - - """ - - def repl(element: _CE, **kw: Any) -> Optional[_CE]: - 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}) - return None - - self.primaryjoin = visitors.replacement_traverse( - self.primaryjoin, {}, repl - ) - - def _warn_non_column_elements(self) -> None: - 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) -> None: - """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_(element: _CE, **kw: Any) -> Optional[_CE]: - if "remote" not in element._annotations and element in local_side: - return element._annotate({"local": True}) - return None - - self.primaryjoin = visitors.replacement_traverse( - self.primaryjoin, {}, locals_ - ) - - def _annotate_parentmapper(self) -> None: - def parentmappers_(element: _CE, **kw: Any) -> Optional[_CE]: - if "remote" in element._annotations: - return element._annotate({"parentmapper": self.prop.mapper}) - elif "local" in element._annotations: - return element._annotate({"parentmapper": self.prop.parent}) - return None - - self.primaryjoin = visitors.replacement_traverse( - self.primaryjoin, {}, parentmappers_ - ) - - def _check_remote_side(self) -> None: - 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,) - ) - else: - not_target = util.column_set( - self.parent_persist_selectable.c - ).difference(self.child_persist_selectable.c) - - for _, rmt in self.local_remote_pairs: - if rmt in not_target: - util.warn( - "Expression %s is marked as 'remote', but these " - "column(s) are local to the local side. The " - "remote() annotation is needed only for a " - "self-referential relationship where both sides " - "of the relationship refer to the same tables." - % (rmt,) - ) - - def _check_foreign_cols( - self, join_condition: ColumnElement[bool], primary: bool - ) -> None: - """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) -> None: - """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 = { - 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: _ColumnPairIterable - ) -> _MutableColumnPairs: - """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) -> None: - sync_pairs: _MutableColumnPairs = [] - lrp: util.OrderedSet[Tuple[ColumnElement[Any], ColumnElement[Any]]] = ( - util.OrderedSet([]) - ) - secondary_sync_pairs: _MutableColumnPairs = [] - - def go( - joincond: ColumnElement[bool], - collection: _MutableColumnPairs, - ) -> None: - def visit_binary( - binary: BinaryExpression[Any], - left: ColumnElement[Any], - right: ColumnElement[Any], - ) -> None: - 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[ - ColumnElement[Any], - weakref.WeakKeyDictionary[ - RelationshipProperty[Any], ColumnElement[Any] - ], - ] = weakref.WeakKeyDictionary() - - def _warn_for_conflicting_sync_targets(self) -> None: - 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) -> Set[ColumnElement[Any]]: - return self._gather_join_annotations("remote") - - @util.memoized_property - def local_columns(self) -> Set[ColumnElement[Any]]: - return self._gather_join_annotations("local") - - @util.memoized_property - def foreign_key_columns(self) -> Set[ColumnElement[Any]]: - return self._gather_join_annotations("foreign") - - def _gather_join_annotations( - self, annotation: str - ) -> Set[ColumnElement[Any]]: - 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: ColumnElement[Any], *annotation: Iterable[str] - ) -> Set[ColumnElement[Any]]: - annotation_set = set(annotation) - return { - cast(ColumnElement[Any], col) - for col in visitors.iterate(clause, {}) - if annotation_set.issubset(col._annotations) - } - - @util.memoized_property - def _secondary_lineage_set(self) -> FrozenSet[ColumnElement[Any]]: - if self.secondary is not None: - return frozenset( - itertools.chain(*[c.proxy_set for c in self.secondary.c]) - ) - else: - return util.EMPTY_SET - - def join_targets( - self, - source_selectable: Optional[FromClause], - dest_selectable: FromClause, - aliased: bool, - single_crit: Optional[ColumnElement[bool]] = None, - extra_criteria: Tuple[ColumnElement[bool], ...] = (), - ) -> Tuple[ - ColumnElement[bool], - Optional[ColumnElement[bool]], - Optional[FromClause], - Optional[ClauseAdapter], - FromClause, - ]: - """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: - - def mark_exclude_cols( - elem: SupportsAnnotations, annotations: _AnnotationDict - ) -> SupportsAnnotations: - """note unrelated columns in the "extra criteria" as either - should be adapted or not adapted, even though they are not - part of our "local" or "remote" side. - - see #9779 for this case, as well as #11010 for a follow up - - """ - - parentmapper_for_element = elem._annotations.get( - "parentmapper", None - ) - - if ( - parentmapper_for_element is not self.prop.parent - and parentmapper_for_element is not self.prop.mapper - and elem not in self._secondary_lineage_set - ): - return _safe_annotate(elem, annotations) - else: - return elem - - extra_criteria = tuple( - _deep_annotate( - elem, - {"should_not_adapt": True}, - annotate_callable=mark_exclude_cols, - ) - for elem in 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=_local_col_exclude, - ) - secondary_aliasizer = ClauseAdapter( - dest_selectable, equivalents=self.child_equivalents - ).chain(primary_aliasizer) - if source_selectable is not None: - primary_aliasizer = ClauseAdapter( - secondary, - exclude_fn=_local_col_exclude, - ).chain( - ClauseAdapter( - source_selectable, - equivalents=self.parent_equivalents, - ) - ) - - secondaryjoin = secondary_aliasizer.traverse(secondaryjoin) - else: - primary_aliasizer = ClauseAdapter( - dest_selectable, - exclude_fn=_local_col_exclude, - equivalents=self.child_equivalents, - ) - if source_selectable is not None: - primary_aliasizer.chain( - ClauseAdapter( - source_selectable, - exclude_fn=_remote_col_exclude, - 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: bool = False) -> Tuple[ - ColumnElement[bool], - Dict[str, ColumnElement[Any]], - Dict[ColumnElement[Any], ColumnElement[Any]], - ]: - binds: Dict[ColumnElement[Any], BindParameter[Any]] = {} - equated_columns: Dict[ColumnElement[Any], ColumnElement[Any]] = {} - - 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( - element: ColumnElement[Any], **kw: Any - ) -> Optional[BindParameter[Any]]: - if ( - (not reverse_direction and "local" in element._annotations) - or reverse_direction - and ( - (has_secondary and element in lookup) - or (not has_secondary and "remote" in element._annotations) - ) - ): - if element not in binds: - binds[element] = sql.bindparam( - None, None, type_=element.type, unique=True - ) - return binds[element] - 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: - """Serializable object that tests for names in c._annotations. - - TODO: does this need to be serializable anymore? can we find what the - use case was for that? - - """ - - __slots__ = ("names",) - - def __init__(self, *names: str): - self.names = frozenset(names) - - def __call__(self, c: ClauseElement) -> bool: - return bool(self.names.intersection(c._annotations)) - - -_local_col_exclude = _ColInAnnotations("local", "should_not_adapt") -_remote_col_exclude = _ColInAnnotations("remote", "should_not_adapt") - - -class Relationship( - RelationshipProperty[_T], - _DeclarativeMapped[_T], -): - """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` - - .. versionchanged:: 2.0 Added :class:`_orm.Relationship` as a Declarative - compatible subclass for :class:`_orm.RelationshipProperty`. - - """ - - inherit_cache = True - """:meta private:""" - - -class _RelationshipDeclared( # type: ignore[misc] - Relationship[_T], - WriteOnlyMapped[_T], # not compatible with Mapped[_T] - DynamicMapped[_T], # not compatible with Mapped[_T] -): - """Relationship subclass used implicitly for declarative mapping.""" - - inherit_cache = True - """:meta private:""" - - @classmethod - def _mapper_property_name(cls) -> str: - return "Relationship" |