From bc50b86cafd6ff9a6cdd4b3566ff6e55a239cf25 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Thu, 14 Dec 2023 22:30:34 -0500 Subject: [PATCH 01/16] vendor dataclasses' asdict --- .../src/hypothesis/internal/compat.py | 68 +++++++++++++++++++ .../hypothesis/strategies/_internal/utils.py | 3 +- .../tests/cover/test_searchstrategy.py | 15 +++- 3 files changed, 84 insertions(+), 2 deletions(-) diff --git a/hypothesis-python/src/hypothesis/internal/compat.py b/hypothesis-python/src/hypothesis/internal/compat.py index 3eaed1eba1..d166fefd6a 100644 --- a/hypothesis-python/src/hypothesis/internal/compat.py +++ b/hypothesis-python/src/hypothesis/internal/compat.py @@ -9,9 +9,12 @@ # obtain one at https://mozilla.org/MPL/2.0/. import codecs +import copy +import dataclasses import inspect import platform import sys +import types import typing from functools import partial from typing import Any, ForwardRef, get_args @@ -188,3 +191,68 @@ def bad_django_TestCase(runner): from hypothesis.extra.django._impl import HypothesisTestCase return not isinstance(runner, HypothesisTestCase) + + +# _ATOMIC_TYPES was introduced as an optimization in 3.12's dataclasses. +_ATOMIC_TYPES = frozenset( + { + types.NoneType, + bool, + int, + float, + str, + complex, + bytes, + types.EllipsisType, + types.NotImplementedType, + types.CodeType, + types.BuiltinFunctionType, + types.FunctionType, + type, + range, + property, + } +) + + +def dataclass_asdict(obj, *, dict_factory=dict): + """ + A vendored variant of dataclasses.asdict. Includes the bugfix for + defaultdicts (cpython/32056) for all versions. See also issues/3812. + """ + if not dataclasses._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 type(obj) in _ATOMIC_TYPES: + return obj + elif dataclasses._is_dataclass_instance(obj): + if dict_factory is dict: + return { + f.name: _asdict_inner(getattr(obj, f.name), dict) + for f in dataclasses.fields(obj) + } + else: + result = [] + for f in dataclasses.fields(obj): + value = _asdict_inner(getattr(obj, f.name), dict_factory) + result.append((f.name, value)) + return dict_factory(result) + elif isinstance(obj, tuple) and hasattr(obj, "_fields"): + return type(obj)(*[_asdict_inner(v, dict_factory) for v in obj]) + elif isinstance(obj, (list, tuple)): + return type(obj)(_asdict_inner(v, dict_factory) for v in obj) + elif isinstance(obj, dict): + if hasattr(type(obj), "default_factory"): + result = type(obj)(obj.default_factory) + for k, v in obj.items(): + result[_asdict_inner(k, dict_factory)] = _asdict_inner(v, dict_factory) + return result + return type(obj)( + (_asdict_inner(k, dict_factory), _asdict_inner(v, dict_factory)) + for k, v in obj.items() + ) + else: + return copy.deepcopy(obj) diff --git a/hypothesis-python/src/hypothesis/strategies/_internal/utils.py b/hypothesis-python/src/hypothesis/strategies/_internal/utils.py index 995b179b40..b2a7661cd6 100644 --- a/hypothesis-python/src/hypothesis/strategies/_internal/utils.py +++ b/hypothesis-python/src/hypothesis/strategies/_internal/utils.py @@ -16,6 +16,7 @@ import attr from hypothesis.internal.cache import LRUReusedCache +from hypothesis.internal.compat import dataclass_asdict from hypothesis.internal.floats import float_to_int from hypothesis.internal.reflection import proxies from hypothesis.vendor.pretty import pretty @@ -177,7 +178,7 @@ def to_jsonable(obj: object) -> object: and dcs.is_dataclass(obj) and not isinstance(obj, type) ): - return to_jsonable(dcs.asdict(obj)) + return to_jsonable(dataclass_asdict(obj)) if attr.has(type(obj)): return to_jsonable(attr.asdict(obj, recurse=False)) # type: ignore if (pyd := sys.modules.get("pydantic")) and isinstance(obj, pyd.BaseModel): diff --git a/hypothesis-python/tests/cover/test_searchstrategy.py b/hypothesis-python/tests/cover/test_searchstrategy.py index b6b6c498e9..8816a79a9e 100644 --- a/hypothesis-python/tests/cover/test_searchstrategy.py +++ b/hypothesis-python/tests/cover/test_searchstrategy.py @@ -8,8 +8,9 @@ # v. 2.0. If a copy of the MPL was not distributed with this file, You can # obtain one at https://mozilla.org/MPL/2.0/. +import dataclasses import functools -from collections import namedtuple +from collections import defaultdict, namedtuple import pytest @@ -90,3 +91,15 @@ def test_flatmap_with_invalid_expand(): def test_jsonable(): assert isinstance(to_jsonable(object()), str) + + +@dataclasses.dataclass() +class HasDefaultDict: + x: defaultdict + + +def test_jsonable_defaultdict(): + obj = HasDefaultDict(defaultdict(list)) + obj.x["a"] = [42] + json = to_jsonable(obj) + assert json == {"x": {"a": [42]}} From 9dff2235ea5d9f5f2a111f42f3ec21a087966e12 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Thu, 14 Dec 2023 22:31:19 -0500 Subject: [PATCH 02/16] don't create jsonable args if no testcase callbacks --- .../src/hypothesis/strategies/_internal/core.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hypothesis-python/src/hypothesis/strategies/_internal/core.py b/hypothesis-python/src/hypothesis/strategies/_internal/core.py index a5a862635a..03653c61e1 100644 --- a/hypothesis-python/src/hypothesis/strategies/_internal/core.py +++ b/hypothesis-python/src/hypothesis/strategies/_internal/core.py @@ -77,6 +77,7 @@ from hypothesis.internal.conjecture.utils import calc_label_from_cls, check_sample from hypothesis.internal.entropy import get_seeder_and_restorer from hypothesis.internal.floats import float_of +from hypothesis.internal.observability import TESTCASE_CALLBACKS from hypothesis.internal.reflection import ( define_function_signature, get_pretty_function_description, @@ -2103,7 +2104,9 @@ def draw(self, strategy: SearchStrategy[Ex], label: Any = None) -> Ex: self.count += 1 printer = RepresentationPrinter(context=current_build_context()) desc = f"Draw {self.count}{'' if label is None else f' ({label})'}: " - self.conjecture_data._observability_args[desc] = to_jsonable(result) + if TESTCASE_CALLBACKS: + self.conjecture_data._observability_args[desc] = to_jsonable(result) + printer.text(desc) printer.pretty(result) note(printer.getvalue()) From dab9683d70b53aebcba4673bcf94bbebe58793b8 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Thu, 14 Dec 2023 22:44:57 -0500 Subject: [PATCH 03/16] nocover error case --- hypothesis-python/src/hypothesis/internal/compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hypothesis-python/src/hypothesis/internal/compat.py b/hypothesis-python/src/hypothesis/internal/compat.py index d166fefd6a..ca7b05857f 100644 --- a/hypothesis-python/src/hypothesis/internal/compat.py +++ b/hypothesis-python/src/hypothesis/internal/compat.py @@ -220,7 +220,7 @@ def dataclass_asdict(obj, *, dict_factory=dict): A vendored variant of dataclasses.asdict. Includes the bugfix for defaultdicts (cpython/32056) for all versions. See also issues/3812. """ - if not dataclasses._is_dataclass_instance(obj): + if not dataclasses._is_dataclass_instance(obj): # pragma: no cover raise TypeError("asdict() should be called on dataclass instances") return _asdict_inner(obj, dict_factory) From 5d1917850eb3c1e8b689fc0c6776bbf8f803ecf9 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Thu, 14 Dec 2023 22:52:27 -0500 Subject: [PATCH 04/16] only use vendored implementation on old versions --- .../src/hypothesis/strategies/_internal/utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/hypothesis-python/src/hypothesis/strategies/_internal/utils.py b/hypothesis-python/src/hypothesis/strategies/_internal/utils.py index b2a7661cd6..9b07117a34 100644 --- a/hypothesis-python/src/hypothesis/strategies/_internal/utils.py +++ b/hypothesis-python/src/hypothesis/strategies/_internal/utils.py @@ -178,7 +178,11 @@ def to_jsonable(obj: object) -> object: and dcs.is_dataclass(obj) and not isinstance(obj, type) ): - return to_jsonable(dataclass_asdict(obj)) + if sys.version_info[:2] < (3, 12): + # see issue #3812 + return to_jsonable(dataclass_asdict(obj)) + else: + return to_jsonable(dcs.asdict(obj)) if attr.has(type(obj)): return to_jsonable(attr.asdict(obj, recurse=False)) # type: ignore if (pyd := sys.modules.get("pydantic")) and isinstance(obj, pyd.BaseModel): From ef3b12d3293275a9ecf17e686b0fa7b6307938e1 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Thu, 14 Dec 2023 22:57:45 -0500 Subject: [PATCH 05/16] remove _ATOMIC_TYPES more trouble than it's worth to backport --- .../src/hypothesis/internal/compat.py | 27 +------------------ 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/hypothesis-python/src/hypothesis/internal/compat.py b/hypothesis-python/src/hypothesis/internal/compat.py index ca7b05857f..4dd9cf517f 100644 --- a/hypothesis-python/src/hypothesis/internal/compat.py +++ b/hypothesis-python/src/hypothesis/internal/compat.py @@ -14,7 +14,6 @@ import inspect import platform import sys -import types import typing from functools import partial from typing import Any, ForwardRef, get_args @@ -193,28 +192,6 @@ def bad_django_TestCase(runner): return not isinstance(runner, HypothesisTestCase) -# _ATOMIC_TYPES was introduced as an optimization in 3.12's dataclasses. -_ATOMIC_TYPES = frozenset( - { - types.NoneType, - bool, - int, - float, - str, - complex, - bytes, - types.EllipsisType, - types.NotImplementedType, - types.CodeType, - types.BuiltinFunctionType, - types.FunctionType, - type, - range, - property, - } -) - - def dataclass_asdict(obj, *, dict_factory=dict): """ A vendored variant of dataclasses.asdict. Includes the bugfix for @@ -226,9 +203,7 @@ def dataclass_asdict(obj, *, dict_factory=dict): def _asdict_inner(obj, dict_factory): - if type(obj) in _ATOMIC_TYPES: - return obj - elif dataclasses._is_dataclass_instance(obj): + if dataclasses._is_dataclass_instance(obj): if dict_factory is dict: return { f.name: _asdict_inner(getattr(obj, f.name), dict) From 917dab335c9d52b45597f3ff13f1e7331300bdb8 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Thu, 14 Dec 2023 23:00:52 -0500 Subject: [PATCH 06/16] add note of when to drop --- hypothesis-python/src/hypothesis/internal/compat.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hypothesis-python/src/hypothesis/internal/compat.py b/hypothesis-python/src/hypothesis/internal/compat.py index 4dd9cf517f..65ba56ed80 100644 --- a/hypothesis-python/src/hypothesis/internal/compat.py +++ b/hypothesis-python/src/hypothesis/internal/compat.py @@ -196,6 +196,9 @@ def dataclass_asdict(obj, *, dict_factory=dict): """ A vendored variant of dataclasses.asdict. Includes the bugfix for defaultdicts (cpython/32056) for all versions. See also issues/3812. + + This should be removed whenever we drop support for 3.11. We can use the + standard dataclasses.asdict after that point. """ if not dataclasses._is_dataclass_instance(obj): # pragma: no cover raise TypeError("asdict() should be called on dataclass instances") From 382f075edf1a2dec08724d145984316359d509a2 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 15 Dec 2023 00:58:13 -0500 Subject: [PATCH 07/16] nocover non-default dict_factory --- hypothesis-python/src/hypothesis/internal/compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hypothesis-python/src/hypothesis/internal/compat.py b/hypothesis-python/src/hypothesis/internal/compat.py index 65ba56ed80..8b2123b467 100644 --- a/hypothesis-python/src/hypothesis/internal/compat.py +++ b/hypothesis-python/src/hypothesis/internal/compat.py @@ -212,7 +212,7 @@ def _asdict_inner(obj, dict_factory): f.name: _asdict_inner(getattr(obj, f.name), dict) for f in dataclasses.fields(obj) } - else: + else: # pragma: no cover result = [] for f in dataclasses.fields(obj): value = _asdict_inner(getattr(obj, f.name), dict_factory) From dd97656e7ab0c9541fc96e35c128343448979bb1 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 15 Dec 2023 00:58:30 -0500 Subject: [PATCH 08/16] add coverage tests for dataclass_asdict --- hypothesis-python/tests/cover/test_compat.py | 24 +++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/hypothesis-python/tests/cover/test_compat.py b/hypothesis-python/tests/cover/test_compat.py index 17e0a73469..23612a516c 100644 --- a/hypothesis-python/tests/cover/test_compat.py +++ b/hypothesis-python/tests/cover/test_compat.py @@ -9,6 +9,7 @@ # obtain one at https://mozilla.org/MPL/2.0/. import math +from collections import defaultdict, namedtuple from dataclasses import dataclass from functools import partial from inspect import Parameter, Signature, signature @@ -16,7 +17,7 @@ import pytest -from hypothesis.internal.compat import ceil, floor, get_type_hints +from hypothesis.internal.compat import ceil, dataclass_asdict, floor, get_type_hints floor_ceil_values = [ -10.7, @@ -106,3 +107,24 @@ def func(a, b: int, *c: str, d: Optional[int] = None): ) def test_get_hints_through_partial(pf, names): assert set(get_type_hints(pf)) == set(names.split()) + + +@dataclass +class FilledWithStuff: + a: list + b: tuple + c: namedtuple + d: dict + e: defaultdict + + +def test_dataclass_asdict(): + ANamedTuple = namedtuple("ANamedTuple", ("with_some_field")) + obj = FilledWithStuff(a=[1], b=(2), c=ANamedTuple(3), d={4: 5}, e=defaultdict(list)) + assert dataclass_asdict(obj) == { + "a": [1], + "b": (2), + "c": ANamedTuple(3), + "d": {4: 5}, + "e": {}, + } From 82916e0d1ed4e3e1aef37866cd644d300f57538b Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 15 Dec 2023 01:46:18 -0500 Subject: [PATCH 09/16] nocover 3.12 path --- hypothesis-python/src/hypothesis/strategies/_internal/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hypothesis-python/src/hypothesis/strategies/_internal/utils.py b/hypothesis-python/src/hypothesis/strategies/_internal/utils.py index 9b07117a34..6157e4af95 100644 --- a/hypothesis-python/src/hypothesis/strategies/_internal/utils.py +++ b/hypothesis-python/src/hypothesis/strategies/_internal/utils.py @@ -181,7 +181,7 @@ def to_jsonable(obj: object) -> object: if sys.version_info[:2] < (3, 12): # see issue #3812 return to_jsonable(dataclass_asdict(obj)) - else: + else: # pragma: no cover return to_jsonable(dcs.asdict(obj)) if attr.has(type(obj)): return to_jsonable(attr.asdict(obj, recurse=False)) # type: ignore From afe9dcae3a34f1e8916d09d8813f1786a11e86d0 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 15 Dec 2023 01:46:29 -0500 Subject: [PATCH 10/16] cover remaining to_jsonable tests --- .../tests/cover/test_searchstrategy.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/hypothesis-python/tests/cover/test_searchstrategy.py b/hypothesis-python/tests/cover/test_searchstrategy.py index 8816a79a9e..9a8f678ea5 100644 --- a/hypothesis-python/tests/cover/test_searchstrategy.py +++ b/hypothesis-python/tests/cover/test_searchstrategy.py @@ -12,6 +12,7 @@ import functools from collections import defaultdict, namedtuple +import attr import pytest from hypothesis.errors import InvalidArgument @@ -97,9 +98,20 @@ def test_jsonable(): class HasDefaultDict: x: defaultdict +@attr.s +class AttrsClass: + n = attr.ib() def test_jsonable_defaultdict(): obj = HasDefaultDict(defaultdict(list)) obj.x["a"] = [42] - json = to_jsonable(obj) - assert json == {"x": {"a": [42]}} + assert to_jsonable(obj) == {"x": {"a": [42]}} + +def test_jsonable_attrs(): + obj = AttrsClass(n=10) + assert to_jsonable(obj) == {"n": 10} + +def test_jsonable_namedtuple(): + Obj = namedtuple("Obj", ("x")) + obj = Obj(10) + assert to_jsonable(obj) == {"x": 10} From 85021de56213db09d9a9a7ee6a77de63d73ffb78 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 15 Dec 2023 01:48:10 -0500 Subject: [PATCH 11/16] linting --- hypothesis-python/tests/cover/test_searchstrategy.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hypothesis-python/tests/cover/test_searchstrategy.py b/hypothesis-python/tests/cover/test_searchstrategy.py index 9a8f678ea5..b0f49c2520 100644 --- a/hypothesis-python/tests/cover/test_searchstrategy.py +++ b/hypothesis-python/tests/cover/test_searchstrategy.py @@ -98,19 +98,23 @@ def test_jsonable(): class HasDefaultDict: x: defaultdict + @attr.s class AttrsClass: n = attr.ib() + def test_jsonable_defaultdict(): obj = HasDefaultDict(defaultdict(list)) obj.x["a"] = [42] assert to_jsonable(obj) == {"x": {"a": [42]}} + def test_jsonable_attrs(): obj = AttrsClass(n=10) assert to_jsonable(obj) == {"n": 10} + def test_jsonable_namedtuple(): Obj = namedtuple("Obj", ("x")) obj = Obj(10) From 720d918de0ce3805d3861ddf6f3106f567c1d343 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 15 Dec 2023 02:22:28 -0500 Subject: [PATCH 12/16] add release notes --- hypothesis-python/RELEASE.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 hypothesis-python/RELEASE.rst diff --git a/hypothesis-python/RELEASE.rst b/hypothesis-python/RELEASE.rst new file mode 100644 index 0000000000..4d00de0ece --- /dev/null +++ b/hypothesis-python/RELEASE.rst @@ -0,0 +1,3 @@ +RELEASE_TYPE: patch + +This patch fixes a bug introduced in :ref:`version 6.92.0 `, where using :func:`~python:dataclasses.dataclass` with a :class:`~python:collections.defaultdict` field as a strategy argument would error. From e9ba14c940dae884efef791fbf58ec076587df38 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 15 Dec 2023 15:28:31 -0500 Subject: [PATCH 13/16] move version guard to compat.py --- hypothesis-python/src/hypothesis/internal/compat.py | 5 +++++ .../src/hypothesis/strategies/_internal/utils.py | 6 +----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/hypothesis-python/src/hypothesis/internal/compat.py b/hypothesis-python/src/hypothesis/internal/compat.py index 8b2123b467..ab0a7e3106 100644 --- a/hypothesis-python/src/hypothesis/internal/compat.py +++ b/hypothesis-python/src/hypothesis/internal/compat.py @@ -205,6 +205,11 @@ def dataclass_asdict(obj, *, dict_factory=dict): return _asdict_inner(obj, dict_factory) +if sys.version_info[:2] >= (3, 12): + # see issue #3812 + dataclass_asdict = dataclasses.asdict + + def _asdict_inner(obj, dict_factory): if dataclasses._is_dataclass_instance(obj): if dict_factory is dict: diff --git a/hypothesis-python/src/hypothesis/strategies/_internal/utils.py b/hypothesis-python/src/hypothesis/strategies/_internal/utils.py index 6157e4af95..b2a7661cd6 100644 --- a/hypothesis-python/src/hypothesis/strategies/_internal/utils.py +++ b/hypothesis-python/src/hypothesis/strategies/_internal/utils.py @@ -178,11 +178,7 @@ def to_jsonable(obj: object) -> object: and dcs.is_dataclass(obj) and not isinstance(obj, type) ): - if sys.version_info[:2] < (3, 12): - # see issue #3812 - return to_jsonable(dataclass_asdict(obj)) - else: # pragma: no cover - return to_jsonable(dcs.asdict(obj)) + return to_jsonable(dataclass_asdict(obj)) if attr.has(type(obj)): return to_jsonable(attr.asdict(obj, recurse=False)) # type: ignore if (pyd := sys.modules.get("pydantic")) and isinstance(obj, pyd.BaseModel): From cc2db94a4f36bac43758448199a89eb0dc9e8d42 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 15 Dec 2023 15:28:36 -0500 Subject: [PATCH 14/16] simlify dict_factory case --- .../src/hypothesis/internal/compat.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/hypothesis-python/src/hypothesis/internal/compat.py b/hypothesis-python/src/hypothesis/internal/compat.py index ab0a7e3106..4628ba4799 100644 --- a/hypothesis-python/src/hypothesis/internal/compat.py +++ b/hypothesis-python/src/hypothesis/internal/compat.py @@ -212,17 +212,10 @@ def dataclass_asdict(obj, *, dict_factory=dict): def _asdict_inner(obj, dict_factory): if dataclasses._is_dataclass_instance(obj): - if dict_factory is dict: - return { - f.name: _asdict_inner(getattr(obj, f.name), dict) - for f in dataclasses.fields(obj) - } - else: # pragma: no cover - result = [] - for f in dataclasses.fields(obj): - value = _asdict_inner(getattr(obj, f.name), dict_factory) - result.append((f.name, value)) - return dict_factory(result) + return dict_factory( + (f.name, _asdict_inner(getattr(obj, f.name), dict_factory)) + for f in dataclasses.fields(obj) + ) elif isinstance(obj, tuple) and hasattr(obj, "_fields"): return type(obj)(*[_asdict_inner(v, dict_factory) for v in obj]) elif isinstance(obj, (list, tuple)): From 073965970e27920f82e9cdee8b0f61763c45539f Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 15 Dec 2023 15:52:33 -0500 Subject: [PATCH 15/16] more explicit definition to make ruff happy --- .../src/hypothesis/internal/compat.py | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/hypothesis-python/src/hypothesis/internal/compat.py b/hypothesis-python/src/hypothesis/internal/compat.py index 4628ba4799..085cbd3cdf 100644 --- a/hypothesis-python/src/hypothesis/internal/compat.py +++ b/hypothesis-python/src/hypothesis/internal/compat.py @@ -192,21 +192,22 @@ def bad_django_TestCase(runner): return not isinstance(runner, HypothesisTestCase) -def dataclass_asdict(obj, *, dict_factory=dict): - """ - A vendored variant of dataclasses.asdict. Includes the bugfix for - defaultdicts (cpython/32056) for all versions. See also issues/3812. +# see issue #3812 +if sys.version_info[:2] < (3, 12): - This should be removed whenever we drop support for 3.11. We can use the - standard dataclasses.asdict after that point. - """ - if not dataclasses._is_dataclass_instance(obj): # pragma: no cover - raise TypeError("asdict() should be called on dataclass instances") - return _asdict_inner(obj, dict_factory) + def dataclass_asdict(obj, *, dict_factory=dict): + """ + A vendored variant of dataclasses.asdict. Includes the bugfix for + defaultdicts (cpython/32056) for all versions. See also issues/3812. + This should be removed whenever we drop support for 3.11. We can use the + standard dataclasses.asdict after that point. + """ + if not dataclasses._is_dataclass_instance(obj): # pragma: no cover + raise TypeError("asdict() should be called on dataclass instances") + return _asdict_inner(obj, dict_factory) -if sys.version_info[:2] >= (3, 12): - # see issue #3812 +else: dataclass_asdict = dataclasses.asdict From 3ee5f1a96edbc4a51704bf4cc7b11e4cd526f3f7 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Fri, 15 Dec 2023 23:42:46 -0500 Subject: [PATCH 16/16] nocover 3.12 branch --- hypothesis-python/src/hypothesis/internal/compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hypothesis-python/src/hypothesis/internal/compat.py b/hypothesis-python/src/hypothesis/internal/compat.py index 085cbd3cdf..41e8ce61d1 100644 --- a/hypothesis-python/src/hypothesis/internal/compat.py +++ b/hypothesis-python/src/hypothesis/internal/compat.py @@ -207,7 +207,7 @@ def dataclass_asdict(obj, *, dict_factory=dict): raise TypeError("asdict() should be called on dataclass instances") return _asdict_inner(obj, dict_factory) -else: +else: # pragma: no cover dataclass_asdict = dataclasses.asdict