From a715be0d47bb7f7afa41a686a94e3872099f637d Mon Sep 17 00:00:00 2001 From: Ritvik Nag Date: Wed, 4 Dec 2024 12:57:21 -0500 Subject: [PATCH] bugfix for dataclass in dict --- HISTORY.rst | 9 +++++++++ dataclass_wizard/parsers.py | 8 +++++--- tests/unit/test_dump.py | 22 ++++++++++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 85984b0a..34065d37 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -2,6 +2,15 @@ History ======= +0.32.1 (2024-12-04) +------------------- + +**Bugfixes** + +- Corrected logic in :class:`MappingParser` that assumed all parsers were + subclasses of :class:`AbstractParser` (:issue:`159`). + - Add test case to confirm intended functionality. + 0.32.0 (2024-11-30) ------------------- diff --git a/dataclass_wizard/parsers.py b/dataclass_wizard/parsers.py index e5639a65..8f57265d 100644 --- a/dataclass_wizard/parsers.py +++ b/dataclass_wizard/parsers.py @@ -534,7 +534,7 @@ class MappingParser(AbstractParser[Type[M], M]): __slots__ = ('hook', 'key_parser', 'val_parser', - 'val_base_type') + 'val_type') base_type: Type[M] hook: Callable[[Any, Type[M], AbstractParser, AbstractParser], M] @@ -551,12 +551,12 @@ def __post_init__(self, cls: Type, # Base type of the object which is instantiable # ex. `Dict[str, Any]` -> `dict` self.base_type: Type[M] = get_origin(self.base_type) + self.val_type = val_type val_parser = get_parser(val_type, cls, extras) self.key_parser = getattr(p := get_parser(key_type, cls, extras), '__call__', p) self.val_parser = getattr(val_parser, '__call__', val_parser) - self.val_base_type = val_parser.base_type def __call__(self, o: M) -> M: return self.hook(o, self.base_type, self.key_parser, self.val_parser) @@ -577,7 +577,9 @@ def __post_init__(self, cls: Type, super().__post_init__(cls, extras, get_parser) # The default factory argument to pass to the `defaultdict` subclass - self.default_factory: DefFactory = self.val_base_type + val_type = self.val_type + val_base_type = getattr(val_type, '__origin__', val_type) + self.default_factory: DefFactory = val_base_type def __call__(self, o: DD) -> DD: return self.hook(o, self.base_type, self.default_factory, diff --git a/tests/unit/test_dump.py b/tests/unit/test_dump.py index 44073113..781ab7d2 100644 --- a/tests/unit/test_dump.py +++ b/tests/unit/test_dump.py @@ -486,3 +486,25 @@ class MyClass(JSONSerializable): with expectation: result = c.to_dict() log.debug('Parsed object: %r', result) + + +def test_using_dataclass_in_dict(): + """ + Using dataclass in a dictionary (i.e., dict[str, Test]) + works as expected. + + See https://github.com/rnag/dataclass-wizard/issues/159 + """ + @dataclass + class Test: + field: str + + @dataclass + class Config: + tests: dict[str, Test] + + config = {"tests": {"test_a": {"field": "a"}, "test_b": {"field": "b"}}} + + assert fromdict(Config, config) == Config( + tests={'test_a': Test(field='a'), + 'test_b': Test(field='b')})