main commit
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-10-16 16:30:25 +09:00
parent 91c7e04474
commit 537e7b363f
1146 changed files with 45926 additions and 77196 deletions

View File

@@ -1,5 +1,5 @@
# orm/relationships.py
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
# Copyright (C) 2005-2023 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
@@ -19,7 +19,6 @@ import collections
from collections import abc
import dataclasses
import inspect as _py_inspect
import itertools
import re
import typing
from typing import Any
@@ -27,7 +26,6 @@ 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
@@ -181,10 +179,7 @@ _ORMOrderByArgument = Union[
ORMBackrefArgument = Union[str, Tuple[str, Dict[str, Any]]]
_ORMColCollectionElement = Union[
ColumnClause[Any],
_HasClauseElement[Any],
roles.DMLColumnRole,
"Mapped[Any]",
ColumnClause[Any], _HasClauseElement, roles.DMLColumnRole, "Mapped[Any]"
]
_ORMColCollectionArgument = Union[
str,
@@ -486,7 +481,8 @@ class RelationshipProperty(
else:
self._overlaps = ()
self.cascade = cascade
# mypy ignoring the @property setter
self.cascade = cascade # type: ignore
self.back_populates = back_populates
@@ -708,16 +704,12 @@ class RelationshipProperty(
def __eq__(self, other: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501
"""Implement the ``==`` operator.
In a many-to-one context, such as:
.. sourcecode:: text
In a many-to-one context, such as::
MyClass.some_prop == <some object>
this will typically produce a
clause such as:
.. sourcecode:: text
clause such as::
mytable.related_id == <some id>
@@ -880,12 +872,11 @@ class RelationshipProperty(
An expression like::
session.query(MyClass).filter(
MyClass.somereference.any(SomeRelated.x == 2)
MyClass.somereference.any(SomeRelated.x==2)
)
Will produce a query like:
.. sourcecode:: sql
Will produce a query like::
SELECT * FROM my_table WHERE
EXISTS (SELECT 1 FROM related WHERE related.my_id=my_table.id
@@ -899,11 +890,11 @@ class RelationshipProperty(
:meth:`~.Relationship.Comparator.any` is particularly
useful for testing for empty collections::
session.query(MyClass).filter(~MyClass.somereference.any())
session.query(MyClass).filter(
~MyClass.somereference.any()
)
will produce:
.. sourcecode:: sql
will produce::
SELECT * FROM my_table WHERE
NOT (EXISTS (SELECT 1 FROM related WHERE
@@ -934,12 +925,11 @@ class RelationshipProperty(
An expression like::
session.query(MyClass).filter(
MyClass.somereference.has(SomeRelated.x == 2)
MyClass.somereference.has(SomeRelated.x==2)
)
Will produce a query like:
.. sourcecode:: sql
Will produce a query like::
SELECT * FROM my_table WHERE
EXISTS (SELECT 1 FROM related WHERE
@@ -958,7 +948,7 @@ class RelationshipProperty(
"""
if self.property.uselist:
raise sa_exc.InvalidRequestError(
"'has()' not implemented for collections. Use any()."
"'has()' not implemented for collections. " "Use any()."
)
return self._criterion_exists(criterion, **kwargs)
@@ -978,9 +968,7 @@ class RelationshipProperty(
MyClass.contains(other)
Produces a clause like:
.. sourcecode:: sql
Produces a clause like::
mytable.id == <some id>
@@ -1000,9 +988,7 @@ class RelationshipProperty(
query(MyClass).filter(MyClass.contains(other))
Produces a query like:
.. sourcecode:: sql
Produces a query like::
SELECT * FROM my_table, my_association_table AS
my_association_table_1 WHERE
@@ -1098,15 +1084,11 @@ class RelationshipProperty(
def __ne__(self, other: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501
"""Implement the ``!=`` operator.
In a many-to-one context, such as:
.. sourcecode:: text
In a many-to-one context, such as::
MyClass.some_prop != <some object>
This will typically produce a clause such as:
.. sourcecode:: sql
This will typically produce a clause such as::
mytable.related_id != <some id>
@@ -1322,11 +1304,9 @@ class RelationshipProperty(
state,
dict_,
column,
passive=(
PassiveFlag.PASSIVE_OFF
if state.persistent
else PassiveFlag.PASSIVE_NO_FETCH ^ PassiveFlag.INIT_OK
),
passive=PassiveFlag.PASSIVE_OFF
if state.persistent
else PassiveFlag.PASSIVE_NO_FETCH ^ PassiveFlag.INIT_OK,
)
if current_value is LoaderCallableStatus.NEVER_SET:
@@ -1757,6 +1737,8 @@ class RelationshipProperty(
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)
@@ -1766,17 +1748,19 @@ class RelationshipProperty(
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
is_write_only = mapped_container is not None and issubclass(
mapped_container, WriteOnlyMapped
)
if is_write_only:
self.lazy = "write_only"
self.strategy_key = (("lazy", self.lazy),)
is_dynamic = mapped_container is not None and issubclass(
mapped_container, DynamicMapped
)
if is_dynamic:
self.lazy = "dynamic"
self.strategy_key = (("lazy", self.lazy),)
argument = de_optionalize_union_types(argument)
@@ -1827,12 +1811,15 @@ class RelationshipProperty(
argument, originating_module
)
if (
self.collection_class is None
and not is_write_only
and not is_dynamic
):
self.uselist = False
# we don't allow the collection class to be a
# __forward_arg__ right now, so if we see a forward arg here,
# we know there was no collection class either
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
@@ -2012,11 +1999,9 @@ class RelationshipProperty(
"the single_parent=True flag."
% {
"rel": self,
"direction": (
"many-to-one"
if self.direction is MANYTOONE
else "many-to-many"
),
"direction": "many-to-one"
if self.direction is MANYTOONE
else "many-to-many",
"clsname": self.parent.class_.__name__,
"relatedcls": self.mapper.class_.__name__,
},
@@ -2909,6 +2894,9 @@ class JoinCondition:
) -> None:
"""Check the foreign key columns collected and emit error
messages."""
can_sync = False
foreign_cols = self._gather_columns_with_annotation(
join_condition, "foreign"
)
@@ -3064,9 +3052,9 @@ class JoinCondition:
def _setup_pairs(self) -> None:
sync_pairs: _MutableColumnPairs = []
lrp: util.OrderedSet[Tuple[ColumnElement[Any], ColumnElement[Any]]] = (
util.OrderedSet([])
)
lrp: util.OrderedSet[
Tuple[ColumnElement[Any], ColumnElement[Any]]
] = util.OrderedSet([])
secondary_sync_pairs: _MutableColumnPairs = []
def go(
@@ -3143,9 +3131,9 @@ class JoinCondition:
# 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_})
)
self._track_overlapping_sync_targets[
to_
] = weakref.WeakKeyDictionary({self.prop: from_})
else:
other_props = []
prop_to_from = self._track_overlapping_sync_targets[to_]
@@ -3243,15 +3231,6 @@ class JoinCondition:
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],
@@ -3302,25 +3281,23 @@ class JoinCondition:
if extra_criteria:
def mark_exclude_cols(
def mark_unrelated_columns_as_ok_to_adapt(
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.
"""note unrelated columns in the "extra criteria" as OK
to adapt, 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
see #9779 for this case
"""
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:
@@ -3329,8 +3306,8 @@ class JoinCondition:
extra_criteria = tuple(
_deep_annotate(
elem,
{"should_not_adapt": True},
annotate_callable=mark_exclude_cols,
{"ok_to_adapt_in_join_condition": True},
annotate_callable=mark_unrelated_columns_as_ok_to_adapt,
)
for elem in extra_criteria
)
@@ -3344,16 +3321,14 @@ class JoinCondition:
if secondary is not None:
secondary = secondary._anonymous_fromclause(flat=True)
primary_aliasizer = ClauseAdapter(
secondary,
exclude_fn=_local_col_exclude,
secondary, exclude_fn=_ColInAnnotations("local")
)
secondary_aliasizer = ClauseAdapter(
dest_selectable, equivalents=self.child_equivalents
).chain(primary_aliasizer)
if source_selectable is not None:
primary_aliasizer = ClauseAdapter(
secondary,
exclude_fn=_local_col_exclude,
secondary, exclude_fn=_ColInAnnotations("local")
).chain(
ClauseAdapter(
source_selectable,
@@ -3365,14 +3340,14 @@ class JoinCondition:
else:
primary_aliasizer = ClauseAdapter(
dest_selectable,
exclude_fn=_local_col_exclude,
exclude_fn=_ColInAnnotations("local"),
equivalents=self.child_equivalents,
)
if source_selectable is not None:
primary_aliasizer.chain(
ClauseAdapter(
source_selectable,
exclude_fn=_remote_col_exclude,
exclude_fn=_ColInAnnotations("remote"),
equivalents=self.parent_equivalents,
)
)
@@ -3391,7 +3366,9 @@ class JoinCondition:
dest_selectable,
)
def create_lazy_clause(self, reverse_direction: bool = False) -> Tuple[
def create_lazy_clause(
self, reverse_direction: bool = False
) -> Tuple[
ColumnElement[bool],
Dict[str, ColumnElement[Any]],
Dict[ColumnElement[Any], ColumnElement[Any]],
@@ -3451,29 +3428,25 @@ class JoinCondition:
class _ColInAnnotations:
"""Serializable object that tests for names in c._annotations.
"""Serializable object that tests for a name in c._annotations."""
TODO: does this need to be serializable anymore? can we find what the
use case was for that?
__slots__ = ("name",)
"""
__slots__ = ("names",)
def __init__(self, *names: str):
self.names = frozenset(names)
def __init__(self, name: str):
self.name = name
def __call__(self, c: ClauseElement) -> bool:
return bool(self.names.intersection(c._annotations))
return (
self.name in c._annotations
or "ok_to_adapt_in_join_condition" in c._annotations
)
_local_col_exclude = _ColInAnnotations("local", "should_not_adapt")
_remote_col_exclude = _ColInAnnotations("remote", "should_not_adapt")
class Relationship(
class Relationship( # type: ignore
RelationshipProperty[_T],
_DeclarativeMapped[_T],
WriteOnlyMapped[_T], # not compatible with Mapped[_T]
DynamicMapped[_T], # not compatible with Mapped[_T]
):
"""Describes an object property that holds a single item or list
of items that correspond to a related database table.
@@ -3491,18 +3464,3 @@ class Relationship(
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"