diff --git a/HISTORY.rst b/HISTORY.rst index 97ba212..85984b0 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -7,16 +7,18 @@ History **Features and Improvements** -- Add support for ``Sequence`` and ``MutableSequence`` +- Add support for `ABC Containers`_ in ``typing`` and ``collections.abc``: + * ``Collection`` + * ``Sequence`` + * ``MutableSequence`` **Bugfixes** -* Fix a bug in ``ParseError`` -* Fix issue when field in ``EnvWizard`` is a dataclass, and - instance of that type is passed to constructor -* Fix some logic in ``parsers.py`` that was relying on all parsers - being subclass of :class:`AbstractParser`, as they can now be a function - instead. +- Fixed a bug in :class:`ParseError` handling. +- Resolved an issue in :class:`EnvWizard` where passing an instance of a dataclass field type to the constructor caused problems. +- Corrected logic in :mod:`parsers.py` that assumed all parsers were subclasses of :class:`AbstractParser`; parsers can now be functions as well. + +.. _ABC Containers: https://docs.python.org/3/library/typing.html#aliases-to-container-abcs-in-collections-abc 0.31.0 (2024-11-30) ------------------- diff --git a/dataclass_wizard/loaders.py b/dataclass_wizard/loaders.py index 07ad7fc..db4d689 100644 --- a/dataclass_wizard/loaders.py +++ b/dataclass_wizard/loaders.py @@ -1,4 +1,6 @@ from collections import defaultdict, deque, namedtuple +import collections.abc as abc + from dataclasses import is_dataclass from datetime import datetime, time, date, timedelta from decimal import Decimal @@ -10,7 +12,6 @@ NamedTupleMeta, SupportsFloat, AnyStr, Text, Callable, Optional ) -from collections.abc import Sequence as ABCSequence, MutableSequence as ABCMutableSequence from uuid import UUID @@ -440,11 +441,11 @@ def get_parser_for_annotation(cls, ann_type: Type[T], cls.get_parser_for_annotation ) - elif base_type in (ABCSequence, ABCMutableSequence): + elif base_type in (abc.Sequence, abc.MutableSequence, abc.Collection): load_hook = cls.load_to_iterable # desired (non-generic) origin type - desired_type = list if base_type is ABCMutableSequence else tuple - # Re-map to desired type, e.g. `Sequence[int]` -> `list[int]` + desired_type = tuple if base_type is abc.Sequence else list + # Re-map to desired type, e.g. `Sequence[int]` -> `tuple[int]` ann_type = desired_type[ann_type] if ( ann_type := get_args(ann_type)[0]) else desired_type diff --git a/docs/overview.rst b/docs/overview.rst index 372818f..480d6c5 100644 --- a/docs/overview.rst +++ b/docs/overview.rst @@ -93,6 +93,11 @@ Supported Types - ``Union`` - Also supports `using dataclasses`_. - ``Optional`` +- `ABC Containers`_ in ``typing`` and ``collections.abc`` + - ``Collection`` -- instantiated as ``list`` + - ``MutableSequence`` -- mapped to ``list`` + - ``Sequence`` -- mapped to ``tuple`` + * Recently introduced Generic types - ``Annotated`` - ``Literal`` @@ -178,3 +183,4 @@ Special Cases .. _using dataclasses: https://dataclass-wizard.readthedocs.io/en/latest/common_use_cases/dataclasses_in_union_types.html .. _pytimeparse: https://pypi.org/project/pytimeparse/ +.. _ABC Containers: https://docs.python.org/3/library/typing.html#aliases-to-container-abcs-in-collections-abc diff --git a/tests/unit/test_load.py b/tests/unit/test_load.py index 6e94315..e134c29 100644 --- a/tests/unit/test_load.py +++ b/tests/unit/test_load.py @@ -10,7 +10,7 @@ from datetime import datetime, date, time, timedelta from typing import ( List, Optional, Union, Tuple, Dict, NamedTuple, Type, DefaultDict, - Set, FrozenSet, Generic, Annotated, Literal, Sequence, MutableSequence + Set, FrozenSet, Generic, Annotated, Literal, Sequence, MutableSequence, Collection ) import pytest @@ -2480,8 +2480,8 @@ class _(JSONWizard.Meta): def test_sequence_and_mutable_sequence_are_supported(): """ - Confirm `Sequence` and `MutableSequence` -- imported from - either `typing` or `collections.abc` -- are supported. + Confirm `Collection`, `Sequence`, and `MutableSequence` -- imported + from either `typing` or `collections.abc` -- are supported. """ @dataclass class IssueFields: @@ -2499,6 +2499,7 @@ class Options(JSONWizard): fields_tup: tuple[IssueFields] = IssueFields('A'), fields_var_tup: tuple[IssueFields, ...] = IssueFields('A'), list_of_int: MutableSequence[int] = field(default_factory=list) + list_of_bool: Collection[bool] = field(default_factory=list) # initialize with defaults opt = Options.from_dict({ @@ -2549,3 +2550,11 @@ class Options(JSONWizard): 'ListOfInt': (1, '2', 3.0) }) assert opt.list_of_int == [1, 2, 3] + + # check annotated `Collection` maps to `list` + opt = Options.from_dict({ + 'email': 'a@b.org', + 'token': '', + 'ListOfBool': (1, '0', '1') + }) + assert opt.list_of_bool == [True, False, True]