diff --git a/HISTORY.rst b/HISTORY.rst index 85984b0..fbb9516 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -2,6 +2,16 @@ 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. + - Bump *dev* dependencies to latest version. + 0.32.0 (2024-11-30) ------------------- diff --git a/dataclass_wizard/parsers.py b/dataclass_wizard/parsers.py index e5639a6..8f57265 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/requirements-dev.txt b/requirements-dev.txt index f2e786c..c74fbff 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -15,7 +15,7 @@ pip>=21.3.1 bump2version==1.0.1 wheel==0.45.1 watchdog[watchmedo]==6.0.0 -Sphinx==7.4.7; python_version == "3.9" +Sphinx==7.4.7; python_version == "3.9" # pyup: ignore Sphinx==8.1.3; python_version >= "3.10" -twine==5.1.1 -dataclass-wizard[toml] +twine==6.0.1 +dataclass-wizard[toml] # pyup: ignore diff --git a/requirements-test.txt b/requirements-test.txt index 463d9f3..df8dfca 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,4 +1,4 @@ -pytest==8.3.3 +pytest==8.3.4 pytest-mock>=3.6.1 pytest-cov==6.0.0 # pytest-runner==5.3.1 diff --git a/tests/unit/test_dump.py b/tests/unit/test_dump.py index 4407311..781ab7d 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')})