Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add support for lazy annotations PEP563 (#112)
* also move SerdeError to compat.py
- Loading branch information
Showing
4 changed files
with
223 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
from __future__ import annotations # this is the line this test file is all about | ||
|
||
import dataclasses | ||
from dataclasses import dataclass | ||
from enum import Enum | ||
from typing import List, Tuple | ||
|
||
import pytest | ||
|
||
import serde | ||
from serde import SerdeError, deserialize, from_dict, serialize, to_dict | ||
from serde.compat import dataclass_fields | ||
|
||
serde.init(True) | ||
|
||
|
||
class Status(Enum): | ||
OK = "ok" | ||
ERR = "err" | ||
|
||
|
||
@deserialize | ||
@serialize | ||
@dataclass | ||
class A: | ||
a: int | ||
b: Status | ||
c: List[str] | ||
|
||
|
||
@deserialize | ||
@serialize | ||
@dataclass | ||
class B: | ||
a: A | ||
b: Tuple[str, A] | ||
c: Status | ||
|
||
|
||
# only works with global classes | ||
def test_serde_with_lazy_type_annotations(): | ||
a = A(1, Status.ERR, ["foo"]) | ||
a_dict = {"a": 1, "b": "err", "c": ["foo"]} | ||
|
||
assert a == from_dict(A, a_dict) | ||
assert a_dict == to_dict(a) | ||
|
||
b = B(a, ("foo", a), Status.OK) | ||
b_dict = {"a": a_dict, "b": ("foo", a_dict), "c": "ok"} | ||
|
||
assert b == from_dict(B, b_dict) | ||
assert b_dict == to_dict(b) | ||
|
||
|
||
# test_forward_reference_works currently only works with global visible classes | ||
@dataclass | ||
class ForwardReferenceFoo: | ||
# this is not a string forward reference because we use PEP 563 (see 1st line of this file) | ||
bar: ForwardReferenceBar | ||
|
||
|
||
@serialize | ||
@deserialize | ||
@dataclass | ||
class ForwardReferenceBar: | ||
i: int | ||
|
||
|
||
# assert type is str | ||
assert 'ForwardReferenceBar' == dataclasses.fields(ForwardReferenceFoo)[0].type | ||
|
||
# setup pyserde for Foo after Bar becomes visible to global scope | ||
deserialize(ForwardReferenceFoo) | ||
serialize(ForwardReferenceFoo) | ||
|
||
# now the type really is of type Bar | ||
assert ForwardReferenceBar == dataclasses.fields(ForwardReferenceFoo)[0].type | ||
assert ForwardReferenceBar == next(dataclass_fields(ForwardReferenceFoo)).type | ||
|
||
# verify usage works | ||
def test_forward_reference_works(): | ||
h = ForwardReferenceFoo(bar=ForwardReferenceBar(i=10)) | ||
h_dict = {"bar": {"i": 10}} | ||
|
||
assert to_dict(h) == h_dict | ||
assert from_dict(ForwardReferenceFoo, h_dict) == h | ||
|
||
|
||
# trying to use forward reference normally will throw | ||
def test_unresolved_forward_reference_throws(): | ||
with pytest.raises(SerdeError) as e: | ||
|
||
@serialize | ||
@deserialize | ||
@dataclass | ||
class UnresolvedForwardFoo: | ||
bar: UnresolvedForwardBar | ||
|
||
@serialize | ||
@deserialize | ||
@dataclass | ||
class UnresolvedForwardBar: | ||
i: int | ||
|
||
assert "Failed to resolve type hints for UnresolvedForwardFoo" in str(e) | ||
|
||
|
||
# trying to use string forward reference will throw | ||
def test_string_forward_reference_throws(): | ||
with pytest.raises(SerdeError) as e: | ||
|
||
@serialize | ||
@deserialize | ||
@dataclass | ||
class UnresolvedStringForwardFoo: | ||
# string forward references are not compatible with PEP 563 and will throw | ||
bar: 'UnresolvedStringForwardBar' | ||
|
||
@serialize | ||
@deserialize | ||
@dataclass | ||
class UnresolvedStringForwardBar: | ||
i: int | ||
|
||
# message is different between <= 3.8 & >= 3.9 | ||
assert "Failed to resolve " in str(e.value) |