]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-118974: Add `decorator` argument to `make_dataclass` (gh-122723)
authorVictorien <65306057+Viicos@users.noreply.github.com>
Tue, 1 Oct 2024 13:51:51 +0000 (15:51 +0200)
committerGitHub <noreply@github.com>
Tue, 1 Oct 2024 13:51:51 +0000 (09:51 -0400)
This is to allow the `dataclasses.make_dataclass` infrastructure to be used with another decorator that's compliant with `typing.dataclass_transform`. The new `decorator` argument to `dataclasses.make_dataclass` is `dataclasses.dataclass`, which used to be hard coded.

Doc/library/dataclasses.rst
Lib/dataclasses.py
Lib/test/test_dataclasses/__init__.py
Misc/NEWS.d/next/Library/2024-08-06-07-24-00.gh-issue-118974.qamsCQ.rst [new file with mode: 0644]

index 51c1a427b63787292796fcb3fd41c6ef374da7e4..e34b2db02109601003019f9719187d7a37e47b44 100644 (file)
@@ -399,7 +399,7 @@ Module contents
    :func:`!astuple` raises :exc:`TypeError` if *obj* is not a dataclass
    instance.
 
-.. function:: make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, kw_only=False, slots=False, weakref_slot=False, module=None)
+.. function:: make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, kw_only=False, slots=False, weakref_slot=False, module=None, decorator=dataclass)
 
    Creates a new dataclass with name *cls_name*, fields as defined
    in *fields*, base classes as given in *bases*, and initialized
@@ -415,6 +415,11 @@ Module contents
    of the dataclass is set to that value.
    By default, it is set to the module name of the caller.
 
+   The *decorator* parameter is a callable that will be used to create the dataclass.
+   It should take the class object as a first argument and the same keyword arguments
+   as :func:`@dataclass <dataclass>`. By default, the :func:`@dataclass <dataclass>`
+   function is used.
+
    This function is not strictly required, because any Python
    mechanism for creating a new class with :attr:`!__annotations__` can
    then apply the :func:`@dataclass <dataclass>` function to convert that class to
@@ -438,6 +443,9 @@ Module contents
          def add_one(self):
              return self.x + 1
 
+   .. versionadded:: 3.14
+      Added the *decorator* parameter.
+
 .. function:: replace(obj, /, **changes)
 
    Creates a new object of the same type as *obj*, replacing
index bdda7cc6c00f5d9c8e158760270e11c261c86123..7a24f8a9e5ccee3554d40b56ae2f73d1e7d3e44b 100644 (file)
@@ -1550,7 +1550,7 @@ def _astuple_inner(obj, tuple_factory):
 def make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True,
                    repr=True, eq=True, order=False, unsafe_hash=False,
                    frozen=False, match_args=True, kw_only=False, slots=False,
-                   weakref_slot=False, module=None):
+                   weakref_slot=False, module=None, decorator=dataclass):
     """Return a new dynamically created dataclass.
 
     The dataclass name will be 'cls_name'.  'fields' is an iterable
@@ -1630,8 +1630,8 @@ def make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True,
     if module is not None:
         cls.__module__ = module
 
-    # Apply the normal decorator.
-    return dataclass(cls, init=init, repr=repr, eq=eq, order=order,
+    # Apply the normal provided decorator.
+    return decorator(cls, init=init, repr=repr, eq=eq, order=order,
                      unsafe_hash=unsafe_hash, frozen=frozen,
                      match_args=match_args, kw_only=kw_only, slots=slots,
                      weakref_slot=weakref_slot)
index 2984f4261bd2c4d155d566e323b2e87b18c2c111..2e6c49e29ce828c2d512e4de8aeaf605e173aa50 100644 (file)
@@ -4317,6 +4317,23 @@ class TestMakeDataclass(unittest.TestCase):
                 C = make_dataclass(classname, ['a', 'b'])
                 self.assertEqual(C.__name__, classname)
 
+    def test_dataclass_decorator_default(self):
+        C = make_dataclass('C', [('x', int)], decorator=dataclass)
+        c = C(10)
+        self.assertEqual(c.x, 10)
+
+    def test_dataclass_custom_decorator(self):
+        def custom_dataclass(cls, *args, **kwargs):
+            dc = dataclass(cls, *args, **kwargs)
+            dc.__custom__ = True
+            return dc
+
+        C = make_dataclass('C', [('x', int)], decorator=custom_dataclass)
+        c = C(10)
+        self.assertEqual(c.x, 10)
+        self.assertEqual(c.__custom__, True)
+
+
 class TestReplace(unittest.TestCase):
     def test(self):
         @dataclass(frozen=True)
diff --git a/Misc/NEWS.d/next/Library/2024-08-06-07-24-00.gh-issue-118974.qamsCQ.rst b/Misc/NEWS.d/next/Library/2024-08-06-07-24-00.gh-issue-118974.qamsCQ.rst
new file mode 100644 (file)
index 0000000..79480a6
--- /dev/null
@@ -0,0 +1,2 @@
+Add ``decorator`` parameter to :func:`dataclasses.make_dataclass`
+to customize the functional creation of dataclasses.