From: Mike Bayer Date: Tue, 16 Mar 2021 19:03:22 +0000 (-0400) Subject: Early-assign Base.registry to a private name X-Git-Tag: rel_1_4_1~13^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d386552d11ea697463211c1499b2bee2f5548be7;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Early-assign Base.registry to a private name 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. Fixes: #6054 Change-Id: I1f2e04b0d74c493e7e90eadead4e861d8960a794 --- diff --git a/doc/build/changelog/unreleased_14/6054.rst b/doc/build/changelog/unreleased_14/6054.rst new file mode 100644 index 0000000000..e272df6495 --- /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 1166d307e3..0266c973ae 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 7fbd14b473..e7c8f188d9 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)