From e6370fb962bef537b79cb460c75f2720a4815bc9 Mon Sep 17 00:00:00 2001 From: Batuhan Taskaya Date: Tue, 8 Sep 2020 23:42:12 +0300 Subject: [PATCH 1/5] bpo-41747: Ensure all dataclass methods uses their parents' qualname --- Lib/dataclasses.py | 4 +++- Lib/test/test_dataclasses.py | 12 ++++++++++++ .../Library/2020-09-08-23-41-29.bpo-41747.M6wLKv.rst | 2 ++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2020-09-08-23-41-29.bpo-41747.M6wLKv.rst diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 530d3e99574e8e..e18cfb2ff50fbf 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -7,7 +7,7 @@ import builtins import functools import _thread -from types import GenericAlias +from types import FunctionType, GenericAlias __all__ = ['dataclass', @@ -753,6 +753,8 @@ def _get_field(cls, a_name, a_type): def _set_new_attribute(cls, name, value): # Never overwrites an existing attribute. Returns True if the # attribute already exists. + if isinstance(value, FunctionType): + value.__qualname__ = f"{cls.__qualname__}.{value.__name__}" if name in cls.__dict__: return True setattr(cls, name, value) diff --git a/Lib/test/test_dataclasses.py b/Lib/test/test_dataclasses.py index b20103bdce51cb..b29bc9e53ed599 100644 --- a/Lib/test/test_dataclasses.py +++ b/Lib/test/test_dataclasses.py @@ -1934,6 +1934,18 @@ class R: self.assertEqual(new_sample.x, another_new_sample.x) self.assertEqual(sample.y, another_new_sample.y) + def test_dataclasses_qualnames(self): + @dataclass + class A: + x: int + y: int + + self.assertEqual(A.__init__.__name__, "__init__") + self.assertEqual(A.__init__.__qualname__, "TestCase.test_dataclasses_qualnames..A.__init__") + + with self.assertRaisesRegex(TypeError, r"A.__init__\(\) missing"): + A() + class TestFieldNoAnnotation(unittest.TestCase): def test_field_without_annotation(self): diff --git a/Misc/NEWS.d/next/Library/2020-09-08-23-41-29.bpo-41747.M6wLKv.rst b/Misc/NEWS.d/next/Library/2020-09-08-23-41-29.bpo-41747.M6wLKv.rst new file mode 100644 index 00000000000000..f7a0480cfce261 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-09-08-23-41-29.bpo-41747.M6wLKv.rst @@ -0,0 +1,2 @@ +Ensure all methods from a :func:`dataclasses.dataclass` uses the +``__qualname__`` of the class they belong. Patch by Batuhan Taskaya. From 109c8f08bb82d17fa463f0eb103ab34041c8682d Mon Sep 17 00:00:00 2001 From: Batuhan Taskaya Date: Wed, 9 Sep 2020 00:26:44 +0300 Subject: [PATCH 2/5] Apply suggestion by @pablogsal --- .../next/Library/2020-09-08-23-41-29.bpo-41747.M6wLKv.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2020-09-08-23-41-29.bpo-41747.M6wLKv.rst b/Misc/NEWS.d/next/Library/2020-09-08-23-41-29.bpo-41747.M6wLKv.rst index f7a0480cfce261..6271afd191fc66 100644 --- a/Misc/NEWS.d/next/Library/2020-09-08-23-41-29.bpo-41747.M6wLKv.rst +++ b/Misc/NEWS.d/next/Library/2020-09-08-23-41-29.bpo-41747.M6wLKv.rst @@ -1,2 +1,3 @@ -Ensure all methods from a :func:`dataclasses.dataclass` uses the -``__qualname__`` of the class they belong. Patch by Batuhan Taskaya. +Ensure all methods belonging to :func:`dataclasses.dataclass` objects +now have the proper ``__qualname__`` attribute referring to the class +they belong. Patch by Batuhan Taskaya. From 53bb73825652b8b1eae0e7af6fa0564517154232 Mon Sep 17 00:00:00 2001 From: Batuhan Taskaya Date: Wed, 9 Sep 2020 17:21:34 +0300 Subject: [PATCH 3/5] Extract functionality to _set_qualname, add tests for all auto-generated methods --- Lib/dataclasses.py | 11 ++++++++--- Lib/test/test_dataclasses.py | 16 ++++++++++++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index e18cfb2ff50fbf..6dfcd69a24fb97 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -749,14 +749,19 @@ def _get_field(cls, a_name, a_type): return f +def _set_qualname(cls, value): + # Ensure that the functions returned from _create_fn uses the proper + # __qualname__ (the class they belong) + if isinstance(value, FunctionType): + value.__qualname__ = f"{cls.__qualname__}.{value.__name__}" + return value def _set_new_attribute(cls, name, value): # Never overwrites an existing attribute. Returns True if the # attribute already exists. - if isinstance(value, FunctionType): - value.__qualname__ = f"{cls.__qualname__}.{value.__name__}" if name in cls.__dict__: return True + _set_qualname(cls, value) setattr(cls, name, value) return False @@ -771,7 +776,7 @@ def _hash_set_none(cls, fields, globals): def _hash_add(cls, fields, globals): flds = [f for f in fields if (f.compare if f.hash is None else f.hash)] - return _hash_fn(flds, globals) + return _set_qualname(cls, _hash_fn(flds, globals)) def _hash_exception(cls, fields, globals): # Raise an exception. diff --git a/Lib/test/test_dataclasses.py b/Lib/test/test_dataclasses.py index b29bc9e53ed599..7f390d4725708d 100644 --- a/Lib/test/test_dataclasses.py +++ b/Lib/test/test_dataclasses.py @@ -1935,13 +1935,25 @@ class R: self.assertEqual(sample.y, another_new_sample.y) def test_dataclasses_qualnames(self): - @dataclass + @dataclass(order=True, unsafe_hash=True, frozen=True) class A: x: int y: int self.assertEqual(A.__init__.__name__, "__init__") - self.assertEqual(A.__init__.__qualname__, "TestCase.test_dataclasses_qualnames..A.__init__") + for function in ( + '__eq__', + '__lt__', + '__le__', + '__gt__', + '__ge__', + '__hash__', + '__init__', + '__repr__', + '__setattr__', + '__delattr__', + ): + self.assertEqual(getattr(A, function).__qualname__, f"TestCase.test_dataclasses_qualnames..A.{function}") with self.assertRaisesRegex(TypeError, r"A.__init__\(\) missing"): A() From e222153457d4cdc7cadc7efc3242282c5996428b Mon Sep 17 00:00:00 2001 From: Batuhan Taskaya Date: Wed, 9 Sep 2020 17:45:19 +0300 Subject: [PATCH 4/5] escape dot Co-authored-by: Serhiy Storchaka --- Lib/test/test_dataclasses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_dataclasses.py b/Lib/test/test_dataclasses.py index 7f390d4725708d..a0f57a8053cebe 100644 --- a/Lib/test/test_dataclasses.py +++ b/Lib/test/test_dataclasses.py @@ -1955,7 +1955,7 @@ class A: ): self.assertEqual(getattr(A, function).__qualname__, f"TestCase.test_dataclasses_qualnames..A.{function}") - with self.assertRaisesRegex(TypeError, r"A.__init__\(\) missing"): + with self.assertRaisesRegex(TypeError, r"A\.__init__\(\) missing"): A() From 6f221685f4c20e1e2affde90d51d7f89c779b58f Mon Sep 17 00:00:00 2001 From: Batuhan Taskaya Date: Wed, 9 Sep 2020 21:23:26 +0300 Subject: [PATCH 5/5] Address suggestions --- Lib/dataclasses.py | 2 +- .../next/Library/2020-09-08-23-41-29.bpo-41747.M6wLKv.rst | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 6dfcd69a24fb97..a091545b824ed0 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -751,7 +751,7 @@ def _get_field(cls, a_name, a_type): def _set_qualname(cls, value): # Ensure that the functions returned from _create_fn uses the proper - # __qualname__ (the class they belong) + # __qualname__ (the class they belong to). if isinstance(value, FunctionType): value.__qualname__ = f"{cls.__qualname__}.{value.__name__}" return value diff --git a/Misc/NEWS.d/next/Library/2020-09-08-23-41-29.bpo-41747.M6wLKv.rst b/Misc/NEWS.d/next/Library/2020-09-08-23-41-29.bpo-41747.M6wLKv.rst index 6271afd191fc66..0869462f5bf9d5 100644 --- a/Misc/NEWS.d/next/Library/2020-09-08-23-41-29.bpo-41747.M6wLKv.rst +++ b/Misc/NEWS.d/next/Library/2020-09-08-23-41-29.bpo-41747.M6wLKv.rst @@ -1,3 +1,3 @@ -Ensure all methods belonging to :func:`dataclasses.dataclass` objects -now have the proper ``__qualname__`` attribute referring to the class -they belong. Patch by Batuhan Taskaya. +Ensure all methods that generated from :func:`dataclasses.dataclass` +objects now have the proper ``__qualname__`` attribute referring to +the class they belong to. Patch by Batuhan Taskaya.