Skip to content

Commit

Permalink
Add support for PEP 695 aliases
Browse files Browse the repository at this point in the history
  • Loading branch information
Fatal1ty committed Apr 15, 2024
1 parent 0dd5377 commit 9edb81e
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 2 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1815,8 +1815,9 @@ assert a1_dict == a2_dict == a3_dict == a4_dict == {"x": my_class_instance}

There are situations where you might want some values of the same type to be
treated as their own type. You can create new logical types with
[`NewType`](https://docs.python.org/3/library/typing.html#newtype) or
[`NewType`](https://docs.python.org/3/library/typing.html#newtype),
[`Annotated`](https://docs.python.org/3/library/typing.html#typing.Annotated)
or [`TypeAliasType`](https://docs.python.org/3/library/typing.html#typing.TypeAliasType)
and register serialization strategies for them:

```python
Expand All @@ -1827,6 +1828,8 @@ from mashumaro import DataClassDictMixin
SessionID = NewType("SessionID", str)
AccountID = Annotated[str, "AccountID"]

type DeviceID = str

@dataclass
class Context(DataClassDictMixin):
account_sessions: Mapping[AccountID, SessionID]
Expand All @@ -1840,6 +1843,10 @@ class Context(DataClassDictMixin):
SessionID: {
"deserialize": lambda x: ...,
"serialize": lambda x: ...,
},
DeviceID: {
"deserialize": lambda x: ...,
"serialize": lambda x: ...,
}
}
```
Expand Down
9 changes: 9 additions & 0 deletions mashumaro/core/meta/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
PY_39_MIN,
PY_310_MIN,
PY_311_MIN,
PY_312_MIN,
)
from mashumaro.dialect import Dialect

Expand Down Expand Up @@ -85,6 +86,7 @@
"is_hashable_type",
"evaluate_forward_ref",
"get_forward_ref_referencing_globals",
"is_type_alias_type",
]


Expand Down Expand Up @@ -793,3 +795,10 @@ def get_forward_ref_referencing_globals(
)
else:
return getattr(forward_module, "__dict__", fallback)


def is_type_alias_type(typ: Type) -> bool:
if PY_312_MIN:
return isinstance(typ, typing.TypeAliasType)
else:
return False
3 changes: 3 additions & 0 deletions mashumaro/core/meta/types/pack.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
is_required,
is_self,
is_special_typing_primitive,
is_type_alias_type,
is_type_var,
is_type_var_any,
is_type_var_tuple,
Expand Down Expand Up @@ -516,6 +517,8 @@ def pack_special_typing_primitive(spec: ValueSpec) -> Optional[Expression]:
)
if evaluated is not None:
return PackerRegistry.get(spec.copy(type=evaluated))
elif is_type_alias_type(spec.type):
return PackerRegistry.get(spec.copy(type=spec.type.__value__))
raise UnserializableDataError(
f"{spec.type} as a field type is not supported by mashumaro"
)
Expand Down
3 changes: 3 additions & 0 deletions mashumaro/core/meta/types/unpack.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
is_required,
is_self,
is_special_typing_primitive,
is_type_alias_type,
is_type_var,
is_type_var_any,
is_type_var_tuple,
Expand Down Expand Up @@ -803,6 +804,8 @@ def unpack_special_typing_primitive(spec: ValueSpec) -> Optional[Expression]:
)
if evaluated is not None:
return UnpackerRegistry.get(spec.copy(type=evaluated))
elif is_type_alias_type(spec.type):
return UnpackerRegistry.get(spec.copy(type=spec.type.__value__))
raise UnserializableDataError(
f"{spec.type} as a field type is not supported by mashumaro"
)
Expand Down
5 changes: 4 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from unittest.mock import patch

from mashumaro.core.const import PY_313_MIN
from mashumaro.core.const import PY_312_MIN, PY_313_MIN

if not PY_312_MIN:
collect_ignore = ["test_pep_695.py"]

if PY_313_MIN:
collect_ignore = [
Expand Down
27 changes: 27 additions & 0 deletions tests/test_pep_695.py
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"

0 comments on commit 9edb81e

Please sign in to comment.