]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Early-assign Base.registry to a private name
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 16 Mar 2021 19:03:22 +0000 (15:03 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 16 Mar 2021 19:14:00 +0000 (15:14 -0400)
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

doc/build/changelog/unreleased_14/6054.rst [new file with mode: 0644]
lib/sqlalchemy/orm/decl_api.py
test/orm/declarative/test_basic.py

diff --git a/doc/build/changelog/unreleased_14/6054.rst b/doc/build/changelog/unreleased_14/6054.rst
new file mode 100644 (file)
index 0000000..e272df6
--- /dev/null
@@ -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.
+
+
index 1166d307e3878fbfc0299b3504eb1551473fc39f..0266c973aecc77d83d55b632bce13cbaeab1f5d4 100644 (file)
@@ -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):
index 7fbd14b473ca0e356d9aec6260ed9ab9739264e8..e7c8f188d973202a52906b1c2f278c0de039ecda 100644 (file)
@@ -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)