From dc4ea6d9aa874f4283afceb75ceea303e75e7440 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 16 Feb 2023 19:59:41 +0100 Subject: [PATCH 1/8] Add type hook transformation function --- dacite/core.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dacite/core.py b/dacite/core.py index 9e45129..63e0679 100644 --- a/dacite/core.py +++ b/dacite/core.py @@ -83,12 +83,16 @@ def from_dict(data_class: Type[T], data: Data, config: Optional[Config] = None) setattr(instance, key, value) return instance +def transform_by_type_hooks(data: Any, type_: Type, type_hooks: Dict[Type, Callable[[Any], Any]]): + for th, func in type_hooks.items(): + if is_subclass(th, type_): + return func(data) def _build_value(type_: Type, data: Any, config: Config) -> Any: if is_init_var(type_): type_ = extract_init_var(type_) - if type_ in config.type_hooks: - data = config.type_hooks[type_](data) + if transformed := transform_by_type_hooks(data, type_, config.type_hooks): + data = transformed if is_optional(type_) and data is None: return data if is_union(type_): From 263a3e2ecb1f17428134fee808fe4bd0bf3fd511 Mon Sep 17 00:00:00 2001 From: Chris Snijder Date: Thu, 16 Feb 2023 20:39:41 +0100 Subject: [PATCH 2/8] Modernise the type hook function --- dacite/core.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/dacite/core.py b/dacite/core.py index 63e0679..8b9d6e8 100644 --- a/dacite/core.py +++ b/dacite/core.py @@ -1,6 +1,6 @@ from dataclasses import is_dataclass from itertools import zip_longest -from typing import TypeVar, Type, Optional, get_type_hints, Mapping, Any, Collection, MutableMapping +from typing import Callable, TypeVar, Type, Optional, get_type_hints, Mapping, Any, Collection, MutableMapping from dacite.cache import cache from dacite.config import Config @@ -83,10 +83,12 @@ def from_dict(data_class: Type[T], data: Data, config: Optional[Config] = None) setattr(instance, key, value) return instance -def transform_by_type_hooks(data: Any, type_: Type, type_hooks: Dict[Type, Callable[[Any], Any]]): + +def transform_by_type_hooks(data: Any, type_: Type, type_hooks: dict[Type, Callable[[Any], Any]]) -> Any | None: for th, func in type_hooks.items(): if is_subclass(th, type_): - return func(data) + return func(data) + def _build_value(type_: Type, data: Any, config: Config) -> Any: if is_init_var(type_): From 6f9e85816974edf0676c6c9b28c18ffd4794221d Mon Sep 17 00:00:00 2001 From: Chris Snijder Date: Thu, 16 Feb 2023 21:23:13 +0100 Subject: [PATCH 3/8] Fix Union case. --- dacite/core.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/dacite/core.py b/dacite/core.py index 8b9d6e8..e1ee922 100644 --- a/dacite/core.py +++ b/dacite/core.py @@ -1,6 +1,18 @@ from dataclasses import is_dataclass from itertools import zip_longest -from typing import Callable, TypeVar, Type, Optional, get_type_hints, Mapping, Any, Collection, MutableMapping +from typing import ( + Callable, + TypeVar, + Type, + Optional, + Union, + get_origin, + get_type_hints, + Mapping, + Any, + Collection, + MutableMapping, +) from dacite.cache import cache from dacite.config import Config @@ -85,6 +97,8 @@ def from_dict(data_class: Type[T], data: Data, config: Optional[Config] = None) def transform_by_type_hooks(data: Any, type_: Type, type_hooks: dict[Type, Callable[[Any], Any]]) -> Any | None: + if get_origin(type_) is Union: + return for th, func in type_hooks.items(): if is_subclass(th, type_): return func(data) From adbaca49b33636ea476734544db80be1bc533c16 Mon Sep 17 00:00:00 2001 From: Chris Snijder Date: Fri, 17 Feb 2023 11:58:10 +0100 Subject: [PATCH 4/8] Run test suite, expected to fail. --- .github/workflows/code_check.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code_check.yaml b/.github/workflows/code_check.yaml index b85a786..4f5ad28 100644 --- a/.github/workflows/code_check.yaml +++ b/.github/workflows/code_check.yaml @@ -29,7 +29,7 @@ jobs: python-version: ${{ matrix.python-version }} architecture: x64 - name: Install dependencies - run: pip install -e ".[dev]" + run: pip install -e ".[dev,freezegun]" - name: Testing Check run: pytest --cov=dacite - name: Formatting Check From 05c3f07a767407449e224d867e771df07dd05123 Mon Sep 17 00:00:00 2001 From: Chris Snijder Date: Fri, 17 Feb 2023 12:06:23 +0100 Subject: [PATCH 5/8] Place the logic in the _build_value function, avoid issues with intentionally returning or falsy values. --- dacite/core.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/dacite/core.py b/dacite/core.py index e1ee922..2a7fc9d 100644 --- a/dacite/core.py +++ b/dacite/core.py @@ -1,7 +1,6 @@ from dataclasses import is_dataclass from itertools import zip_longest from typing import ( - Callable, TypeVar, Type, Optional, @@ -96,19 +95,13 @@ def from_dict(data_class: Type[T], data: Data, config: Optional[Config] = None) return instance -def transform_by_type_hooks(data: Any, type_: Type, type_hooks: dict[Type, Callable[[Any], Any]]) -> Any | None: - if get_origin(type_) is Union: - return - for th, func in type_hooks.items(): - if is_subclass(th, type_): - return func(data) - - def _build_value(type_: Type, data: Any, config: Config) -> Any: if is_init_var(type_): type_ = extract_init_var(type_) - if transformed := transform_by_type_hooks(data, type_, config.type_hooks): - data = transformed + if get_origin(type_) is not Union: + for th, func in config.type_hooks.items(): + if is_subclass(th, type_): + data = func(data) if is_optional(type_) and data is None: return data if is_union(type_): From cd8434c52964400d0b548e1e44db1299d9695bd7 Mon Sep 17 00:00:00 2001 From: Chris Snijder Date: Fri, 17 Feb 2023 12:12:45 +0100 Subject: [PATCH 6/8] Don't use get_origin, introduced in 3.8 --- dacite/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dacite/core.py b/dacite/core.py index 2a7fc9d..0e978af 100644 --- a/dacite/core.py +++ b/dacite/core.py @@ -98,7 +98,7 @@ def from_dict(data_class: Type[T], data: Data, config: Optional[Config] = None) def _build_value(type_: Type, data: Any, config: Config) -> Any: if is_init_var(type_): type_ = extract_init_var(type_) - if get_origin(type_) is not Union: + if not is_union(type_): for th, func in config.type_hooks.items(): if is_subclass(th, type_): data = func(data) From 957c1e72c1703c745fefc60dc75444a58fe696d7 Mon Sep 17 00:00:00 2001 From: Chris Snijder Date: Fri, 17 Feb 2023 12:23:49 +0100 Subject: [PATCH 7/8] Make code more self-explanatory --- dacite/core.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/dacite/core.py b/dacite/core.py index 0e978af..2f083ec 100644 --- a/dacite/core.py +++ b/dacite/core.py @@ -4,8 +4,6 @@ TypeVar, Type, Optional, - Union, - get_origin, get_type_hints, Mapping, Any, @@ -99,8 +97,8 @@ def _build_value(type_: Type, data: Any, config: Config) -> Any: if is_init_var(type_): type_ = extract_init_var(type_) if not is_union(type_): - for th, func in config.type_hooks.items(): - if is_subclass(th, type_): + for th_type, func in config.type_hooks.items(): + if is_subclass(th_type, type_): data = func(data) if is_optional(type_) and data is None: return data From 19ec0b0f48a5dfbaa4e3efc46d3ce1cf6d028902 Mon Sep 17 00:00:00 2001 From: Chris Snijder Date: Fri, 17 Feb 2023 12:30:14 +0100 Subject: [PATCH 8/8] Don't introduce freezegun dependency --- .github/workflows/code_check.yaml | 2 +- dacite/core.py | 11 +---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/.github/workflows/code_check.yaml b/.github/workflows/code_check.yaml index 4f5ad28..b85a786 100644 --- a/.github/workflows/code_check.yaml +++ b/.github/workflows/code_check.yaml @@ -29,7 +29,7 @@ jobs: python-version: ${{ matrix.python-version }} architecture: x64 - name: Install dependencies - run: pip install -e ".[dev,freezegun]" + run: pip install -e ".[dev]" - name: Testing Check run: pytest --cov=dacite - name: Formatting Check diff --git a/dacite/core.py b/dacite/core.py index 2f083ec..154fb57 100644 --- a/dacite/core.py +++ b/dacite/core.py @@ -1,15 +1,6 @@ from dataclasses import is_dataclass from itertools import zip_longest -from typing import ( - TypeVar, - Type, - Optional, - get_type_hints, - Mapping, - Any, - Collection, - MutableMapping, -) +from typing import TypeVar, Type, Optional, get_type_hints, Mapping, Any, Collection, MutableMapping from dacite.cache import cache from dacite.config import Config