]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
reorganize Mapped[] super outside of MapperProperty
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 29 Sep 2022 16:56:23 +0000 (12:56 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 6 Oct 2022 00:35:57 +0000 (20:35 -0400)
We made all the MapperProperty classes a subclass
of Mapped[] to allow declarative mappings to name
Mapped[] on the left side.  this was cheating a bit because
MapperProperty is not actually a descriptor, and the mapping
process replaces the object with InstrumentedAttribute at
mapping time, which is the actual Mapped[] descriptor.

But now in I6929f3da6e441cad92285e7309030a9bac4e429d we
are considering making the "cheating" a little more extensive
by putting DynamicMapped / WriteOnlyMapped in Relationship's
hierarchy, which need a flat out "type: ignore" to work.

Instead of pushing more cheats into the core classes, move
out the "Declarative"-facing versions of these classes to be
typing only: Relationship, Composite, Synonym, and MappedSQLExpression
added for ColumnProperty.  Keep the internals expressed on the
old names, RelationshipProperty, CompositeProperty, SynonymProperty,
ColumnProprerty, which will remain "pure" with fully correct typing.
then have the typing only endpoints be where the "cheating"
and "type: ignores" have to happen, so that these are more or less
slightly better forms of "Any".

Change-Id: Ied7cc11196c9204da6851f49593d1b1fd2ef8ad8

31 files changed:
doc/build/changelog/unreleased_14/8588.rst
doc/build/orm/internals.rst
lib/sqlalchemy/ext/associationproxy.py
lib/sqlalchemy/ext/declarative/extensions.py
lib/sqlalchemy/ext/mypy/names.py
lib/sqlalchemy/orm/__init__.py
lib/sqlalchemy/orm/_orm_constructors.py
lib/sqlalchemy/orm/_typing.py
lib/sqlalchemy/orm/attributes.py
lib/sqlalchemy/orm/base.py
lib/sqlalchemy/orm/clsregistry.py
lib/sqlalchemy/orm/decl_api.py
lib/sqlalchemy/orm/decl_base.py
lib/sqlalchemy/orm/descriptor_props.py
lib/sqlalchemy/orm/dynamic.py
lib/sqlalchemy/orm/interfaces.py
lib/sqlalchemy/orm/mapper.py
lib/sqlalchemy/orm/path_registry.py
lib/sqlalchemy/orm/properties.py
lib/sqlalchemy/orm/query.py
lib/sqlalchemy/orm/relationships.py
lib/sqlalchemy/orm/strategies.py
lib/sqlalchemy/orm/util.py
lib/sqlalchemy/util/__init__.py
lib/sqlalchemy/util/langhelpers.py
test/ext/mypy/plugin_files/mixin_two.py
test/ext/mypy/plugin_files/typing_err2.py
test/orm/declarative/test_basic.py
test/orm/test_mapper.py
test/orm/test_options.py
test/orm/test_rel_fn.py

index 879b8b290736d7674433250c23978f56a5d43ede..474c14c4fa0e2f164bb82e10a28917965c904dec 100644 (file)
@@ -7,4 +7,4 @@
     special keyword "ALGORITHM" in the middle, which was intended to be
     optional but was not working correctly.  The change allows view reflection
     to work more completely on MySQL-compatible variants such as StarRocks.
-    Pull request courtesy John Bodley.
\ No newline at end of file
+    Pull request courtesy John Bodley.
index 19c88d810c7da90a9d7fe1f07c4031b7e7f8b8bd..f0ace43a6f625cdb300c2a2b04b6a8c1fca8ce92 100644 (file)
@@ -21,9 +21,9 @@ sections, are listed here.
     :members:
 
 .. autoclass:: Composite
-    :members:
 
-.. autodata:: CompositeProperty
+.. autoclass:: CompositeProperty
+    :members:
 
 .. autoclass:: AttributeEventToken
     :members:
@@ -42,8 +42,7 @@ sections, are listed here.
 
 
 .. autoclass:: InstrumentedAttribute
-    :members: __get__, __set__, __delete__
-    :undoc-members:
+    :members:
 
 .. autoclass:: LoaderCallableStatus
     :members:
@@ -55,6 +54,8 @@ sections, are listed here.
 .. autoclass:: MapperProperty
     :members:
 
+.. autoclass:: MappedSQLExpression
+
 .. autoclass:: InspectionAttrExtensionType
     :members:
 
@@ -71,19 +72,17 @@ sections, are listed here.
     :inherited-members:
 
 .. autoclass:: Relationship
-    :members:
-    :inherited-members:
 
 .. autoclass:: RelationshipDirection
     :members:
 
-.. autodata:: RelationshipProperty
+.. autoclass:: RelationshipProperty
+  :members:
 
 .. autoclass:: Synonym
-    :members:
-    :inherited-members:
 
-.. autodata:: SynonymProperty
+.. autoclass:: SynonymProperty
+    :members:
 
 .. autoclass:: QueryContext
     :members:
@@ -91,7 +90,6 @@ sections, are listed here.
 
 .. autoclass:: QueryableAttribute
     :members:
-    :inherited-members:
 
 .. autoclass:: UOWTransaction
     :members:
index 6285c4ce3875d9721cbfb6aa9c0fd5000a5a5298..a17c37dacebe8ef4320bfd584585287e343dd081 100644 (file)
@@ -559,12 +559,12 @@ class AssociationProxyInstance(SQLORMOperations[_T]):
         target_collection = parent.target_collection
         value_attr = parent.value_attr
         prop = cast(
-            "orm.Relationship[_T]",
+            "orm.RelationshipProperty[_T]",
             orm.class_mapper(owning_class).get_property(target_collection),
         )
 
         # this was never asserted before but this should be made clear.
-        if not isinstance(prop, orm.Relationship):
+        if not isinstance(prop, orm.RelationshipProperty):
             raise NotImplementedError(
                 "association proxy to a non-relationship "
                 "intermediary is not supported"
index 596379bacbdcc04010f88c0c8dc784f95db6231a..0804737b5ec179254bdfa06a1e81a0475b97f79c 100644 (file)
@@ -454,7 +454,7 @@ class DeferredReflection:
                 for rel in mapper._props.values():
 
                     if (
-                        isinstance(rel, relationships.Relationship)
+                        isinstance(rel, relationships.RelationshipProperty)
                         and rel._init_args.secondary._is_populated()
                     ):
 
index 8232ca6dbd7a55ea83091f6de91c860f63a7e49b..fac6bf5b14f5d4d6a744cc8afbd6e79444a52f2c 100644 (file)
@@ -70,7 +70,18 @@ _lookup: Dict[str, Tuple[int, Set[str]]] = {
         RELATIONSHIP,
         {
             "sqlalchemy.orm.relationships.Relationship",
+            "sqlalchemy.orm.relationships.RelationshipProperty",
             "sqlalchemy.orm.Relationship",
+            "sqlalchemy.orm.RelationshipProperty",
+        },
+    ),
+    "RelationshipProperty": (
+        RELATIONSHIP,
+        {
+            "sqlalchemy.orm.relationships.Relationship",
+            "sqlalchemy.orm.relationships.RelationshipProperty",
+            "sqlalchemy.orm.Relationship",
+            "sqlalchemy.orm.RelationshipProperty",
         },
     ),
     "registry": (
@@ -83,6 +94,17 @@ _lookup: Dict[str, Tuple[int, Set[str]]] = {
     "ColumnProperty": (
         COLUMN_PROPERTY,
         {
+            "sqlalchemy.orm.properties.MappedSQLExpression",
+            "sqlalchemy.orm.MappedSQLExpression",
+            "sqlalchemy.orm.properties.ColumnProperty",
+            "sqlalchemy.orm.ColumnProperty",
+        },
+    ),
+    "MappedSQLExpression": (
+        COLUMN_PROPERTY,
+        {
+            "sqlalchemy.orm.properties.MappedSQLExpression",
+            "sqlalchemy.orm.MappedSQLExpression",
             "sqlalchemy.orm.properties.ColumnProperty",
             "sqlalchemy.orm.ColumnProperty",
         },
@@ -92,6 +114,17 @@ _lookup: Dict[str, Tuple[int, Set[str]]] = {
         {
             "sqlalchemy.orm.descriptor_props.Synonym",
             "sqlalchemy.orm.Synonym",
+            "sqlalchemy.orm.descriptor_props.SynonymProperty",
+            "sqlalchemy.orm.SynonymProperty",
+        },
+    ),
+    "SynonymProperty": (
+        SYNONYM_PROPERTY,
+        {
+            "sqlalchemy.orm.descriptor_props.Synonym",
+            "sqlalchemy.orm.Synonym",
+            "sqlalchemy.orm.descriptor_props.SynonymProperty",
+            "sqlalchemy.orm.SynonymProperty",
         },
     ),
     "Composite": (
@@ -99,6 +132,17 @@ _lookup: Dict[str, Tuple[int, Set[str]]] = {
         {
             "sqlalchemy.orm.descriptor_props.Composite",
             "sqlalchemy.orm.Composite",
+            "sqlalchemy.orm.descriptor_props.CompositeProperty",
+            "sqlalchemy.orm.CompositeProperty",
+        },
+    ),
+    "CompositeProperty": (
+        COMPOSITE_PROPERTY,
+        {
+            "sqlalchemy.orm.descriptor_props.Composite",
+            "sqlalchemy.orm.Composite",
+            "sqlalchemy.orm.descriptor_props.CompositeProperty",
+            "sqlalchemy.orm.CompositeProperty",
         },
     ),
     "MapperProperty": (
index 7f0de6782e839574349ef12f4b9991b5c703fb91..6bfda6e2e3e15288268b8224e2ea57932a8a9175 100644 (file)
@@ -26,7 +26,6 @@ from ._orm_constructors import backref as backref
 from ._orm_constructors import clear_mappers as clear_mappers
 from ._orm_constructors import column_property as column_property
 from ._orm_constructors import composite as composite
-from ._orm_constructors import CompositeProperty as CompositeProperty
 from ._orm_constructors import contains_alias as contains_alias
 from ._orm_constructors import create_session as create_session
 from ._orm_constructors import deferred as deferred
@@ -36,9 +35,7 @@ from ._orm_constructors import mapped_column as mapped_column
 from ._orm_constructors import outerjoin as outerjoin
 from ._orm_constructors import query_expression as query_expression
 from ._orm_constructors import relationship as relationship
-from ._orm_constructors import RelationshipProperty as RelationshipProperty
 from ._orm_constructors import synonym as synonym
-from ._orm_constructors import SynonymProperty as SynonymProperty
 from ._orm_constructors import with_loader_criteria as with_loader_criteria
 from ._orm_constructors import with_polymorphic as with_polymorphic
 from .attributes import AttributeEventToken as AttributeEventToken
@@ -66,7 +63,9 @@ from .decl_api import MappedAsDataclass as MappedAsDataclass
 from .decl_api import registry as registry
 from .decl_api import synonym_for as synonym_for
 from .descriptor_props import Composite as Composite
+from .descriptor_props import CompositeProperty as CompositeProperty
 from .descriptor_props import Synonym as Synonym
+from .descriptor_props import SynonymProperty as SynonymProperty
 from .dynamic import AppenderQuery as AppenderQuery
 from .events import AttributeEvents as AttributeEvents
 from .events import InstanceEvents as InstanceEvents
@@ -106,10 +105,12 @@ from .mapper import reconstructor as reconstructor
 from .mapper import validates as validates
 from .properties import ColumnProperty as ColumnProperty
 from .properties import MappedColumn as MappedColumn
+from .properties import MappedSQLExpression as MappedSQLExpression
 from .query import AliasOption as AliasOption
 from .query import Query as Query
 from .relationships import foreign as foreign
 from .relationships import Relationship as Relationship
+from .relationships import RelationshipProperty as RelationshipProperty
 from .relationships import remote as remote
 from .scoping import scoped_session as scoped_session
 from .session import close_all_sessions as close_all_sessions
index 0ea870277fe2a93b99c84f6516520d9f8217d971..0b4861af3b556b2a11697adfc6b9e9f4bc9f7fd0 100644 (file)
@@ -26,9 +26,11 @@ from .descriptor_props import Synonym
 from .interfaces import _AttributeOptions
 from .properties import ColumnProperty
 from .properties import MappedColumn
+from .properties import MappedSQLExpression
 from .query import AliasOption
 from .relationships import _RelationshipArgumentType
 from .relationships import Relationship
+from .relationships import RelationshipProperty
 from .session import Session
 from .util import _ORMJoin
 from .util import AliasedClass
@@ -74,16 +76,6 @@ if TYPE_CHECKING:
 _T = typing.TypeVar("_T")
 
 
-CompositeProperty = Composite
-"""Alias for :class:`_orm.Composite`."""
-
-RelationshipProperty = Relationship
-"""Alias for :class:`_orm.Relationship`."""
-
-SynonymProperty = Synonym
-"""Alias for :class:`_orm.Synonym`."""
-
-
 @util.deprecated(
     "1.4",
     "The :class:`.AliasOption` object is not necessary "
@@ -308,7 +300,7 @@ def column_property(
     expire_on_flush: bool = True,
     info: Optional[_InfoType] = None,
     doc: Optional[str] = None,
-) -> ColumnProperty[_T]:
+) -> MappedSQLExpression[_T]:
     r"""Provide a column-level property for use with a mapping.
 
     Column-based properties can normally be applied to the mapper's
@@ -392,7 +384,7 @@ def column_property(
         expressions
 
     """
-    return ColumnProperty(
+    return MappedSQLExpression(
         column,
         *additional_columns,
         attribute_options=_AttributeOptions(
@@ -772,7 +764,9 @@ def relationship(
     foreign_keys: Optional[_ORMColCollectionArgument] = None,
     remote_side: Optional[_ORMColCollectionArgument] = None,
     join_depth: Optional[int] = None,
-    comparator_factory: Optional[Type[Relationship.Comparator[Any]]] = None,
+    comparator_factory: Optional[
+        Type[RelationshipProperty.Comparator[Any]]
+    ] = None,
     single_parent: bool = False,
     innerjoin: bool = False,
     distinct_target_key: Optional[bool] = None,
@@ -1567,6 +1561,7 @@ def relationship(
 
 
     """
+
     return Relationship(
         argument,
         secondary=secondary,
@@ -1802,7 +1797,7 @@ def _mapper_fn(*arg: Any, **kw: Any) -> NoReturn:
 
 def dynamic_loader(
     argument: Optional[_RelationshipArgumentType[Any]] = None, **kw: Any
-) -> Relationship[Any]:
+) -> RelationshipProperty[Any]:
     """Construct a dynamically-loading mapper property.
 
     This is essentially the same as
index ed04c96c7c161904731dc4be54c746b6bd280d31..06df0731d2cf777ccd124f4b8105df8000edb857 100644 (file)
@@ -28,7 +28,7 @@ if TYPE_CHECKING:
     from .interfaces import MapperProperty
     from .interfaces import UserDefinedOption
     from .mapper import Mapper
-    from .relationships import Relationship
+    from .relationships import RelationshipProperty
     from .state import InstanceState
     from .util import AliasedClass
     from .util import AliasedInsp
@@ -132,7 +132,7 @@ if TYPE_CHECKING:
 
     def prop_is_relationship(
         prop: MapperProperty[Any],
-    ) -> TypeGuard[Relationship[Any]]:
+    ) -> TypeGuard[RelationshipProperty[Any]]:
         ...
 
     def is_collection_impl(
index 119503014201798f4685e5ba2291375a2f348c08..fcc016f549c2951ea03fc390778749bffffd88d2 100644 (file)
@@ -39,6 +39,7 @@ from . import collections
 from . import exc as orm_exc
 from . import interfaces
 from ._typing import insp_is_aliased_class
+from .base import _DeclarativeMapped
 from .base import ATTR_EMPTY
 from .base import ATTR_WAS_SET
 from .base import CALLABLES_OK
@@ -95,7 +96,7 @@ if TYPE_CHECKING:
     from .collections import CollectionAdapter
     from .dynamic import DynamicAttributeImpl
     from .interfaces import MapperProperty
-    from .relationships import Relationship
+    from .relationships import RelationshipProperty
     from .state import InstanceState
     from .util import AliasedInsp
     from ..event.base import _Dispatch
@@ -131,7 +132,7 @@ SelfQueryableAttribute = TypeVar(
 @inspection._self_inspects
 class QueryableAttribute(
     roles.ExpressionElementRole[_T],
-    interfaces._MappedAttribute[_T],
+    _DeclarativeMapped[_T],
     interfaces.InspectionAttr,
     interfaces.PropComparator[_T],
     roles.JoinTargetRole,
@@ -408,7 +409,7 @@ class QueryableAttribute(
         self, *clauses: _ColumnExpressionArgument[bool]
     ) -> interfaces.PropComparator[bool]:
         if TYPE_CHECKING:
-            assert isinstance(self.comparator, Relationship.Comparator)
+            assert isinstance(self.comparator, RelationshipProperty.Comparator)
 
         exprs = tuple(
             coercions.expect(roles.WhereHavingRole, clause)
@@ -507,17 +508,24 @@ class InstrumentedAttribute(QueryableAttribute[_T]):
     __slots__ = ()
 
     inherit_cache = True
+    """:meta private:"""
 
-    #    if not TYPE_CHECKING:
+    # hack to make __doc__ writeable on instances of
+    # InstrumentedAttribute, while still keeping classlevel
+    # __doc__ correct
 
-    @property  # type: ignore
+    @util.rw_hybridproperty  # type: ignore
     def __doc__(self) -> Optional[str]:  # type: ignore
         return self._doc
 
-    @__doc__.setter
-    def __doc__(self, value: Optional[str]) -> None:
+    @__doc__.setter  # type: ignore
+    def __doc__(self, value: Optional[str]) -> None:  # type: ignore
         self._doc = value
 
+    @__doc__.classlevel  # type: ignore
+    def __doc__(cls) -> Optional[str]:  # type: ignore
+        return super().__doc__
+
     def __set__(self, instance: object, value: Any) -> None:
         self.impl.set(
             instance_state(instance), instance_dict(instance), value, None
@@ -2612,7 +2620,7 @@ def register_descriptor(
         class_, key, comparator=comparator, parententity=parententity
     )
 
-    descriptor.__doc__ = doc
+    descriptor.__doc__ = doc  # type: ignore
 
     manager.instrument_attribute(key, descriptor)
     return descriptor
index 47ae99efe25aa7e1a2c883c34edb7caeb31e127f..d3814abd57b3e19f0c2913e27a1e965fd1173532 100644 (file)
@@ -210,11 +210,11 @@ EXT_CONTINUE, EXT_STOP, EXT_SKIP, NO_KEY = tuple(EventConstants)
 
 class RelationshipDirection(Enum):
     """enumeration which indicates the 'direction' of a
-    :class:`_orm.Relationship`.
+    :class:`_orm.RelationshipProperty`.
 
     :class:`.RelationshipDirection` is accessible from the
     :attr:`_orm.Relationship.direction` attribute of
-    :class:`_orm.Relationship`.
+    :class:`_orm.RelationshipProperty`.
 
     """
 
@@ -795,10 +795,19 @@ class Mapped(ORMDescriptor[_T], roles.TypedColumnsClauseRole[_T], TypingOnly):
             ...
 
 
-class _MappedAttribute(Mapped[_T], TypingOnly):
+class _MappedAttribute(Generic[_T], TypingOnly):
     """Mixin for attributes which should be replaced by mapper-assigned
     attributes.
 
     """
 
     __slots__ = ()
+
+
+class _DeclarativeMapped(Mapped[_T], _MappedAttribute[_T]):
+    """Mixin for :class:`.MapperProperty` subclasses that allows them to
+    be compatible with ORM-annotated declarative mappings.
+
+    """
+
+    __slots__ = ()
index dd79eb1d096d13cf45f932d40028069bb0531f46..99a51c998f803aabc7885b9e1545de5e598b2652 100644 (file)
@@ -36,7 +36,7 @@ import weakref
 
 from . import attributes
 from . import interfaces
-from .descriptor_props import Synonym
+from .descriptor_props import SynonymProperty
 from .properties import ColumnProperty
 from .util import class_mapper
 from .. import exc
@@ -46,7 +46,7 @@ from ..sql.schema import _get_table_key
 from ..util.typing import CallableReference
 
 if TYPE_CHECKING:
-    from .relationships import Relationship
+    from .relationships import RelationshipProperty
     from ..sql.schema import MetaData
     from ..sql.schema import Table
 
@@ -350,7 +350,7 @@ class _GetColumns:
             if desc.extension_type is interfaces.NotExtension.NOT_EXTENSION:
                 assert isinstance(desc, attributes.QueryableAttribute)
                 prop = desc.property
-                if isinstance(prop, Synonym):
+                if isinstance(prop, SynonymProperty):
                     key = prop.name
                 elif not isinstance(prop, ColumnProperty):
                     raise exc.InvalidRequestError(
@@ -398,7 +398,7 @@ class _class_resolver:
     )
 
     cls: Type[Any]
-    prop: Relationship[Any]
+    prop: RelationshipProperty[Any]
     fallback: Mapping[str, Any]
     arg: str
     favor_tables: bool
@@ -407,7 +407,7 @@ class _class_resolver:
     def __init__(
         self,
         cls: Type[Any],
-        prop: Relationship[Any],
+        prop: RelationshipProperty[Any],
         fallback: Mapping[str, Any],
         arg: str,
         favor_tables: bool = False,
@@ -520,7 +520,7 @@ _fallback_dict: Mapping[str, Any] = None  # type: ignore
 
 
 def _resolver(
-    cls: Type[Any], prop: Relationship[Any]
+    cls: Type[Any], prop: RelationshipProperty[Any]
 ) -> Tuple[
     Callable[[str], Callable[[], Union[Type[Any], Table, _ModNS]]],
     Callable[[str, bool], _class_resolver],
index d34ec8c93e8994a68c0000725a4ed44695ba39d2..5724d53a25ee68f6fe1f6f1b1bbd80d8ffcb4a2b 100644 (file)
@@ -57,7 +57,7 @@ from .descriptor_props import Synonym as _orm_synonym
 from .mapper import Mapper
 from .properties import ColumnProperty
 from .properties import MappedColumn
-from .relationships import Relationship
+from .relationships import RelationshipProperty
 from .state import InstanceState
 from .. import exc
 from .. import inspection
@@ -141,7 +141,7 @@ class DeclarativeAttributeIntercept(
 @compat_typing.dataclass_transform(
     field_descriptors=(
         MappedColumn[Any],
-        Relationship[Any],
+        RelationshipProperty[Any],
         Composite[Any],
         ColumnProperty[Any],
         Synonym[Any],
@@ -1290,7 +1290,7 @@ class registry:
     @compat_typing.dataclass_transform(
         field_descriptors=(
             MappedColumn[Any],
-            Relationship[Any],
+            RelationshipProperty[Any],
             Composite[Any],
             ColumnProperty[Any],
             Synonym[Any],
index e8d6e4c1b1ffb0df709ae23b7710d42f2632f469..a383e92ca1227f9e1cffae78281bb72c67a30a17 100644 (file)
@@ -40,8 +40,8 @@ from .attributes import InstrumentedAttribute
 from .attributes import QueryableAttribute
 from .base import _is_mapped_class
 from .base import InspectionAttr
-from .descriptor_props import Composite
-from .descriptor_props import Synonym
+from .descriptor_props import CompositeProperty
+from .descriptor_props import SynonymProperty
 from .interfaces import _AttributeOptions
 from .interfaces import _IntrospectsAnnotations
 from .interfaces import _MappedAttribute
@@ -1211,7 +1211,7 @@ class _ClassScanMapperConfig(_MapperConfig):
             ):
                 # detect a QueryableAttribute that's already mapped being
                 # assigned elsewhere in userland, turn into a synonym()
-                value = Synonym(value.key)
+                value = SynonymProperty(value.key)
                 setattr(cls, k, value)
 
             if (
@@ -1316,7 +1316,7 @@ class _ClassScanMapperConfig(_MapperConfig):
                     del our_stuff[key]
 
                 for col in c.columns_to_assign:
-                    if not isinstance(c, Composite):
+                    if not isinstance(c, CompositeProperty):
                         name_to_prop_key[col.name].add(key)
                     declared_columns.add(col)
 
@@ -1736,7 +1736,7 @@ def _add_attribute(
         elif isinstance(value, QueryableAttribute) and value.key != key:
             # detect a QueryableAttribute that's already mapped being
             # assigned elsewhere in userland, turn into a synonym()
-            value = Synonym(value.key)
+            value = SynonymProperty(value.key)
             mapped_cls.__mapper__.add_property(key, value)
         else:
             type.__setattr__(cls, key, value)
index 13d3b70fe6ee89cf8e3f5f25370e7e9234abab80..35b12b2ede76a4b0505d6a2b5c1e6dce090fa86e 100644 (file)
@@ -33,6 +33,7 @@ import weakref
 
 from . import attributes
 from . import util as orm_util
+from .base import _DeclarativeMapped
 from .base import LoaderCallableStatus
 from .base import Mapped
 from .base import PassiveFlag
@@ -172,19 +173,15 @@ _composite_getters: weakref.WeakKeyDictionary[
 ] = weakref.WeakKeyDictionary()
 
 
-class Composite(
+class CompositeProperty(
     _MapsColumns[_CC], _IntrospectsAnnotations, DescriptorProperty[_CC]
 ):
     """Defines a "composite" mapped attribute, representing a collection
     of columns as one attribute.
 
-    :class:`.Composite` is constructed using the :func:`.composite`
+    :class:`.CompositeProperty` is constructed using the :func:`.composite`
     function.
 
-    .. versionchanged:: 2.0 Renamed :class:`_orm.CompositeProperty`
-       to :class:`_orm.Composite`.  The old name
-       :class:`_orm.CompositeProperty` remains as an alias.
-
     .. seealso::
 
         :ref:`mapper_composite`
@@ -722,11 +719,11 @@ class Composite(
                 group=False, *self._comparable_elements
             )
 
-        def __clause_element__(self) -> Composite.CompositeBundle[_PT]:
+        def __clause_element__(self) -> CompositeProperty.CompositeBundle[_PT]:
             return self.expression
 
         @util.memoized_property
-        def expression(self) -> Composite.CompositeBundle[_PT]:
+        def expression(self) -> CompositeProperty.CompositeBundle[_PT]:
             clauses = self.clauses._annotate(
                 {
                     "parententity": self._parententity,
@@ -734,7 +731,7 @@ class Composite(
                     "proxy_key": self.prop.key,
                 }
             )
-            return Composite.CompositeBundle(self.prop, clauses)
+            return CompositeProperty.CompositeBundle(self.prop, clauses)
 
         def _bulk_update_tuples(
             self, value: Any
@@ -814,6 +811,25 @@ class Composite(
         return str(self.parent.class_.__name__) + "." + self.key
 
 
+class Composite(CompositeProperty[_T], _DeclarativeMapped[_T]):
+    """Declarative-compatible front-end for the :class:`.CompositeProperty`
+    class.
+
+    Public constructor is the :func:`_orm.composite` function.
+
+    .. versionchanged:: 2.0 Added :class:`_orm.Composite` as a Declarative
+       compatible subclass of :class:`_orm.CompositeProperty`.
+
+    .. seealso::
+
+        :ref:`mapper_composite`
+
+    """
+
+    inherit_cache = True
+    """:meta private:"""
+
+
 class ConcreteInheritedProperty(DescriptorProperty[_T]):
     """A 'do nothing' :class:`.MapperProperty` that disables
     an attribute on a concrete subclass that is only present
@@ -871,7 +887,7 @@ class ConcreteInheritedProperty(DescriptorProperty[_T]):
         self.descriptor = NoninheritedConcreteProp()
 
 
-class Synonym(DescriptorProperty[_T]):
+class SynonymProperty(DescriptorProperty[_T]):
     """Denote an attribute name as a synonym to a mapped property,
     in that the attribute will mirror the value and expression behavior
     of another attribute.
@@ -879,10 +895,6 @@ class Synonym(DescriptorProperty[_T]):
     :class:`.Synonym` is constructed using the :func:`_orm.synonym`
     function.
 
-    .. versionchanged:: 2.0 Renamed :class:`_orm.SynonymProperty`
-       to :class:`_orm.Synonym`.  The old name
-       :class:`_orm.SynonymProperty` remains as an alias.
-
     .. seealso::
 
         :ref:`synonyms` - Overview of synonyms
@@ -1008,3 +1020,21 @@ class Synonym(DescriptorProperty[_T]):
             p._mapped_by_synonym = self.key
 
         self.parent = parent
+
+
+class Synonym(SynonymProperty[_T], _DeclarativeMapped[_T]):
+    """Declarative front-end for the :class:`.SynonymProperty` class.
+
+    Public constructor is the :func:`_orm.synonym` function.
+
+    .. versionchanged:: 2.0 Added :class:`_orm.Synonym` as a Declarative
+       compatible subclass for :class:`_orm.SynonymProperty`
+
+    .. seealso::
+
+        :ref:`synonyms` - Overview of synonyms
+
+    """
+
+    inherit_cache = True
+    """:meta private:"""
index 8663389bc3c626525832fb6e46274d6fd72568ef..8cc4c6c0423be1970c9970a3e3b80d270da921ae 100644 (file)
@@ -48,7 +48,7 @@ if TYPE_CHECKING:
 
 
 @log.class_logger
-@relationships.Relationship.strategy_for(lazy="dynamic")
+@relationships.RelationshipProperty.strategy_for(lazy="dynamic")
 class DynaLoader(strategies.AbstractRelationshipLoader, log.Identified):
     def init_class_attribute(self, mapper):
         self.is_class_level = True
index 3c2903f30491fdf60502476dc84864d460fed7e3..b3fbe6ba7c9e7c7824372c7010035b32be33e19d 100644 (file)
@@ -271,7 +271,10 @@ class _MapsColumns(_MappedAttribute[_T]):
 # by typing tools
 @inspection._self_inspects
 class MapperProperty(
-    HasCacheKey, _MappedAttribute[_T], InspectionAttrInfo, util.MemoizedSlots
+    HasCacheKey,
+    _MappedAttribute[_T],
+    InspectionAttrInfo,
+    util.MemoizedSlots,
 ):
     """Represent a particular class attribute mapped by :class:`_orm.Mapper`.
 
index 553f7b35b25bc0a58b214439329a67594fa2c130..36b97cf17e0532db8f94b4f28cb9162c7a5e9784 100644 (file)
@@ -95,13 +95,13 @@ if TYPE_CHECKING:
     from ._typing import _RegistryType
     from .decl_api import registry
     from .dependency import DependencyProcessor
-    from .descriptor_props import Composite
-    from .descriptor_props import Synonym
+    from .descriptor_props import CompositeProperty
+    from .descriptor_props import SynonymProperty
     from .events import MapperEvents
     from .instrumentation import ClassManager
     from .path_registry import CachingEntityRegistry
     from .properties import ColumnProperty
-    from .relationships import Relationship
+    from .relationships import RelationshipProperty
     from .state import InstanceState
     from ..engine import Row
     from ..engine import RowMapping
@@ -2782,7 +2782,7 @@ class Mapper(
 
     @HasMemoized.memoized_attribute
     @util.preload_module("sqlalchemy.orm.descriptor_props")
-    def synonyms(self) -> util.ReadOnlyProperties[Synonym[Any]]:
+    def synonyms(self) -> util.ReadOnlyProperties[SynonymProperty[Any]]:
         """Return a namespace of all :class:`.Synonym`
         properties maintained by this :class:`_orm.Mapper`.
 
@@ -2795,7 +2795,7 @@ class Mapper(
         """
         descriptor_props = util.preloaded.orm_descriptor_props
 
-        return self._filter_properties(descriptor_props.Synonym)
+        return self._filter_properties(descriptor_props.SynonymProperty)
 
     @property
     def entity_namespace(self):
@@ -2817,7 +2817,9 @@ class Mapper(
 
     @HasMemoized.memoized_attribute
     @util.preload_module("sqlalchemy.orm.relationships")
-    def relationships(self) -> util.ReadOnlyProperties[Relationship[Any]]:
+    def relationships(
+        self,
+    ) -> util.ReadOnlyProperties[RelationshipProperty[Any]]:
         """A namespace of all :class:`.Relationship` properties
         maintained by this :class:`_orm.Mapper`.
 
@@ -2841,12 +2843,12 @@ class Mapper(
 
         """
         return self._filter_properties(
-            util.preloaded.orm_relationships.Relationship
+            util.preloaded.orm_relationships.RelationshipProperty
         )
 
     @HasMemoized.memoized_attribute
     @util.preload_module("sqlalchemy.orm.descriptor_props")
-    def composites(self) -> util.ReadOnlyProperties[Composite[Any]]:
+    def composites(self) -> util.ReadOnlyProperties[CompositeProperty[Any]]:
         """Return a namespace of all :class:`.Composite`
         properties maintained by this :class:`_orm.Mapper`.
 
@@ -2858,7 +2860,7 @@ class Mapper(
 
         """
         return self._filter_properties(
-            util.preloaded.orm_descriptor_props.Composite
+            util.preloaded.orm_descriptor_props.CompositeProperty
         )
 
     def _filter_properties(
index 8a51ded5f9058af23780b334664ad22148a649cc..cb05283f97ac42dbd6bc0b07e15adb3438c7aa56 100644 (file)
@@ -37,7 +37,7 @@ if TYPE_CHECKING:
     from ._typing import _InternalEntityType
     from .interfaces import MapperProperty
     from .mapper import Mapper
-    from .relationships import Relationship
+    from .relationships import RelationshipProperty
     from .util import AliasedInsp
     from ..sql.cache_key import _CacheKeyTraversalType
     from ..sql.elements import BindParameter
@@ -573,7 +573,7 @@ class PropRegistry(PathRegistry):
         self.has_entity = prop._links_to_entity
         if prop._is_relationship:
             if TYPE_CHECKING:
-                assert isinstance(prop, Relationship)
+                assert isinstance(prop, RelationshipProperty)
             self.entity = prop.entity
             self.mapper = prop.mapper
         else:
index 3d9fe578d8b73a5030b3d868e3dd043d647d2cdd..1f2e9706b2562f1edf321a8ea9b1c8ff0e57003d 100644 (file)
@@ -27,9 +27,10 @@ from typing import TypeVar
 
 from . import attributes
 from . import strategy_options
-from .descriptor_props import Composite
+from .base import _DeclarativeMapped
+from .descriptor_props import CompositeProperty
 from .descriptor_props import ConcreteInheritedProperty
-from .descriptor_props import Synonym
+from .descriptor_props import SynonymProperty
 from .interfaces import _AttributeOptions
 from .interfaces import _DEFAULT_ATTRIBUTE_OPTIONS
 from .interfaces import _IntrospectsAnnotations
@@ -37,7 +38,7 @@ from .interfaces import _MapsColumns
 from .interfaces import MapperProperty
 from .interfaces import PropComparator
 from .interfaces import StrategizedProperty
-from .relationships import Relationship
+from .relationships import RelationshipProperty
 from .. import exc as sa_exc
 from .. import ForeignKey
 from .. import log
@@ -81,10 +82,10 @@ _NC = TypeVar("_NC", bound="NamedColumn[Any]")
 
 __all__ = [
     "ColumnProperty",
-    "Composite",
+    "CompositeProperty",
     "ConcreteInheritedProperty",
-    "Relationship",
-    "Synonym",
+    "RelationshipProperty",
+    "SynonymProperty",
 ]
 
 
@@ -95,7 +96,8 @@ class ColumnProperty(
     _IntrospectsAnnotations,
     log.Identified,
 ):
-    """Describes an object attribute that corresponds to a table column.
+    """Describes an object attribute that corresponds to a table column
+    or other column expression.
 
     Public constructor is the :func:`_orm.column_property` function.
 
@@ -103,6 +105,8 @@ class ColumnProperty(
 
     strategy_wildcard_key = strategy_options._COLUMN_TOKEN
     inherit_cache = True
+    """:meta private:"""
+
     _links_to_entity = False
 
     columns: List[NamedColumn[Any]]
@@ -474,10 +478,29 @@ class ColumnProperty(
         return str(self.parent.class_.__name__) + "." + self.key
 
 
+class MappedSQLExpression(ColumnProperty[_T], _DeclarativeMapped[_T]):
+    """Declarative front-end for the :class:`.ColumnProperty` class.
+
+    Public constructor is the :func:`_orm.column_property` function.
+
+    .. versionchanged:: 2.0 Added :class:`_orm.MappedSQLExpression` as
+       a Declarative compatible subclass for :class:`_orm.ColumnProperty`.
+
+    .. seealso::
+
+        :class:`.MappedColumn`
+
+    """
+
+    inherit_cache = True
+    """:meta private:"""
+
+
 class MappedColumn(
     SQLCoreOperations[_T],
     _IntrospectsAnnotations,
     _MapsColumns[_T],
+    _DeclarativeMapped[_T],
 ):
     """Maps a single :class:`_schema.Column` on a class.
 
index 61c446060748f81ea74badfd7fb2ca6021af6066..30b0f41cf5ce635d3f4d5d40c0ec891574173a80 100644 (file)
@@ -1263,7 +1263,7 @@ class Query(
 
             for prop in mapper.iterate_properties:
                 if (
-                    isinstance(prop, relationships.Relationship)
+                    isinstance(prop, relationships.RelationshipProperty)
                     and prop.mapper is entity_zero.mapper  # type: ignore
                 ):
                     property = prop  # type: ignore  # noqa: A001
index f0221cb3c2a3d04a51a8b0b6d61717215212f38a..c215623e214691b53016c39a5693ad1b69cef654 100644 (file)
@@ -44,6 +44,7 @@ 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 LoaderCallableStatus
@@ -285,7 +286,7 @@ class _RelationshipArgs(NamedTuple):
 
 
 @log.class_logger
-class Relationship(
+class RelationshipProperty(
     _IntrospectsAnnotations, StrategizedProperty[_T], log.Identified
 ):
     """Describes an object property that holds a single item or list
@@ -297,14 +298,11 @@ class Relationship(
 
         :ref:`relationship_config_toplevel`
 
-    .. versionchanged:: 2.0 Renamed :class:`_orm.RelationshipProperty`
-       to :class:`_orm.Relationship`.  The old name
-       :class:`_orm.RelationshipProperty` remains as an alias.
-
     """
 
     strategy_wildcard_key = strategy_options._RELATIONSHIP_TOKEN
     inherit_cache = True
+    """:meta private:"""
 
     _links_to_entity = True
     _is_relationship = True
@@ -372,7 +370,7 @@ class Relationship(
         remote_side: Optional[_ORMColCollectionArgument] = None,
         join_depth: Optional[int] = None,
         comparator_factory: Optional[
-            Type[Relationship.Comparator[Any]]
+            Type[RelationshipProperty.Comparator[Any]]
         ] = None,
         single_parent: bool = False,
         innerjoin: bool = False,
@@ -388,7 +386,7 @@ class Relationship(
         _local_remote_pairs: Optional[_ColumnPairs] = None,
         _legacy_inactive_history_style: bool = False,
     ):
-        super(Relationship, self).__init__(attribute_options=attribute_options)
+        super().__init__(attribute_options=attribute_options)
 
         self.uselist = uselist
         self.argument = argument
@@ -450,7 +448,9 @@ class Relationship(
         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 Relationship.Comparator
+        self.comparator_factory = (
+            comparator_factory or RelationshipProperty.Comparator
+        )
         util.set_creation_order(self)
 
         if info is not None:
@@ -458,7 +458,7 @@ class Relationship(
 
         self.strategy_key = (("lazy", self.lazy),)
 
-        self._reverse_property: Set[Relationship[Any]] = set()
+        self._reverse_property: Set[RelationshipProperty[Any]] = set()
 
         if overlaps:
             self._overlaps = set(re.split(r"\s*,\s*", overlaps))  # type: ignore  # noqa: E501
@@ -509,7 +509,7 @@ class Relationship(
 
     class Comparator(util.MemoizedSlots, PropComparator[_PT]):
         """Produce boolean, comparison, and other operators for
-        :class:`.Relationship` attributes.
+        :class:`.RelationshipProperty` attributes.
 
         See the documentation for :class:`.PropComparator` for a brief
         overview of ORM level operator definition.
@@ -536,18 +536,18 @@ class Relationship(
             "_extra_criteria",
         )
 
-        prop: RODescriptorReference[Relationship[_PT]]
+        prop: RODescriptorReference[RelationshipProperty[_PT]]
         _of_type: Optional[_EntityType[_PT]]
 
         def __init__(
             self,
-            prop: Relationship[_PT],
+            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:`.Relationship.Comparator`
+            """Construction of :class:`.RelationshipProperty.Comparator`
             is internal to the ORM's attribute mechanics.
 
             """
@@ -562,7 +562,7 @@ class Relationship(
 
         def adapt_to_entity(
             self, adapt_to_entity: AliasedInsp[Any]
-        ) -> Relationship.Comparator[Any]:
+        ) -> RelationshipProperty.Comparator[Any]:
             return self.__class__(
                 self.prop,
                 self._parententity,
@@ -572,7 +572,7 @@ class Relationship(
 
         entity: _InternalEntityType[_PT]
         """The target entity referred to by this
-        :class:`.Relationship.Comparator`.
+        :class:`.RelationshipProperty.Comparator`.
 
         This is either a :class:`_orm.Mapper` or :class:`.AliasedInsp`
         object.
@@ -584,7 +584,7 @@ class Relationship(
 
         mapper: Mapper[_PT]
         """The target :class:`_orm.Mapper` referred to by this
-        :class:`.Relationship.Comparator`.
+        :class:`.RelationshipProperty.Comparator`.
 
         This is the "target" or "remote" side of the
         :func:`_orm.relationship`.
@@ -639,7 +639,7 @@ class Relationship(
 
 
             """
-            return Relationship.Comparator(
+            return RelationshipProperty.Comparator(
                 self.prop,
                 self._parententity,
                 adapt_to_entity=self._adapt_to_entity,
@@ -662,7 +662,7 @@ class Relationship(
                 for clause in util.coerce_generator_arg(criteria)
             )
 
-            return Relationship.Comparator(
+            return RelationshipProperty.Comparator(
                 self.prop,
                 self._parententity,
                 adapt_to_entity=self._adapt_to_entity,
@@ -1124,7 +1124,7 @@ class Relationship(
             else:
                 return _orm_annotate(self.__negated_contains_or_equals(other))
 
-        def _memoized_attr_property(self) -> Relationship[_PT]:
+        def _memoized_attr_property(self) -> RelationshipProperty[_PT]:
             self.prop.parent._check_configure()
             return self.prop
 
@@ -1531,7 +1531,7 @@ class Relationship(
 
     @staticmethod
     def _check_sync_backref(
-        rel_a: Relationship[Any], rel_b: Relationship[Any]
+        rel_a: RelationshipProperty[Any], rel_b: RelationshipProperty[Any]
     ) -> None:
         if rel_a.viewonly and rel_b.sync_backref:
             raise sa_exc.InvalidRequestError(
@@ -1547,7 +1547,7 @@ class Relationship(
 
     def _add_reverse_property(self, key: str) -> None:
         other = self.mapper.get_property(key, _configure_mappers=False)
-        if not isinstance(other, Relationship):
+        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 "
@@ -1601,7 +1601,7 @@ class Relationship(
     @util.memoized_property
     def mapper(self) -> Mapper[_T]:
         """Return the targeted :class:`_orm.Mapper` for this
-        :class:`.Relationship`.
+        :class:`.RelationshipProperty`.
 
         """
         return self.entity.mapper
@@ -1616,7 +1616,7 @@ class Relationship(
         self._post_init()
         self._generate_backref()
         self._join_condition._warn_for_conflicting_sync_targets()
-        super(Relationship, self).do_init()
+        super().do_init()
         self._lazy_strategy = cast(
             "LazyLoader", self._get_strategy((("lazy", "select"),))
         )
@@ -1883,7 +1883,7 @@ class Relationship(
     @property
     def cascade(self) -> CascadeOptions:
         """Return the current cascade setting for this
-        :class:`.Relationship`.
+        :class:`.RelationshipProperty`.
         """
         return self._cascade
 
@@ -1963,7 +1963,7 @@ class Relationship(
 
     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:`.Relationship`.
+        mapped by the tables referenced by this :class:`.RelationshipProperty`.
 
         """
 
@@ -2041,7 +2041,7 @@ class Relationship(
             kwargs.setdefault("passive_updates", self.passive_updates)
             kwargs.setdefault("sync_backref", self.sync_backref)
             self.back_populates = backref_key
-            relationship = Relationship(
+            relationship = RelationshipProperty(
                 parent,
                 self.secondary,
                 primaryjoin=pj,
@@ -2182,7 +2182,7 @@ class JoinCondition:
     primaryjoin: ColumnElement[bool]
     secondaryjoin: Optional[ColumnElement[bool]]
     secondary: Optional[FromClause]
-    prop: Relationship[Any]
+    prop: RelationshipProperty[Any]
 
     synchronize_pairs: _ColumnPairs
     secondary_synchronize_pairs: _ColumnPairs
@@ -2201,6 +2201,7 @@ class JoinCondition:
         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,
@@ -2210,10 +2211,11 @@ class JoinCondition:
         local_remote_pairs: Optional[_ColumnPairs] = None,
         remote_side: Any = None,
         self_referential: Any = False,
-        prop: Optional[Relationship[Any]] = None,
+        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
@@ -2248,8 +2250,6 @@ class JoinCondition:
         self._log_joins()
 
     def _log_joins(self) -> None:
-        if self.prop is None:
-            return
         log = self.prop.logger
         log.info("%s setup primary join %s", self.prop, self.primaryjoin)
         log.info("%s setup secondary join %s", self.prop, self.secondaryjoin)
@@ -2780,9 +2780,6 @@ class JoinCondition:
         )
 
     def _annotate_parentmapper(self) -> None:
-        if self.prop is None:
-            return
-
         def parentmappers_(element: _CE, **kw: Any) -> Optional[_CE]:
             if "remote" in element._annotations:
                 return element._annotate({"parentmapper": self.prop.mapper})
@@ -3040,7 +3037,9 @@ class JoinCondition:
 
     _track_overlapping_sync_targets: weakref.WeakKeyDictionary[
         ColumnElement[Any],
-        weakref.WeakKeyDictionary[Relationship[Any], ColumnElement[Any]],
+        weakref.WeakKeyDictionary[
+            RelationshipProperty[Any], ColumnElement[Any]
+        ],
     ] = weakref.WeakKeyDictionary()
 
     def _warn_for_conflicting_sync_targets(self) -> None:
@@ -3343,3 +3342,21 @@ class _ColInAnnotations:
 
     def __call__(self, c: ClauseElement) -> bool:
         return self.name in c._annotations
+
+
+class Relationship(RelationshipProperty[_T], _DeclarativeMapped[_T]):
+    """Declarative front-end for the :class:`.RelationshipProperty` class.
+
+    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:"""
index 8652591c88089b19c0774c319f9125f545934a70..c381b4ba7150e5903a5ceb7bb36d4007c39b5c4a 100644 (file)
@@ -58,7 +58,7 @@ from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
 from ..sql.selectable import Select
 
 if TYPE_CHECKING:
-    from .relationships import Relationship
+    from .relationships import RelationshipProperty
     from ..sql.elements import ColumnElement
 
 
@@ -590,7 +590,7 @@ class AbstractRelationshipLoader(LoaderStrategy):
 
 
 @log.class_logger
-@relationships.Relationship.strategy_for(do_nothing=True)
+@relationships.RelationshipProperty.strategy_for(do_nothing=True)
 class DoNothingLoader(LoaderStrategy):
     """Relationship loader that makes no change to the object's state.
 
@@ -602,8 +602,8 @@ class DoNothingLoader(LoaderStrategy):
 
 
 @log.class_logger
-@relationships.Relationship.strategy_for(lazy="noload")
-@relationships.Relationship.strategy_for(lazy=None)
+@relationships.RelationshipProperty.strategy_for(lazy="noload")
+@relationships.RelationshipProperty.strategy_for(lazy=None)
 class NoLoader(AbstractRelationshipLoader):
     """Provide loading behavior for a :class:`.Relationship`
     with "lazy=None".
@@ -643,11 +643,11 @@ class NoLoader(AbstractRelationshipLoader):
 
 
 @log.class_logger
-@relationships.Relationship.strategy_for(lazy=True)
-@relationships.Relationship.strategy_for(lazy="select")
-@relationships.Relationship.strategy_for(lazy="raise")
-@relationships.Relationship.strategy_for(lazy="raise_on_sql")
-@relationships.Relationship.strategy_for(lazy="baked_select")
+@relationships.RelationshipProperty.strategy_for(lazy=True)
+@relationships.RelationshipProperty.strategy_for(lazy="select")
+@relationships.RelationshipProperty.strategy_for(lazy="raise")
+@relationships.RelationshipProperty.strategy_for(lazy="raise_on_sql")
+@relationships.RelationshipProperty.strategy_for(lazy="baked_select")
 class LazyLoader(
     AbstractRelationshipLoader, util.MemoizedSlots, log.Identified
 ):
@@ -677,10 +677,10 @@ class LazyLoader(
     _rev_lazywhere: ColumnElement[bool]
     _rev_bind_to_col: Dict[str, ColumnElement[Any]]
 
-    parent_property: Relationship[Any]
+    parent_property: RelationshipProperty[Any]
 
     def __init__(
-        self, parent: Relationship[Any], strategy_key: Tuple[Any, ...]
+        self, parent: RelationshipProperty[Any], strategy_key: Tuple[Any, ...]
     ):
         super(LazyLoader, self).__init__(parent, strategy_key)
         self._raise_always = self.strategy_opts["lazy"] == "raise"
@@ -1336,7 +1336,7 @@ class PostLoader(AbstractRelationshipLoader):
         )
 
 
-@relationships.Relationship.strategy_for(lazy="immediate")
+@relationships.RelationshipProperty.strategy_for(lazy="immediate")
 class ImmediateLoader(PostLoader):
     __slots__ = ()
 
@@ -1426,7 +1426,7 @@ class ImmediateLoader(PostLoader):
 
 
 @log.class_logger
-@relationships.Relationship.strategy_for(lazy="subquery")
+@relationships.RelationshipProperty.strategy_for(lazy="subquery")
 class SubqueryLoader(PostLoader):
     __slots__ = ("join_depth",)
 
@@ -2067,8 +2067,8 @@ class SubqueryLoader(PostLoader):
 
 
 @log.class_logger
-@relationships.Relationship.strategy_for(lazy="joined")
-@relationships.Relationship.strategy_for(lazy=False)
+@relationships.RelationshipProperty.strategy_for(lazy="joined")
+@relationships.RelationshipProperty.strategy_for(lazy=False)
 class JoinedLoader(AbstractRelationshipLoader):
     """Provide loading behavior for a :class:`.Relationship`
     using joined eager loading.
@@ -2803,7 +2803,7 @@ class JoinedLoader(AbstractRelationshipLoader):
 
 
 @log.class_logger
-@relationships.Relationship.strategy_for(lazy="selectin")
+@relationships.RelationshipProperty.strategy_for(lazy="selectin")
 class SelectInLoader(PostLoader, util.MemoizedSlots):
     __slots__ = (
         "join_depth",
index b92969f778ee13c3a12167634abbf420c16c4252..b8c2f6e9e57a2651a31647837ba43018b1cb44dd 100644 (file)
@@ -88,7 +88,7 @@ if typing.TYPE_CHECKING:
     from .context import ORMCompileState
     from .mapper import Mapper
     from .query import Query
-    from .relationships import Relationship
+    from .relationships import RelationshipProperty
     from ..engine import Row
     from ..engine import RowMapping
     from ..sql._typing import _CE
@@ -1638,7 +1638,9 @@ class _ORMJoin(expression.Join):
 
         if isinstance(onclause, attributes.QueryableAttribute):
             if TYPE_CHECKING:
-                assert isinstance(onclause.comparator, Relationship.Comparator)
+                assert isinstance(
+                    onclause.comparator, RelationshipProperty.Comparator
+                )
             on_selectable = onclause.comparator._source_selectable()
             prop = onclause.property
             _extra_criteria += onclause._extra_criteria
@@ -1813,7 +1815,7 @@ def with_parent(
       .. versionadded:: 1.2
 
     """
-    prop_t: Relationship[Any]
+    prop_t: RelationshipProperty[Any]
 
     if isinstance(prop, str):
         raise sa_exc.ArgumentError(
index c3dfdadc4e96ef34a71867e43184afdfeb15ca16..cd7e0fd81a96be2463d799f702259087889ccc03 100644 (file)
@@ -138,6 +138,7 @@ from .langhelpers import portable_instancemethod as portable_instancemethod
 from .langhelpers import quoted_token_parser as quoted_token_parser
 from .langhelpers import ro_memoized_property as ro_memoized_property
 from .langhelpers import ro_non_memoized_property as ro_non_memoized_property
+from .langhelpers import rw_hybridproperty as rw_hybridproperty
 from .langhelpers import safe_reraise as safe_reraise
 from .langhelpers import set_creation_order as set_creation_order
 from .langhelpers import string_or_unprintable as string_or_unprintable
index 70c9bba9f8b0d02f178d0d8b46f8a7bef7dd95fa..d4dac7249c5b6690ec61d19bc69a7fba7ea65b7b 100644 (file)
@@ -178,7 +178,7 @@ def string_or_unprintable(element: Any) -> str:
 
 def clsname_as_plain_name(cls: Type[Any]) -> str:
     return " ".join(
-        n.lower() for n in re.findall(r"([A-Z][a-z]+)", cls.__name__)
+        n.lower() for n in re.findall(r"([A-Z][a-z]+|SQL)", cls.__name__)
     )
 
 
@@ -1546,6 +1546,32 @@ class hybridproperty(Generic[_T]):
         return self
 
 
+class rw_hybridproperty(Generic[_T]):
+    def __init__(self, func: Callable[..., _T]):
+        self.func = func
+        self.clslevel = func
+        self.setfn: Optional[Callable[..., Any]] = None
+
+    def __get__(self, instance: Any, owner: Any) -> _T:
+        if instance is None:
+            clsval = self.clslevel(owner)
+            return clsval
+        else:
+            return self.func(instance)
+
+    def __set__(self, instance: Any, value: Any) -> None:
+        assert self.setfn is not None
+        self.setfn(instance, value)
+
+    def setter(self, func: Callable[..., Any]) -> rw_hybridproperty[_T]:
+        self.setfn = func
+        return self
+
+    def classlevel(self, func: Callable[..., Any]) -> rw_hybridproperty[_T]:
+        self.clslevel = func
+        return self
+
+
 class hybridmethod(Generic[_T]):
     """Decorate a function as cls- or instance- level."""
 
index 897ce8249846733deab86d1af729851a713f4f9d..900b28fa494e0cf6b4f8f91be84c0b16caa095fb 100644 (file)
@@ -6,8 +6,8 @@ from sqlalchemy import String
 from sqlalchemy.orm import deferred
 from sqlalchemy.orm import Mapped
 from sqlalchemy.orm import registry
-from sqlalchemy.orm import Relationship
 from sqlalchemy.orm import relationship
+from sqlalchemy.orm import RelationshipProperty
 from sqlalchemy.orm.decl_api import declared_attr
 from sqlalchemy.orm.interfaces import MapperProperty
 from sqlalchemy.sql.schema import ForeignKey
@@ -37,11 +37,11 @@ class HasAMixin:
         return relationship("A", back_populates="bs")
 
     @declared_attr
-    def a3(cls) -> Relationship["A"]:
+    def a3(cls) -> RelationshipProperty["A"]:
         return relationship("A", back_populates="bs")
 
     @declared_attr
-    def c1(cls) -> Relationship[C]:
+    def c1(cls) -> RelationshipProperty[C]:
         return relationship(C, back_populates="bs")
 
     @declared_attr
index ec56358755c9f62e84d920507090f0af2f9e348a..5b8dfe4af0284b6dff7b58ef5138c221e6d7391c 100644 (file)
@@ -3,8 +3,8 @@ from sqlalchemy import Integer
 from sqlalchemy import String
 from sqlalchemy.orm import declared_attr
 from sqlalchemy.orm import registry
-from sqlalchemy.orm import Relationship
 from sqlalchemy.orm import relationship
+from sqlalchemy.orm import RelationshipProperty
 
 reg: registry = registry()
 
@@ -30,8 +30,8 @@ class Foo:
 
     # EXPECTED: Can't infer type from @declared_attr on function 'some_relationship' # noqa
     @declared_attr
-    # EXPECTED_MYPY: Missing type parameters for generic type "Relationship"
-    def some_relationship(cls) -> Relationship:
+    # EXPECTED_MYPY: Missing type parameters for generic type "RelationshipProperty"
+    def some_relationship(cls) -> RelationshipProperty:
         return relationship("Bar")
 
 
index e93286e40db178c38fc37bf42e5b56d6b87997ec..9f8e81d9193b976d6911716bbb48b0ecf5690d05 100644 (file)
@@ -1733,7 +1733,7 @@ class DeclarativeMultiBaseTest(
 
         assert ASub.brap.property is A.data.property
         assert isinstance(
-            ASub.brap.original_property, descriptor_props.Synonym
+            ASub.brap.original_property, descriptor_props.SynonymProperty
         )
 
     def test_alt_name_attr_subclass_relationship_inline(self):
@@ -1755,7 +1755,7 @@ class DeclarativeMultiBaseTest(
 
         assert ASub.brap.property is A.b.property
         assert isinstance(
-            ASub.brap.original_property, descriptor_props.Synonym
+            ASub.brap.original_property, descriptor_props.SynonymProperty
         )
         ASub(brap=B())
 
@@ -1768,7 +1768,9 @@ class DeclarativeMultiBaseTest(
 
         A.brap = A.data
         assert A.brap.property is A.data.property
-        assert isinstance(A.brap.original_property, descriptor_props.Synonym)
+        assert isinstance(
+            A.brap.original_property, descriptor_props.SynonymProperty
+        )
 
     def test_alt_name_attr_subclass_relationship_attrset(
         self, require_metaclass
@@ -1787,7 +1789,9 @@ class DeclarativeMultiBaseTest(
             id = Column("id", Integer, primary_key=True)
 
         assert A.brap.property is A.b.property
-        assert isinstance(A.brap.original_property, descriptor_props.Synonym)
+        assert isinstance(
+            A.brap.original_property, descriptor_props.SynonymProperty
+        )
         A(brap=B())
 
     def test_eager_order_by(self):
index f11ad31009d05224773a09e2a37c7d3142953a16..0aa38ca54d5ae25b8faf16f2ccc0572fa0bc9211 100644 (file)
@@ -28,8 +28,8 @@ from sqlalchemy.orm import Load
 from sqlalchemy.orm import load_only
 from sqlalchemy.orm import reconstructor
 from sqlalchemy.orm import registry
-from sqlalchemy.orm import Relationship
 from sqlalchemy.orm import relationship
+from sqlalchemy.orm import RelationshipProperty
 from sqlalchemy.orm import Session
 from sqlalchemy.orm import synonym
 from sqlalchemy.orm.persistence import _sort_states
@@ -3016,7 +3016,7 @@ class ComparatorFactoryTest(_fixtures.FixtureTest, AssertsCompiledSQL):
         # NOTE: this API changed in 0.8, previously __clause_element__()
         # gave the parent selecatable, now it gives the
         # primaryjoin/secondaryjoin
-        class MyFactory(Relationship.Comparator):
+        class MyFactory(RelationshipProperty.Comparator):
             __hash__ = None
 
             def __eq__(self, other):
@@ -3024,7 +3024,7 @@ class ComparatorFactoryTest(_fixtures.FixtureTest, AssertsCompiledSQL):
                     self._source_selectable().c.user_id
                 ) == func.foobar(other.id)
 
-        class MyFactory2(Relationship.Comparator):
+        class MyFactory2(RelationshipProperty.Comparator):
             __hash__ = None
 
             def __eq__(self, other):
index 883a58292eab35dea247720cc526c61353578981..ecca33062904662d7320de41c7aae303de794e87 100644 (file)
@@ -941,7 +941,7 @@ class OptionsNoPropTest(_fixtures.FixtureTest):
             lambda: (joinedload(Keyword.id).joinedload(Item.keywords),),
             'Can\'t apply "joined loader" strategy to property "Keyword.id", '
             'which is a "column property"; this loader strategy is intended '
-            'to be used with a "relationship".',
+            'to be used with a "relationship property".',
         )
 
     def test_option_against_wrong_multi_entity_type_attr_two(self):
@@ -951,8 +951,9 @@ class OptionsNoPropTest(_fixtures.FixtureTest):
             [Keyword, Item],
             lambda: (joinedload(Keyword.keywords).joinedload(Item.keywords),),
             'Can\'t apply "joined loader" strategy to property '
-            '"Keyword.keywords", which is a "column property"; this loader '
-            'strategy is intended to be used with a "relationship".',
+            '"Keyword.keywords", which is a "mapped sql expression"; '
+            "this loader "
+            'strategy is intended to be used with a "relationship property".',
         )
 
     def test_option_against_wrong_multi_entity_type_attr_three(self):
index f5da7aa8149c1f331920233e2195b6bc5dd5028e..eb9460580073257bc304d5a2235131b54958f130 100644 (file)
@@ -37,6 +37,7 @@ class _JoinFixtures:
             Column("x", Integer),
             Column("y", Integer),
         )
+
         cls.right = Table(
             "rgt",
             m,
@@ -45,6 +46,25 @@ class _JoinFixtures:
             Column("x", Integer),
             Column("y", Integer),
         )
+
+        from sqlalchemy.orm import registry
+
+        reg = registry()
+
+        cls.relationship = relationship("Otherwise")
+
+        @reg.mapped
+        class Whatever:
+            __table__ = cls.left
+
+            foo = cls.relationship
+
+        @reg.mapped
+        class Otherwise:
+            __table__ = cls.right
+
+        reg.configure()
+
         cls.right_multi_fk = Table(
             "rgt_multi_fk",
             m,
@@ -199,6 +219,7 @@ class _JoinFixtures:
             self.three_tab_b,
             self.three_tab_a,
             self.three_tab_b,
+            prop=self.relationship,
             support_sync=False,
             can_be_synced_fn=_can_sync,
             primaryjoin=and_(
@@ -214,6 +235,7 @@ class _JoinFixtures:
             self.m2mright,
             self.m2mleft,
             self.m2mright,
+            prop=self.relationship,
             secondary=self.m2msecondary,
             **kw,
         )
@@ -231,6 +253,7 @@ class _JoinFixtures:
                 self.m2mleft,
                 self.m2mright,
                 self.m2mleft,
+                prop=self.relationship,
                 secondary=self.m2msecondary,
                 primaryjoin=j1.secondaryjoin_minus_local,
                 secondaryjoin=j1.primaryjoin_minus_local,
@@ -239,17 +262,32 @@ class _JoinFixtures:
 
     def _join_fixture_o2m(self, **kw):
         return relationships.JoinCondition(
-            self.left, self.right, self.left, self.right, **kw
+            self.left,
+            self.right,
+            self.left,
+            self.right,
+            prop=self.relationship,
+            **kw,
         )
 
     def _join_fixture_m2o(self, **kw):
         return relationships.JoinCondition(
-            self.right, self.left, self.right, self.left, **kw
+            self.right,
+            self.left,
+            self.right,
+            self.left,
+            prop=self.relationship,
+            **kw,
         )
 
     def _join_fixture_o2m_selfref(self, **kw):
         return relationships.JoinCondition(
-            self.selfref, self.selfref, self.selfref, self.selfref, **kw
+            self.selfref,
+            self.selfref,
+            self.selfref,
+            self.selfref,
+            prop=self.relationship,
+            **kw,
         )
 
     def _join_fixture_m2o_selfref(self, **kw):
@@ -258,6 +296,7 @@ class _JoinFixtures:
             self.selfref,
             self.selfref,
             self.selfref,
+            prop=self.relationship,
             remote_side=set([self.selfref.c.id]),
             **kw,
         )
@@ -268,6 +307,7 @@ class _JoinFixtures:
             self.composite_selfref,
             self.composite_selfref,
             self.composite_selfref,
+            prop=self.relationship,
             **kw,
         )
 
@@ -277,6 +317,7 @@ class _JoinFixtures:
             self.composite_selfref,
             self.composite_selfref,
             self.composite_selfref,
+            prop=self.relationship,
             remote_side=set(
                 [
                     self.composite_selfref.c.id,
@@ -292,6 +333,7 @@ class _JoinFixtures:
             self.composite_selfref,
             self.composite_selfref,
             self.composite_selfref,
+            prop=self.relationship,
             primaryjoin=and_(
                 self.composite_selfref.c.group_id
                 == func.foo(self.composite_selfref.c.group_id),
@@ -307,6 +349,7 @@ class _JoinFixtures:
             self.composite_selfref,
             self.composite_selfref,
             self.composite_selfref,
+            prop=self.relationship,
             primaryjoin=and_(
                 self.composite_selfref.c.group_id
                 == func.foo(self.composite_selfref.c.group_id),
@@ -323,6 +366,7 @@ class _JoinFixtures:
             self.composite_selfref,
             self.composite_selfref,
             self.composite_selfref,
+            prop=self.relationship,
             primaryjoin=and_(
                 remote(self.composite_selfref.c.group_id)
                 == func.foo(self.composite_selfref.c.group_id),
@@ -338,6 +382,7 @@ class _JoinFixtures:
             self.right,
             self.left,
             self.right,
+            prop=self.relationship,
             primaryjoin=(self.left.c.x + self.left.c.y)
             == relationships.remote(
                 relationships.foreign(self.right.c.x * self.right.c.y)
@@ -351,6 +396,7 @@ class _JoinFixtures:
             self.right,
             self.left,
             self.right,
+            prop=self.relationship,
             primaryjoin=(self.left.c.x + self.left.c.y)
             == relationships.foreign(self.right.c.x * self.right.c.y),
             **kw,
@@ -362,6 +408,7 @@ class _JoinFixtures:
             self.right,
             self.left,
             self.right,
+            prop=self.relationship,
             primaryjoin=(self.left.c.x + self.left.c.y)
             == (self.right.c.x * self.right.c.y),
             **kw,
@@ -378,6 +425,7 @@ class _JoinFixtures:
             right,
             self.base_w_sub_rel,
             self.rel_sub,
+            prop=self.relationship,
             primaryjoin=self.base_w_sub_rel.c.sub_id == self.rel_sub.c.id,
             **kw,
         )
@@ -391,6 +439,7 @@ class _JoinFixtures:
             self.base,
             self.sub_w_base_rel,
             self.base,
+            prop=self.relationship,
             primaryjoin=self.sub_w_base_rel.c.base_id == self.base.c.id,
         )
 
@@ -407,6 +456,7 @@ class _JoinFixtures:
             right,
             self.sub,
             self.sub_w_base_rel,
+            prop=self.relationship,
             primaryjoin=self.sub_w_base_rel.c.base_id == self.base.c.id,
         )
 
@@ -420,6 +470,7 @@ class _JoinFixtures:
             right,
             self.sub,
             self.sub_w_sub_rel,
+            prop=self.relationship,
             primaryjoin=self.sub.c.id == self.sub_w_sub_rel.c.sub_id,
         )
 
@@ -433,6 +484,7 @@ class _JoinFixtures:
             right,
             self.right_w_base_rel,
             self.right_w_base_rel,
+            prop=self.relationship,
         )
 
     def _join_fixture_m2o_sub_to_joined_sub_func(self, **kw):
@@ -445,6 +497,7 @@ class _JoinFixtures:
             right,
             self.right_w_base_rel,
             self.right_w_base_rel,
+            prop=self.relationship,
             primaryjoin=self.right_w_base_rel.c.base_id
             == func.foo(self.base.c.id),
         )
@@ -453,7 +506,13 @@ class _JoinFixtures:
         left = self.base.join(self.sub, self.base.c.id == self.sub.c.id)
 
         # see test_relationships->AmbiguousJoinInterpretedAsSelfRef
-        return relationships.JoinCondition(left, self.sub, left, self.sub)
+        return relationships.JoinCondition(
+            left,
+            self.sub,
+            left,
+            self.sub,
+            prop=self.relationship,
+        )
 
     def _join_fixture_o2m_to_annotated_func(self, **kw):
         return relationships.JoinCondition(
@@ -461,6 +520,7 @@ class _JoinFixtures:
             self.right,
             self.left,
             self.right,
+            prop=self.relationship,
             primaryjoin=self.left.c.id == foreign(func.foo(self.right.c.lid)),
             **kw,
         )
@@ -471,6 +531,7 @@ class _JoinFixtures:
             self.right,
             self.left,
             self.right,
+            prop=self.relationship,
             primaryjoin=self.left.c.id == func.foo(self.right.c.lid),
             consider_as_foreign_keys={self.right.c.lid},
             **kw,
@@ -482,6 +543,7 @@ class _JoinFixtures:
             self.composite_multi_ref,
             self.composite_target,
             self.composite_multi_ref,
+            prop=self.relationship,
             consider_as_foreign_keys={
                 self.composite_multi_ref.c.uid2,
                 self.composite_multi_ref.c.oid,
@@ -495,6 +557,7 @@ class _JoinFixtures:
             self.right,
             self.left,
             self.right,
+            prop=self.relationship,
             primaryjoin=and_(
                 self.left.c.id == self.right.c.lid, self.left.c.x == 5
             ),
@@ -507,6 +570,7 @@ class _JoinFixtures:
             self.purely_single_col,
             self.purely_single_col,
             self.purely_single_col,
+            prop=self.relationship,
             support_sync=False,
             primaryjoin=self.purely_single_col.c.path.like(
                 remote(foreign(self.purely_single_col.c.path.concat("%")))
@@ -519,6 +583,7 @@ class _JoinFixtures:
             self.purely_single_col,
             self.purely_single_col,
             self.purely_single_col,
+            prop=self.relationship,
             support_sync=False,
             primaryjoin=remote(self.purely_single_col.c.path).like(
                 foreign(self.purely_single_col.c.path.concat("%"))
@@ -534,6 +599,7 @@ class _JoinFixtures:
             self.selfref,
             self.selfref,
             self.selfref,
+            prop=self.relationship,
             support_sync=False,
             primaryjoin=fn(
                 # we're putting a do-nothing annotation on
@@ -579,7 +645,7 @@ class _JoinFixtures:
             exc.SAWarning,
             "Non-simple column elements in "
             "primary join condition for property "
-            r"None - consider using remote\(\) "
+            r"Whatever.foo - consider using remote\(\) "
             "annotations to mark the remote side.",
             fn,
         )
@@ -776,7 +842,7 @@ class ColumnCollectionsTest(
         self._assert_raises_no_relevant_fks(
             self._join_fixture_compound_expression_1_non_annotated,
             r"lft.x \+ lft.y = rgt.x \* rgt.y",
-            "None",
+            "Whatever.foo",
             "primary",
         )
 
@@ -1048,7 +1114,7 @@ class DetermineJoinTest(_JoinFixtures, fixtures.TestBase, AssertsCompiledSQL):
         assert_raises_message(
             exc.AmbiguousForeignKeysError,
             "Could not determine join condition between "
-            "parent/child tables on relationship None - "
+            "parent/child tables on relationship Whatever.foo - "
             "there are multiple foreign key paths linking "
             "the tables.  Specify the 'foreign_keys' argument, "
             "providing a list of those columns which "
@@ -1059,41 +1125,45 @@ class DetermineJoinTest(_JoinFixtures, fixtures.TestBase, AssertsCompiledSQL):
             self.right_multi_fk,
             self.left,
             self.right_multi_fk,
+            prop=self.relationship,
         )
 
     def test_determine_join_no_fks_o2m(self):
         self._assert_raises_no_join(
             relationships.JoinCondition,
-            "None",
+            "Whatever.foo",
             None,
             self.left,
             self.selfref,
             self.left,
             self.selfref,
+            prop=self.relationship,
         )
 
     def test_determine_join_ambiguous_fks_m2m(self):
 
         self._assert_raises_ambig_join(
             relationships.JoinCondition,
-            "None",
+            "Whatever.foo",
             self.m2msecondary_ambig_fks,
             self.m2mleft,
             self.m2mright,
             self.m2mleft,
             self.m2mright,
+            prop=self.relationship,
             secondary=self.m2msecondary_ambig_fks,
         )
 
     def test_determine_join_no_fks_m2m(self):
         self._assert_raises_no_join(
             relationships.JoinCondition,
-            "None",
+            "Whatever.foo",
             self.m2msecondary_no_fks,
             self.m2mleft,
             self.m2mright,
             self.m2mleft,
             self.m2mright,
+            prop=self.relationship,
             secondary=self.m2msecondary_no_fks,
         )
 
@@ -1103,6 +1173,7 @@ class DetermineJoinTest(_JoinFixtures, fixtures.TestBase, AssertsCompiledSQL):
             self.m2mright,
             self.m2mleft,
             self.m2mright,
+            prop=self.relationship,
             secondary=self.m2msecondary_ambig_fks,
             consider_as_foreign_keys={
                 self.m2msecondary_ambig_fks.c.lid1,