]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Limit dc field logic to only fields that are definitely dc
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 21 Apr 2021 14:39:09 +0000 (10:39 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 21 Apr 2021 14:42:21 +0000 (10:42 -0400)
Fixed regression where recent changes to support Python dataclasses had the
inadvertent effect that an ORM mapped class could not successfully override
the ``__new__()`` method.

In this case the "__new__" method comes out as staticmethod in
cls.__dict__ vs. a function in the metaclass dict_, so comparing
using identity fails.   I was hoping not to have too much
"dataclass" hardcoded, the logic here if it were generalized
to other attribute declaration systems there
would still have a flag that indicates an attribute is part of
the "special declaration system".

Fixes: #6331
Change-Id: Ia28a44fb57c668fa2fc5cd1ff38fd511f2c747e6

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

diff --git a/doc/build/changelog/unreleased_14/6331.rst b/doc/build/changelog/unreleased_14/6331.rst
new file mode 100644 (file)
index 0000000..0bcecae
--- /dev/null
@@ -0,0 +1,7 @@
+.. change::
+    :tags: bug, orm, regression, declarative
+    :tickets: 6331
+
+    Fixed regression where recent changes to support Python dataclasses had the
+    inadvertent effect that an ORM mapped class could not successfully override
+    the ``__new__()`` method.
index 5a5d98a9588ec89952c2d32703f5e9918606a9bc..b3444f26f442e133d13223a5555fd6c2232bcf34 100644 (file)
@@ -410,7 +410,7 @@ class _ClassScanMapperConfig(_MapperConfig):
 
             def local_attributes_for_class():
                 for name, obj in vars(cls).items():
-                    yield name, obj
+                    yield name, obj, False
 
         else:
             field_names = set()
@@ -421,10 +421,10 @@ class _ClassScanMapperConfig(_MapperConfig):
                         field_names.add(field.name)
                         yield field.name, _as_dc_declaredattr(
                             field.metadata, sa_dataclass_metadata_key
-                        )
+                        ), True
                 for name, obj in vars(cls).items():
                     if name not in field_names:
-                        yield name, obj
+                        yield name, obj, False
 
         return local_attributes_for_class
 
@@ -455,7 +455,7 @@ class _ClassScanMapperConfig(_MapperConfig):
                     local_attributes_for_class, attribute_is_overridden
                 )
 
-            for name, obj in local_attributes_for_class():
+            for name, obj, is_dataclass in local_attributes_for_class():
                 if name == "__mapper_args__":
                     check_decl = _check_declared_props_nocascade(
                         obj, name, cls
@@ -567,15 +567,17 @@ class _ClassScanMapperConfig(_MapperConfig):
                     # however, check for some more common mistakes
                     else:
                         self._warn_for_decl_attributes(base, name, obj)
-                elif name not in dict_ or dict_[name] is not obj:
+                elif is_dataclass and (
+                    name not in dict_ or dict_[name] is not obj
+                ):
                     # here, we are definitely looking at the target class
                     # and not a superclass.   this is currently a
                     # dataclass-only path.  if the name is only
                     # a dataclass field and isn't in local cls.__dict__,
                     # put the object there.
-
                     # assert that the dataclass-enabled resolver agrees
                     # with what we are seeing
+
                     assert not attribute_is_overridden(name, obj)
 
                     if _is_declarative_props(obj):
@@ -607,7 +609,7 @@ class _ClassScanMapperConfig(_MapperConfig):
         column_copies = self.column_copies
         # copy mixin columns to the mapped class
 
-        for name, obj in attributes_for_class():
+        for name, obj, is_dataclass in attributes_for_class():
             if isinstance(obj, Column):
                 if attribute_is_overridden(name, obj):
                     # if column has been overridden
index e7c8f188d973202a52906b1c2f278c0de039ecda..ecc824391d37c8b03dc4593c8c0c8202e9398032 100644 (file)
@@ -2282,6 +2282,24 @@ class DeclarativeTest(DeclarativeTestBase):
 
         assert not hasattr(Foo, "data_hybrid")
 
+    def test_classes_can_override_new(self):
+        class MyTable(Base):
+            __tablename__ = "my_table"
+            id = Column(Integer, primary_key=True)
+
+            def __new__(cls, *args, **kwargs):
+                return object.__new__(cls)
+
+            def some_method(self):
+                pass
+
+            @staticmethod
+            def some_static_method(self):
+                pass
+
+        mt = MyTable(id=5)
+        eq_(mt.id, 5)
+
     @testing.requires.python36
     def test_kw_support_in_declarative_meta_init(self):
         # This will not fail if DeclarativeMeta __init__ supports **kw