-
-
Notifications
You must be signed in to change notification settings - Fork 47
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #212 from Fatal1ty/pep-695
Add support for PEP 695 aliases
- Loading branch information
Showing
8 changed files
with
299 additions
and
2 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
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,240 @@ | ||
from dataclasses import dataclass | ||
from datetime import date, datetime | ||
from typing import Any, Mapping | ||
|
||
from mashumaro import DataClassDictMixin | ||
from mashumaro.mixins.json import DataClassJSONMixin | ||
from tests.entities import MyGenericDataClass, SerializableTypeGenericList | ||
|
||
|
||
@dataclass | ||
class Foo[T](DataClassJSONMixin): | ||
x: T | ||
y: "Foo[Any] | None" | ||
|
||
|
||
@dataclass | ||
class Bar(Foo): | ||
pass | ||
|
||
|
||
def test_one_generic(): | ||
@dataclass | ||
class A[T]: | ||
x: T | ||
|
||
@dataclass | ||
class B(A[datetime], DataClassDictMixin): | ||
pass | ||
|
||
obj = B(datetime(2021, 8, 15)) | ||
assert B.from_dict({"x": "2021-08-15T00:00:00"}) == obj | ||
assert obj.to_dict() == {"x": "2021-08-15T00:00:00"} | ||
|
||
|
||
def test_one_generic_list(): | ||
@dataclass | ||
class A[T](list[T]): | ||
x: list[T] | ||
|
||
@dataclass | ||
class B(A[datetime], DataClassDictMixin): | ||
pass | ||
|
||
obj = B(x=[datetime(2021, 8, 15)]) | ||
assert B.from_dict({"x": ["2021-08-15T00:00:00"]}) == obj | ||
assert obj.to_dict() == {"x": ["2021-08-15T00:00:00"]} | ||
|
||
|
||
def test_two_generics(): | ||
@dataclass | ||
class A1[T]: | ||
x: list[T] | ||
|
||
@dataclass | ||
class A2[T, S]: | ||
y: Mapping[T, S] | ||
|
||
@dataclass | ||
class B(A1[datetime], A2[datetime, date], DataClassDictMixin): | ||
pass | ||
|
||
obj = B( | ||
x=[datetime(2021, 8, 15), datetime(2021, 8, 16)], | ||
y={datetime(2021, 8, 17): date(2021, 8, 18)}, | ||
) | ||
dump = { | ||
"x": ["2021-08-15T00:00:00", "2021-08-16T00:00:00"], | ||
"y": {"2021-08-17T00:00:00": "2021-08-18"}, | ||
} | ||
assert B.from_dict(dump) == obj | ||
assert obj.to_dict() == dump | ||
|
||
|
||
def test_partially_concrete_generic(): | ||
@dataclass | ||
class A[T, S]: | ||
x: Mapping[T, S] | ||
|
||
@dataclass | ||
class B[S](A[datetime, S], DataClassDictMixin): | ||
pass | ||
|
||
obj = B(x={datetime(2022, 11, 21): "3.14"}) | ||
assert B.from_dict({"x": {"2022-11-21T00:00:00": "3.14"}}) == obj | ||
assert obj.to_dict() == {"x": {"2022-11-21T00:00:00": "3.14"}} | ||
|
||
|
||
def test_partially_concrete_generic_with_bound(): | ||
@dataclass | ||
class A[T, P: (Mapping[int, int], list[float])]: | ||
x: Mapping[T, P] | ||
|
||
@dataclass | ||
class B[P: (Mapping[int, int], list[float])]( | ||
A[date, P], DataClassDictMixin | ||
): | ||
pass | ||
|
||
obj1 = B(x={date(2022, 11, 21): {1: 2, 3: 4}}) | ||
assert B.from_dict({"x": {"2022-11-21": {"1": "2", "3": "4"}}}) == obj1 | ||
assert obj1.to_dict() == {"x": {"2022-11-21": {1: 2, 3: 4}}} | ||
obj2 = B(x={date(2022, 11, 21): [1.1, 3.3]}) | ||
assert ( | ||
B.from_dict({"x": {"2022-11-21": {"1.1": "2.2", "3.3": "4.4"}}}) | ||
== obj2 | ||
) | ||
assert obj2.to_dict() == {"x": {"2022-11-21": [1.1, 3.3]}} | ||
obj3 = B(x={date(2022, 11, 21): [1.1, 2.2, 3.3, 4.4]}) | ||
assert ( | ||
B.from_dict({"x": {"2022-11-21": ["1.1", "2.2", "3.3", "4.4"]}}) | ||
== obj3 | ||
) | ||
assert obj3.to_dict() == {"x": {"2022-11-21": [1.1, 2.2, 3.3, 4.4]}} | ||
|
||
|
||
def test_concrete_generic_with_different_type_var(): | ||
@dataclass | ||
class A[T]: | ||
x: T | ||
|
||
@dataclass | ||
class B[P: (Mapping[int, int], list[float])](A[P], DataClassDictMixin): | ||
pass | ||
|
||
obj = B.from_dict({"x": {"1": "2", "3": "4"}}) | ||
assert obj == B(x={1: 2, 3: 4}) | ||
obj = B.from_dict({"x": {"1.1": "2.2", "3.3": "4.4"}}) | ||
assert obj == B(x=[1.1, 3.3]) | ||
obj = B.from_dict({"x": ["1.1", "2.2", "3.3", "4.4"]}) | ||
assert obj == B(x=[1.1, 2.2, 3.3, 4.4]) | ||
|
||
|
||
def test_loose_generic_info_with_any_type(): | ||
@dataclass | ||
class A[T]: | ||
x: T | ||
|
||
@dataclass | ||
class B(A, DataClassDictMixin): | ||
pass | ||
|
||
obj = B.from_dict({"x": {"1.1": "2.2", "3.3": "4.4"}}) | ||
assert obj == B(x={"1.1": "2.2", "3.3": "4.4"}) | ||
obj = B.from_dict({"x": ["1.1", "2.2", "3.3", "4.4"]}) | ||
assert obj == B(x=["1.1", "2.2", "3.3", "4.4"]) | ||
|
||
|
||
def test_loose_generic_info_with_bound(): | ||
@dataclass | ||
class A[P: (Mapping[int, int], list[float])]: | ||
x: P | ||
|
||
@dataclass | ||
class B(A, DataClassDictMixin): | ||
pass | ||
|
||
obj = B.from_dict({"x": {"1": "2", "3": "4"}}) | ||
assert obj == B(x={1: 2, 3: 4}) | ||
obj = B.from_dict({"x": {"1.1": "2.2", "3.3": "4.4"}}) | ||
assert obj == B(x=[1.1, 3.3]) | ||
obj = B.from_dict({"x": ["1.1", "2.2", "3.3", "4.4"]}) | ||
assert obj == B(x=[1.1, 2.2, 3.3, 4.4]) | ||
|
||
|
||
def test_loose_generic_info_in_first_generic(): | ||
@dataclass | ||
class A[P: (Mapping[int, int], list[float])]: | ||
x: P | ||
|
||
@dataclass | ||
class B(A): | ||
pass | ||
|
||
@dataclass | ||
class C[P: (Mapping[int, int], list[float])](B): | ||
y: P | ||
|
||
@dataclass | ||
class D(C[list[float]], DataClassDictMixin): | ||
pass | ||
|
||
obj = D.from_dict({"x": {"1": "2"}, "y": {"3.3": "4.4"}}) | ||
assert obj == D(x={1: 2}, y=[3.3]) | ||
obj = D.from_dict({"x": {"1.1": "2.2"}, "y": {"3.3": "4.4"}}) | ||
assert obj == D(x=[1.1], y=[3.3]) | ||
|
||
|
||
def test_not_dataclass_generic(): | ||
class MyGeneric[P: (Mapping[int, int], list[float]), T]: | ||
pass | ||
|
||
@dataclass | ||
class GenericDataClass[P: (Mapping[int, int], list[float])]: | ||
x: P | ||
|
||
@dataclass | ||
class DataClass[P: (Mapping[int, int], list[float]), T]( | ||
MyGeneric[P, T], GenericDataClass[P] | ||
): | ||
pass | ||
|
||
@dataclass | ||
class ConcreteDataClass(DataClass[list[float], float], DataClassDictMixin): | ||
pass | ||
|
||
obj = ConcreteDataClass.from_dict({"x": {"1": "2", "3": "4"}}) | ||
assert obj == ConcreteDataClass(x=[1.0, 3.0]) | ||
|
||
|
||
def test_generic_dataclass_as_field_type(): | ||
@dataclass | ||
class DataClass(DataClassDictMixin): | ||
date: MyGenericDataClass[date] | ||
str: MyGenericDataClass[str] | ||
|
||
obj = DataClass( | ||
date=MyGenericDataClass(date(2021, 9, 14)), | ||
str=MyGenericDataClass("2021-09-14"), | ||
) | ||
dictionary = {"date": {"x": "2021-09-14"}, "str": {"x": "2021-09-14"}} | ||
assert DataClass.from_dict(dictionary) == obj | ||
assert obj.to_dict() == dictionary | ||
|
||
|
||
def test_serializable_type_generic_class(): | ||
@dataclass | ||
class DataClass(DataClassDictMixin): | ||
x: SerializableTypeGenericList[str] | ||
|
||
obj = DataClass(SerializableTypeGenericList(["a", "b", "c"])) | ||
assert DataClass.from_dict({"x": ["a", "b", "c"]}) == obj | ||
assert obj.to_dict() == {"x": ["a", "b", "c"]} | ||
|
||
|
||
def test_self_referenced_generic_no_max_recursion_error(): | ||
obj = Bar(42, Foo(33, None)) | ||
assert obj.to_dict() == {"x": 42, "y": {"x": 33, "y": None}} | ||
assert Bar.from_dict({"x": 42, "y": {"x": 33, "y": None}}) == obj | ||
assert obj.to_json() == '{"x": 42, "y": {"x": 33, "y": null}}' | ||
assert Bar.from_json('{"x": 42, "y": {"x": 33, "y": null}}') == obj |
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,27 @@ | ||
from dataclasses import dataclass | ||
from datetime import date | ||
|
||
from mashumaro import DataClassDictMixin | ||
from mashumaro.codecs import BasicDecoder, BasicEncoder | ||
|
||
|
||
def test_type_alias_type_with_dataclass_dict_mixin(): | ||
type MyDate = date | ||
|
||
@dataclass | ||
class MyClass(DataClassDictMixin): | ||
x: MyDate | ||
|
||
obj = MyClass(date(2024, 4, 15)) | ||
assert MyClass.from_dict({"x": "2024-04-15"}) == obj | ||
assert obj.to_dict() == {"x": "2024-04-15"} | ||
|
||
|
||
def test_type_alias_type_with_codecs(): | ||
type MyDate = date | ||
decoder = BasicDecoder(MyDate) | ||
encoder = BasicEncoder(MyDate) | ||
|
||
obj = date(2024, 4, 15) | ||
assert decoder.decode("2024-04-15") == obj | ||
assert encoder.encode(obj) == "2024-04-15" |