diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-01-24 17:04:27 -0500 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-02-13 14:23:04 -0500 |
| commit | e545298e35ea9f126054b337e4b5ba01988b29f7 (patch) | |
| tree | e64aea159111d5921ff01f08b1c4efb667249dfe /lib/sqlalchemy/orm/clsregistry.py | |
| parent | f1da1623b800cd4de3b71fd1b2ad5ccfde286780 (diff) | |
| download | sqlalchemy-e545298e35ea9f126054b337e4b5ba01988b29f7.tar.gz | |
establish mypy / typing approach for v2.0
large patch to get ORM / typing efforts started.
this is to support adding new test cases to mypy,
support dropping sqlalchemy2-stubs entirely from the
test suite, validate major ORM typing reorganization
to eliminate the need for the mypy plugin.
* New declarative approach which uses annotation
introspection, fixes: #7535
* Mapped[] is now at the base of all ORM constructs
that find themselves in classes, to support direct
typing without plugins
* Mypy plugin updated for new typing structures
* Mypy test suite broken out into "plugin" tests vs.
"plain" tests, and enhanced to better support test
structures where we assert that various objects are
introspected by the type checker as we expect.
as we go forward with typing, we will
add new use cases to "plain" where we can assert that
types are introspected as we expect.
* For typing support, users will be much more exposed to the
class names of things. Add these all to "sqlalchemy" import
space.
* Column(ForeignKey()) no longer needs to be `@declared_attr`
if the FK refers to a remote table
* composite() attributes mapped to a dataclass no longer
need to implement a `__composite_values__()` method
* with_variant() accepts multiple dialect names
Change-Id: I22797c0be73a8fbbd2d6f5e0c0b7258b17fe145d
Fixes: #7535
Fixes: #7551
References: #6810
Diffstat (limited to 'lib/sqlalchemy/orm/clsregistry.py')
| -rw-r--r-- | lib/sqlalchemy/orm/clsregistry.py | 46 |
1 files changed, 35 insertions, 11 deletions
diff --git a/lib/sqlalchemy/orm/clsregistry.py b/lib/sqlalchemy/orm/clsregistry.py index 3bf7ddde8..c24b3c696 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 |
