]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
use internal declarative creator for DeclarativeBaseNoMeta
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 30 May 2023 18:54:42 +0000 (14:54 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 5 Jun 2023 15:32:11 +0000 (11:32 -0400)
Fixed issue where :class:`.DeclarativeBaseNoMeta` declarative base class
would not function with non-mapped mixins or abstract classes, raising an
``AttributeError`` instead.

Fixes: #9862
Change-Id: I91cfe663530a2eb712004b9fb09d3f0cefcaeef5

doc/build/changelog/unreleased_20/9862.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_20/9862.rst b/doc/build/changelog/unreleased_20/9862.rst
new file mode 100644 (file)
index 0000000..efa6274
--- /dev/null
@@ -0,0 +1,7 @@
+.. change::
+    :tags: bug, orm
+    :tickets: 9862
+
+    Fixed issue where :class:`.DeclarativeBaseNoMeta` declarative base class
+    would not function with non-mapped mixins or abstract classes, raising an
+    ``AttributeError`` instead.
index cb25e34ca0fc134e522341746f38d609a3851006..01b78aa99c58acad26641b75ade90e299d7250e7 100644 (file)
@@ -943,7 +943,7 @@ class DeclarativeBaseNoMeta(inspection.Inspectable[InstanceState[Any]]):
             _check_not_declarative(cls, DeclarativeBaseNoMeta)
             _setup_declarative_base(cls)
         else:
-            cls._sa_registry.map_declaratively(cls)
+            _as_declarative(cls._sa_registry, cls, cls.__dict__)
 
 
 def add_mapped_attribute(
index 3ad7db0799035829e08604e70b0adfc1f2e548cc..a76e375b22b4f39e350badc2f8b68e02b15d024d 100644 (file)
@@ -1151,6 +1151,54 @@ class DeclarativeMultiBaseTest(
         eq_(a1, Address(email="two"))
         eq_(a1.user, User(name="u1"))
 
+    @testing.variation("mora", ["mixin", "abstract"])
+    def test_abstract_and_or_mixin(self, mora):
+        if mora.abstract:
+
+            class Employee(Base):
+                __abstract__ = True
+
+                id = mapped_column(Integer, primary_key=True, sort_order=-1)
+
+            class Manager(Employee):
+                __tablename__ = "manager"
+                name = mapped_column(String(50))
+                manager_data = mapped_column(String(40))
+
+            class Engineer(Employee):
+                __tablename__ = "engineer"
+
+                name = mapped_column(String(50))
+                engineer_info = mapped_column(String(40))
+
+        elif mora.mixin:
+
+            class Mixin:
+                pass
+
+            class EmployeeMixin:
+                id = mapped_column(Integer, primary_key=True, sort_order=-1)
+
+            class Manager(EmployeeMixin, Base):
+                __tablename__ = "manager"
+                name = mapped_column(String(50))
+                manager_data = mapped_column(String(40))
+
+            class Engineer(EmployeeMixin, Base):
+                __tablename__ = "engineer"
+
+                name = mapped_column(String(50))
+                engineer_info = mapped_column(String(40))
+
+        else:
+            mora.fail()
+
+        self.assert_compile(
+            select(Engineer),
+            "SELECT engineer.id, engineer.name, engineer.engineer_info "
+            "FROM engineer",
+        )
+
     def test_back_populates_setup(self):
         class User(Base):
             __tablename__ = "users"