summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm/relationships.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/orm/relationships.py')
-rw-r--r--lib/sqlalchemy/orm/relationships.py114
1 files changed, 73 insertions, 41 deletions
diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py
index 58c7c4efd..66021c9c2 100644
--- a/lib/sqlalchemy/orm/relationships.py
+++ b/lib/sqlalchemy/orm/relationships.py
@@ -21,7 +21,10 @@ import re
import typing
from typing import Any
from typing import Callable
+from typing import Dict
from typing import Optional
+from typing import Sequence
+from typing import Tuple
from typing import Type
from typing import TypeVar
from typing import Union
@@ -30,6 +33,7 @@ import weakref
from . import attributes
from . import strategy_options
from .base import _is_mapped_class
+from .base import class_mapper
from .base import state_str
from .interfaces import _IntrospectsAnnotations
from .interfaces import MANYTOMANY
@@ -53,7 +57,9 @@ from ..sql import expression
from ..sql import operators
from ..sql import roles
from ..sql import visitors
-from ..sql.elements import SQLCoreOperations
+from ..sql._typing import _ColumnExpressionArgument
+from ..sql._typing import _HasClauseElement
+from ..sql.elements import ColumnClause
from ..sql.util import _deep_deannotate
from ..sql.util import _shallow_annotate
from ..sql.util import adapt_criterion_to_null
@@ -61,11 +67,14 @@ 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 Literal
if typing.TYPE_CHECKING:
+ from ._typing import _EntityType
from .mapper import Mapper
from .util import AliasedClass
from .util import AliasedInsp
+ from ..sql.elements import ColumnElement
_T = TypeVar("_T", bound=Any)
_PT = TypeVar("_PT", bound=Any)
@@ -81,6 +90,34 @@ _RelationshipArgumentType = Union[
Callable[[], "AliasedClass[_T]"],
]
+_LazyLoadArgumentType = Literal[
+ "select",
+ "joined",
+ "selectin",
+ "subquery",
+ "raise",
+ "raise_on_sql",
+ "noload",
+ "immediate",
+ "dynamic",
+ True,
+ False,
+ None,
+]
+
+
+_RelationshipJoinConditionArgument = Union[
+ str, _ColumnExpressionArgument[bool]
+]
+_ORMOrderByArgument = Union[
+ Literal[False], str, _ColumnExpressionArgument[Any]
+]
+_ORMBackrefArgument = Union[str, Tuple[str, Dict[str, Any]]]
+_ORMColCollectionArgument = Union[
+ str,
+ Sequence[Union[ColumnClause[Any], _HasClauseElement, roles.DMLColumnRole]],
+]
+
def remote(expr):
"""Annotate a portion of a primaryjoin expression
@@ -144,6 +181,7 @@ class Relationship(
inherit_cache = True
_links_to_entity = True
+ _is_relationship = True
_persistence_only = dict(
passive_deletes=False,
@@ -159,38 +197,39 @@ class Relationship(
self,
argument: Optional[_RelationshipArgumentType[_T]] = None,
secondary=None,
+ *,
+ uselist=None,
+ collection_class=None,
primaryjoin=None,
secondaryjoin=None,
- foreign_keys=None,
- uselist=None,
+ back_populates=None,
order_by=False,
backref=None,
- back_populates=None,
+ cascade_backrefs=False,
overlaps=None,
post_update=False,
- cascade=False,
+ cascade="save-update, merge",
viewonly=False,
- lazy="select",
- collection_class=None,
- passive_deletes=_persistence_only["passive_deletes"],
- passive_updates=_persistence_only["passive_updates"],
+ lazy: _LazyLoadArgumentType = "select",
+ passive_deletes=False,
+ passive_updates=True,
+ active_history=False,
+ enable_typechecks=True,
+ foreign_keys=None,
remote_side=None,
- enable_typechecks=_persistence_only["enable_typechecks"],
join_depth=None,
comparator_factory=None,
single_parent=False,
innerjoin=False,
distinct_target_key=None,
- doc=None,
- active_history=_persistence_only["active_history"],
- cascade_backrefs=_persistence_only["cascade_backrefs"],
load_on_pending=False,
- bake_queries=True,
- _local_remote_pairs=None,
query_class=None,
info=None,
omit_join=None,
sync_backref=None,
+ doc=None,
+ bake_queries=True,
+ _local_remote_pairs=None,
_legacy_inactive_history_style=False,
):
super(Relationship, self).__init__()
@@ -250,7 +289,6 @@ class Relationship(
self.omit_join = omit_join
self.local_remote_pairs = _local_remote_pairs
- self.bake_queries = bake_queries
self.load_on_pending = load_on_pending
self.comparator_factory = comparator_factory or Relationship.Comparator
self.comparator = self.comparator_factory(self, None)
@@ -267,12 +305,7 @@ class Relationship(
else:
self._overlaps = ()
- if cascade is not False:
- self.cascade = cascade
- elif self.viewonly:
- self.cascade = "none"
- else:
- self.cascade = "save-update, merge"
+ self.cascade = cascade
self.order_by = order_by
@@ -539,9 +572,9 @@ class Relationship(
def _criterion_exists(
self,
- criterion: Optional[SQLCoreOperations[Any]] = None,
+ criterion: Optional[_ColumnExpressionArgument[bool]] = None,
**kwargs: Any,
- ) -> Exists[bool]:
+ ) -> Exists:
if getattr(self, "_of_type", None):
info = inspect(self._of_type)
target_mapper, to_selectable, is_aliased_class = (
@@ -898,7 +931,12 @@ class Relationship(
comparator: Comparator[_T]
- def _with_parent(self, instance, alias_secondary=True, from_entity=None):
+ 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 = None
if from_entity is not None:
@@ -1502,7 +1540,7 @@ class Relationship(
argument = argument
if isinstance(argument, type):
- entity = mapperlib.class_mapper(argument, configure=False)
+ entity = class_mapper(argument, configure=False)
else:
try:
entity = inspect(argument)
@@ -1568,7 +1606,7 @@ class Relationship(
"""Test that this relationship is legal, warn about
inheritance conflicts."""
mapperlib = util.preloaded.orm_mapper
- if self.parent.non_primary and not mapperlib.class_mapper(
+ if self.parent.non_primary and not class_mapper(
self.parent.class_, configure=False
).has_property(self.key):
raise sa_exc.ArgumentError(
@@ -1585,29 +1623,23 @@ class Relationship(
)
@property
- def cascade(self):
+ def cascade(self) -> CascadeOptions:
"""Return the current cascade setting for this
:class:`.Relationship`.
"""
return self._cascade
@cascade.setter
- def cascade(self, cascade):
+ def cascade(self, cascade: Union[str, CascadeOptions]):
self._set_cascade(cascade)
- def _set_cascade(self, cascade):
- cascade = CascadeOptions(cascade)
+ def _set_cascade(self, cascade_arg: Union[str, CascadeOptions]):
+ cascade = CascadeOptions(cascade_arg)
if self.viewonly:
- non_viewonly = set(cascade).difference(
- CascadeOptions._viewonly_cascades
+ cascade = CascadeOptions(
+ cascade.intersection(CascadeOptions._viewonly_cascades)
)
- if non_viewonly:
- raise sa_exc.ArgumentError(
- 'Cascade settings "%s" apply to persistence operations '
- "and should not be combined with a viewonly=True "
- "relationship." % (", ".join(sorted(non_viewonly)))
- )
if "mapper" in self.__dict__:
self._check_cascade_settings(cascade)
@@ -1754,8 +1786,8 @@ class Relationship(
relationship = Relationship(
parent,
self.secondary,
- pj,
- sj,
+ primaryjoin=pj,
+ secondaryjoin=sj,
foreign_keys=foreign_keys,
back_populates=self.key,
**kwargs,