Skip to content

Commit

Permalink
Fix PEP 0673 before 3.11 (#487)
Browse files Browse the repository at this point in the history
  • Loading branch information
george-zubrienko authored Oct 6, 2023
1 parent 9370a40 commit 299cec2
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 1 deletion.
17 changes: 16 additions & 1 deletion dataclasses_json/core.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import copy
import json
import sys
import warnings
from collections import defaultdict, namedtuple
from dataclasses import (MISSING,
Expand All @@ -10,7 +11,7 @@
from decimal import Decimal
from enum import Enum
from typing import (Any, Collection, Mapping, Union, get_type_hints,
Tuple, TypeVar)
Tuple, TypeVar, Type)
from uuid import UUID

from typing_inspect import is_union_type # type: ignore
Expand Down Expand Up @@ -373,6 +374,20 @@ def _decode_item(type_arg, x):
return _decode_generic(type_arg, x, infer_missing)
return x

def handle_pep0673(pre_0673_hint: str) -> Union[Type, str]:
for module in sys.modules:
maybe_resolved = getattr(sys.modules[module], type_args, None)
if maybe_resolved:
return maybe_resolved

warnings.warn(f"Could not resolve self-reference for type {pre_0673_hint}, "
f"decoded type might be incorrect or decode might fail altogether.")
return pre_0673_hint

# Before https://peps.python.org/pep-0673 (3.11+) self-type hints are simply strings
if sys.version_info.minor < 11 and type_args is not type and type(type_args) is str:
type_args = handle_pep0673(type_args)

if _isinstance_safe(type_args, Collection) and not _issubclass_safe(type_args, Enum):
return list(_decode_item(type_arg, x) for type_arg, x in zip(type_args, xs))
return list(_decode_item(type_args, x) for x in xs)
Expand Down
7 changes: 7 additions & 0 deletions tests/entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,3 +381,10 @@ class DataClassWithNestedDictWithTupleKeys:
@dataclass
class DataClassWithCounter:
c: Counter[str]


@dataclass_json
@dataclass
class DataClassWithSelf(DataClassJsonMixin):
id: str
ref: Optional['DataClassWithSelf']
39 changes: 39 additions & 0 deletions tests/test_self.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""
PEP 0673 that allows self-references has only been added in 3.11
This suite verifies that we are somewhat able to serde self-referencing classes before 3.11.
"""
import json
from typing import Dict

import pytest

from tests.entities import DataClassWithSelf


class TestSelf:
@pytest.mark.parametrize("entity, expected", [
(DataClassWithSelf(id="1", ref=None), {
"id": "1",
"ref": None
}),
(DataClassWithSelf(id="1", ref=DataClassWithSelf(id="2", ref=None)), {
"id": "1",
"ref": { "id": "2", "ref": None }
})
])
def test_self_serde(self, entity: DataClassWithSelf, expected: Dict):
assert entity.to_dict() == expected and DataClassWithSelf.from_dict(expected).to_json() == entity.to_json()

@pytest.mark.parametrize("entity", [
({
"id": "1",
"ref": {"id": "2", "ref": None}
}),
({
"id": "1",
"ref": None
})
])
def test_self_type(self, entity: Dict):
obj = DataClassWithSelf.from_json(json.dumps(entity))
assert isinstance(obj, DataClassWithSelf) and (isinstance(obj.ref, DataClassWithSelf) or isinstance(obj.ref, type(None)))

6 comments on commit 299cec2

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report
FileStmtsMissCoverMissing
dataclasses_json
   cfg.py51492%80, 84–86
   core.py2571694%39–42, 52, 65, 67, 82, 84, 170, 378–385, 389
   mm.py2042986%33–36, 42–45, 53–56, 62–65, 88, 170–171, 176, 180, 184, 189, 193, 197, 205, 211, 216, 225, 230, 235, 253–260
   stringcase.py25388%59, 76, 97
   undefined.py146299%25, 39
   utils.py1312978%12–25, 45–50, 61–65, 75, 100–101, 109–110, 163, 182, 207
tests
   entities.py239399%20, 234, 240
   test_annotations.py814248%50–67, 78–102, 106–122
   test_api.py142299%139–140
   test_str_subclass.py22195%9
TOTAL264313195% 

Tests Skipped Failures Errors Time
305 1 💤 0 ❌ 0 🔥 3.381s ⏱️

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report
FileStmtsMissCoverMissing
dataclasses_json
   cfg.py51492%80, 84–86
   core.py2581793%39–42, 52, 65, 67, 82, 84, 170, 198, 378–385, 389
   mm.py2053085%33–36, 42–45, 53–56, 62–65, 88, 170–171, 176, 180, 184, 189, 193, 197, 205, 211, 216, 225, 230, 235, 244, 253–260
   stringcase.py25388%59, 76, 97
   undefined.py146299%25, 39
   utils.py1313673%12–25, 45–50, 61–65, 75, 100–101, 109–110, 125–133, 163, 182, 207
tests
   entities.py239399%22, 234, 240
   test_annotations.py814248%50–67, 78–102, 106–122
   test_api.py142497%88, 99, 139–140
   test_str_subclass.py22195%9
   test_union.py1311092%97–104, 118–125
TOTAL264515294% 

Tests Skipped Failures Errors Time
305 3 💤 0 ❌ 0 🔥 3.739s ⏱️

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report
FileStmtsMissCoverMissing
dataclasses_json
   cfg.py51492%80, 84–86
   core.py2581793%39–42, 52, 65, 67, 82, 84, 170, 198, 378–385, 389
   mm.py2053085%33–36, 42–45, 53–56, 62–65, 88, 170–171, 176, 180, 184, 189, 193, 197, 205, 211, 216, 225, 230, 235, 244, 253–260
   stringcase.py25388%59, 76, 97
   undefined.py146299%25, 39
   utils.py1313673%12–25, 45–50, 61–65, 75, 100–101, 109–110, 125–133, 163, 182, 207
tests
   entities.py239399%22, 234, 240
   test_annotations.py814248%50–67, 78–102, 106–122
   test_api.py142497%88, 99, 139–140
   test_str_subclass.py22195%9
   test_union.py1311092%97–104, 118–125
TOTAL264515294% 

Tests Skipped Failures Errors Time
305 3 💤 0 ❌ 0 🔥 4.425s ⏱️

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report
FileStmtsMissCoverMissing
dataclasses_json
   cfg.py51492%80, 84–86
   core.py2571694%39–42, 52, 65, 67, 82, 84, 170, 378–385, 389
   mm.py2042986%33–36, 42–45, 53–56, 62–65, 88, 170–171, 176, 180, 184, 189, 193, 197, 205, 211, 216, 225, 230, 235, 253–260
   stringcase.py25388%59, 76, 97
   undefined.py146299%25, 39
   utils.py1312978%12–25, 45–50, 61–65, 75, 100–101, 109–110, 163, 182, 207
tests
   entities.py239399%22, 234, 240
   test_annotations.py814248%50–67, 78–102, 106–122
   test_api.py142199%139
   test_str_subclass.py22195%9
TOTAL264313095% 

Tests Skipped Failures Errors Time
305 1 💤 0 ❌ 0 🔥 4.863s ⏱️

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report
FileStmtsMissCoverMissing
dataclasses_json
   cfg.py51492%80, 84–86
   core.py2581694%39–42, 52, 65, 67, 82, 84, 170, 378–385, 389
   mm.py2042986%33–36, 42–45, 53–56, 62–65, 88, 170–171, 176, 180, 184, 189, 193, 197, 205, 211, 216, 225, 230, 235, 253–260
   stringcase.py25388%59, 76, 97
   undefined.py144299%25, 39
   utils.py1312978%12–25, 45–50, 61–65, 75, 100–101, 109–110, 163, 182, 207
tests
   entities.py218399%20, 234, 240
   test_annotations.py804248%50–67, 78–102, 106–122
   test_api.py140299%139–140
   test_str_subclass.py22195%9
TOTAL252213195% 

Tests Skipped Failures Errors Time
305 1 💤 0 ❌ 0 🔥 4.104s ⏱️

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report
FileStmtsMissCoverMissing
dataclasses_json
   cfg.py51492%80, 84–86
   core.py2581793%39–42, 52, 65, 67, 82, 84, 170, 198, 378–385, 389
   mm.py2053085%33–36, 42–45, 53–56, 62–65, 88, 170–171, 176, 180, 184, 189, 193, 197, 205, 211, 216, 225, 230, 235, 244, 253–260
   stringcase.py25388%59, 76, 97
   undefined.py146299%25, 39
   utils.py1313673%12–25, 45–50, 61–65, 75, 100–101, 109–110, 125–133, 163, 182, 207
tests
   entities.py239399%22, 234, 240
   test_annotations.py814248%50–67, 78–102, 106–122
   test_api.py142497%88, 99, 139–140
   test_str_subclass.py22195%9
   test_union.py1311092%97–104, 118–125
TOTAL264515294% 

Tests Skipped Failures Errors Time
305 3 💤 0 ❌ 0 🔥 7.571s ⏱️

Please sign in to comment.