diff options
| -rw-r--r-- | doc/build/changelog/unreleased_14/6054.rst | 13 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/decl_api.py | 17 | ||||
| -rw-r--r-- | test/orm/declarative/test_basic.py | 30 |
3 files changed, 59 insertions, 1 deletions
diff --git a/doc/build/changelog/unreleased_14/6054.rst b/doc/build/changelog/unreleased_14/6054.rst new file mode 100644 index 000000000..e272df649 --- /dev/null +++ b/doc/build/changelog/unreleased_14/6054.rst @@ -0,0 +1,13 @@ +.. change:: + :tags: bug, orm, declarative, regression + :tickets: 6054 + + Fixed bug where user-mapped classes that contained an attribute named + "registry" would cause conflicts with the new registry-based mapping system + when using :class:`.DeclarativeMeta`. While the attribute remains + something that can be set explicitly on a declarative base to be + consumed by the metaclass, once located it is placed under a private + class variable so it does not conflict with future subclasses that use + the same name for other purposes. + + diff --git a/lib/sqlalchemy/orm/decl_api.py b/lib/sqlalchemy/orm/decl_api.py index 1166d307e..0266c973a 100644 --- a/lib/sqlalchemy/orm/decl_api.py +++ b/lib/sqlalchemy/orm/decl_api.py @@ -25,6 +25,7 @@ from .decl_base import _DeferredMapperConfig from .decl_base import _del_attribute from .decl_base import _mapper from .descriptor_props import SynonymProperty as _orm_synonym +from .. import exc from .. import inspection from .. import util from ..sql.schema import MetaData @@ -56,8 +57,22 @@ def has_inherited_table(cls): class DeclarativeMeta(type): def __init__(cls, classname, bases, dict_, **kw): + # early-consume registry from the initial declarative base, + # assign privately to not conflict with subclass attributes named + # "registry" + reg = getattr(cls, "_sa_registry", None) + if reg is None: + reg = dict_.get("registry", None) + if not isinstance(reg, registry): + raise exc.InvalidRequestError( + "Declarative base class has no 'registry' attribute, " + "or registry is not a sqlalchemy.orm.registry() object" + ) + else: + cls._sa_registry = reg + if not cls.__dict__.get("__abstract__", False): - _as_declarative(cls.registry, cls, cls.__dict__) + _as_declarative(reg, cls, dict_) type.__init__(cls, classname, bases, dict_) def __setattr__(cls, key, value): diff --git a/test/orm/declarative/test_basic.py b/test/orm/declarative/test_basic.py index 7fbd14b47..e7c8f188d 100644 --- a/test/orm/declarative/test_basic.py +++ b/test/orm/declarative/test_basic.py @@ -1024,6 +1024,36 @@ class DeclarativeTest(DeclarativeTestBase): set([Child.name_upper.property.columns[0]]), ) + def test_class_has_registry_attr(self): + existing_registry = Base.registry + + class A(Base): + __tablename__ = "a" + + registry = {"foo": "bar"} + id = Column(Integer, primary_key=True) + data = Column(String) + + class SubA(A): + pass + + is_(Base.registry, existing_registry) + is_(inspect(A).registry, existing_registry) + eq_(A.registry, {"foo": "bar"}) + + is_(inspect(SubA).registry, existing_registry) + eq_(SubA.registry, {"foo": "bar"}) + + def test_class_does_not_have_registry_attr(self): + with assertions.expect_raises_message( + exc.InvalidRequestError, + r"Declarative base class has no 'registry' attribute, or " + r"registry is not a sqlalchemy.orm.registry\(\) object", + ): + + class Base(with_metaclass(DeclarativeMeta)): + metadata = sa.MetaData() + def test_shared_class_registry(self): reg = {} Base1 = declarative_base(testing.db, class_registry=reg) |
