summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm/clsregistry.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/orm/clsregistry.py')
-rw-r--r--lib/sqlalchemy/orm/clsregistry.py46
1 files changed, 35 insertions, 11 deletions
diff --git a/lib/sqlalchemy/orm/clsregistry.py b/lib/sqlalchemy/orm/clsregistry.py
index ac6b0fd4c..037b70257 100644
--- a/lib/sqlalchemy/orm/clsregistry.py
+++ b/lib/sqlalchemy/orm/clsregistry.py
@@ -10,11 +10,14 @@ This system allows specification of classes and expressions used in
:func:`_orm.relationship` using strings.
"""
+import re
+from typing import MutableMapping
+from typing import Union
import weakref
from . import attributes
from . import interfaces
-from .descriptor_props import SynonymProperty
+from .descriptor_props import Synonym
from .properties import ColumnProperty
from .util import class_mapper
from .. import exc
@@ -22,6 +25,8 @@ from .. import inspection
from .. import util
from ..sql.schema import _get_table_key
+_ClsRegistryType = MutableMapping[str, Union[type, "ClsRegistryToken"]]
+
# strong references to registries which we place in
# the _decl_class_registry, which is usually weak referencing.
# the internal registries here link to classes with weakrefs and remove
@@ -118,7 +123,13 @@ def _key_is_empty(key, decl_class_registry, test):
return not test(thing)
-class _MultipleClassMarker:
+class ClsRegistryToken:
+ """an object that can be in the registry._class_registry as a value."""
+
+ __slots__ = ()
+
+
+class _MultipleClassMarker(ClsRegistryToken):
"""refers to multiple classes of the same name
within _decl_class_registry.
@@ -182,7 +193,7 @@ class _MultipleClassMarker:
self.contents.add(weakref.ref(item, self._remove_item))
-class _ModuleMarker:
+class _ModuleMarker(ClsRegistryToken):
"""Refers to a module name within
_decl_class_registry.
@@ -281,7 +292,7 @@ class _GetColumns:
desc = mp.all_orm_descriptors[key]
if desc.extension_type is interfaces.NOT_EXTENSION:
prop = desc.property
- if isinstance(prop, SynonymProperty):
+ if isinstance(prop, Synonym):
key = prop.name
elif not isinstance(prop, ColumnProperty):
raise exc.InvalidRequestError(
@@ -372,13 +383,26 @@ class _class_resolver:
return self.fallback[key]
def _raise_for_name(self, name, err):
- raise exc.InvalidRequestError(
- "When initializing mapper %s, expression %r failed to "
- "locate a name (%r). If this is a class name, consider "
- "adding this relationship() to the %r class after "
- "both dependent classes have been defined."
- % (self.prop.parent, self.arg, name, self.cls)
- ) from err
+ generic_match = re.match(r"(.+)\[(.+)\]", name)
+
+ if generic_match:
+ raise exc.InvalidRequestError(
+ f"When initializing mapper {self.prop.parent}, "
+ f'expression "relationship({self.arg!r})" seems to be '
+ "using a generic class as the argument to relationship(); "
+ "please state the generic argument "
+ "using an annotation, e.g. "
+ f'"{self.prop.key}: Mapped[{generic_match.group(1)}'
+ f'[{generic_match.group(2)}]] = relationship()"'
+ ) from err
+ else:
+ raise exc.InvalidRequestError(
+ "When initializing mapper %s, expression %r failed to "
+ "locate a name (%r). If this is a class name, consider "
+ "adding this relationship() to the %r class after "
+ "both dependent classes have been defined."
+ % (self.prop.parent, self.arg, name, self.cls)
+ ) from err
def _resolve_name(self):
name = self.arg