]> granicus.if.org Git - python/commitdiff
bpo-32499: Add dataclasses.is_dataclass(obj), which returns True if obj is a dataclas...
authorEric V. Smith <ericvsmith@users.noreply.github.com>
Sat, 6 Jan 2018 17:41:53 +0000 (12:41 -0500)
committerGitHub <noreply@github.com>
Sat, 6 Jan 2018 17:41:53 +0000 (12:41 -0500)
Lib/dataclasses.py
Lib/test/test_dataclasses.py
Misc/NEWS.d/next/Library/2018-01-06-10-54-16.bpo-32499.koyY-4.rst [new file with mode: 0644]

index eaaed63ef2826ec8b9b3b079980929bd811263bb..b4786bf502e8c6af69f67744cac656be541484dc 100644 (file)
@@ -16,6 +16,7 @@ __all__ = ['dataclass',
            'astuple',
            'make_dataclass',
            'replace',
+           'is_dataclass',
            ]
 
 # Raised when an attempt is made to modify a frozen class.
@@ -615,11 +616,17 @@ def fields(class_or_instance):
     return tuple(f for f in fields.values() if f._field_type is _FIELD)
 
 
-def _isdataclass(obj):
+def _is_dataclass_instance(obj):
     """Returns True if obj is an instance of a dataclass."""
     return not isinstance(obj, type) and hasattr(obj, _MARKER)
 
 
+def is_dataclass(obj):
+    """Returns True if obj is a dataclass or an instance of a
+    dataclass."""
+    return hasattr(obj, _MARKER)
+
+
 def asdict(obj, *, dict_factory=dict):
     """Return the fields of a dataclass instance as a new dictionary mapping
     field names to field values.
@@ -639,12 +646,12 @@ def asdict(obj, *, dict_factory=dict):
     dataclass instances. This will also look into built-in containers:
     tuples, lists, and dicts.
     """
-    if not _isdataclass(obj):
+    if not _is_dataclass_instance(obj):
         raise TypeError("asdict() should be called on dataclass instances")
     return _asdict_inner(obj, dict_factory)
 
 def _asdict_inner(obj, dict_factory):
-    if _isdataclass(obj):
+    if _is_dataclass_instance(obj):
         result = []
         for f in fields(obj):
             value = _asdict_inner(getattr(obj, f.name), dict_factory)
@@ -678,12 +685,12 @@ def astuple(obj, *, tuple_factory=tuple):
     tuples, lists, and dicts.
     """
 
-    if not _isdataclass(obj):
+    if not _is_dataclass_instance(obj):
         raise TypeError("astuple() should be called on dataclass instances")
     return _astuple_inner(obj, tuple_factory)
 
 def _astuple_inner(obj, tuple_factory):
-    if _isdataclass(obj):
+    if _is_dataclass_instance(obj):
         result = []
         for f in fields(obj):
             value = _astuple_inner(getattr(obj, f.name), tuple_factory)
@@ -751,7 +758,7 @@ def replace(obj, **changes):
     # We're going to mutate 'changes', but that's okay because it's a new
     #  dict, even if called with 'replace(obj, **my_changes)'.
 
-    if not _isdataclass(obj):
+    if not _is_dataclass_instance(obj):
         raise TypeError("replace() should be called on dataclass instances")
 
     # It's an error to have init=False fields in 'changes'.
index ed6956398827b47a3649eec6e6fd908893cebca5..fca384d8c3c06c7daccdd8d19aa7d195ce089bb4 100755 (executable)
@@ -1,6 +1,6 @@
 from dataclasses import (
     dataclass, field, FrozenInstanceError, fields, asdict, astuple,
-    make_dataclass, replace, InitVar, Field, MISSING
+    make_dataclass, replace, InitVar, Field, MISSING, is_dataclass,
 )
 
 import pickle
@@ -1365,27 +1365,32 @@ class TestCase(unittest.TestCase):
 
         self.assertIs(C().x, int)
 
-    def test_isdataclass(self):
-        # There is no isdataclass() helper any more, but the PEP
-        #  describes how to write it, so make sure that works.  Note
-        #  that this version returns True for both classes and
-        #  instances.
-        def isdataclass(obj):
-            try:
-                fields(obj)
-                return True
-            except TypeError:
-                return False
+    def test_is_dataclass(self):
+        class NotDataClass:
+            pass
 
-        self.assertFalse(isdataclass(0))
-        self.assertFalse(isdataclass(int))
+        self.assertFalse(is_dataclass(0))
+        self.assertFalse(is_dataclass(int))
+        self.assertFalse(is_dataclass(NotDataClass))
+        self.assertFalse(is_dataclass(NotDataClass()))
 
         @dataclass
         class C:
             x: int
 
-        self.assertTrue(isdataclass(C))
-        self.assertTrue(isdataclass(C(0)))
+        @dataclass
+        class D:
+            d: C
+            e: int
+
+        c = C(10)
+        d = D(c, 4)
+
+        self.assertTrue(is_dataclass(C))
+        self.assertTrue(is_dataclass(c))
+        self.assertFalse(is_dataclass(c.x))
+        self.assertTrue(is_dataclass(d.d))
+        self.assertFalse(is_dataclass(d.e))
 
     def test_helper_fields_with_class_instance(self):
         # Check that we can call fields() on either a class or instance,
diff --git a/Misc/NEWS.d/next/Library/2018-01-06-10-54-16.bpo-32499.koyY-4.rst b/Misc/NEWS.d/next/Library/2018-01-06-10-54-16.bpo-32499.koyY-4.rst
new file mode 100644 (file)
index 0000000..bf3e99c
--- /dev/null
@@ -0,0 +1,2 @@
+Add dataclasses.is_dataclass(obj), which returns True if obj is a dataclass
+or an instance of one.