diff --git a/README.md b/README.md index 4b094c49..91b93450 100644 --- a/README.md +++ b/README.md @@ -221,6 +221,7 @@ for special primitives from the [`typing`](https://docs.python.org/3/library/typ for standard interpreter types from [`types`](https://docs.python.org/3/library/types.html#standard-interpreter-types) module: * [`NoneType`](https://docs.python.org/3/library/types.html#types.NoneType) * [`UnionType`](https://docs.python.org/3/library/types.html#types.UnionType) +* [`MappingProxyType`](https://docs.python.org/3/library/types.html#types.MappingProxyType) for enumerations based on classes from the standard [`enum`](https://docs.python.org/3/library/enum.html) module: * [`Enum`](https://docs.python.org/3/library/enum.html#enum.Enum) diff --git a/mashumaro/core/meta/types/unpack.py b/mashumaro/core/meta/types/unpack.py index 986cfd11..62c11829 100644 --- a/mashumaro/core/meta/types/unpack.py +++ b/mashumaro/core/meta/types/unpack.py @@ -1230,6 +1230,12 @@ def inner_expr( ) elif is_typed_dict(spec.origin_type): return unpack_typed_dict(spec) + elif issubclass(spec.origin_type, types.MappingProxyType): + spec.builder.ensure_module_imported(types) + return ( + f'types.MappingProxyType({{{inner_expr(0, "key")}: {inner_expr(1)}' + f" for key, value in {spec.expression}.items()}})" + ) elif ensure_generic_mapping(spec, args, typing.Mapping): return ( f'{{{inner_expr(0, "key")}: {inner_expr(1)} ' diff --git a/tests/test_data_types.py b/tests/test_data_types.py index ea5f0e68..d50d0cfc 100644 --- a/tests/test_data_types.py +++ b/tests/test_data_types.py @@ -16,6 +16,7 @@ WindowsPath, ) from queue import Queue +from types import MappingProxyType from typing import ( Any, AnyStr, @@ -123,6 +124,7 @@ class Fixture: DICT = {"a": 1, "b": 2} ORDERED_DICT = collections.OrderedDict(a=1, b=2) DEFAULT_DICT = collections.defaultdict(int, a=1, b=2) + MAPPING_PROXY = MappingProxyType(DICT) DEFAULT_NONE_DICT = collections.defaultdict(None, a=1, b=2) COUNTER: Counter[str] = collections.Counter(a=1, b=2) BYTES = b"123" @@ -334,7 +336,13 @@ class Fixture: ) if PY_39_MIN: - inner_values.append((ZoneInfo, ZoneInfo("Europe/Moscow"), "Europe/Moscow")) + inner_values.extend( + ( + (ZoneInfo, ZoneInfo("Europe/Moscow"), "Europe/Moscow"), + (MappingProxyType[str, int], Fixture.MAPPING_PROXY, Fixture.DICT), + (MappingProxyType, Fixture.MAPPING_PROXY, Fixture.DICT), + ) + ) hashable_inner_values = [ diff --git a/tests/test_meta.py b/tests/test_meta.py index 798a5ec8..e50cb08a 100644 --- a/tests/test_meta.py +++ b/tests/test_meta.py @@ -1,5 +1,6 @@ import collections import collections.abc +import types import typing from dataclasses import InitVar, dataclass from datetime import datetime @@ -8,7 +9,12 @@ import typing_extensions from mashumaro import DataClassDictMixin -from mashumaro.core.const import PEP_585_COMPATIBLE, PY_38, PY_310_MIN +from mashumaro.core.const import ( + PEP_585_COMPATIBLE, + PY_38, + PY_39_MIN, + PY_310_MIN, +) from mashumaro.core.meta.code.builder import CodeBuilder # noinspection PyProtectedMember @@ -229,6 +235,11 @@ def test_type_name(): ) assert type_name(typing.Optional[NoneType]) == "NoneType" + if PY_39_MIN: + assert ( + type_name(types.MappingProxyType[int, int]) + == "mappingproxy[int, int]" + ) if PY_310_MIN: assert type_name(int | None) == "typing.Optional[int]" assert type_name(None | int) == "typing.Optional[int]" @@ -340,6 +351,11 @@ def test_type_name_short(): ) assert type_name(typing.Optional[NoneType], short=True) == "NoneType" + if PY_39_MIN: + assert ( + type_name(types.MappingProxyType[int, int], short=True) + == "mappingproxy[int, int]" + ) if PY_310_MIN: assert type_name(int | None, short=True) == "Optional[int]" assert type_name(None | int, short=True) == "Optional[int]"