Skip to content

Commit

Permalink
Merge branch 'fix-optional-inside-tuple'
Browse files Browse the repository at this point in the history
  • Loading branch information
Fatal1ty committed Apr 15, 2022
2 parents 9c03c74 + 5ffab59 commit 8ab5856
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 0 deletions.
10 changes: 10 additions & 0 deletions mashumaro/core/meta/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -1554,6 +1554,7 @@ def _pack_tuple(self, fname, value_name, args, parent, metadata) -> str:
parent,
value_name,
metadata=metadata,
could_be_none=True,
)
return f"[{packer} for value in value]"
else:
Expand All @@ -1564,6 +1565,7 @@ def _pack_tuple(self, fname, value_name, args, parent, metadata) -> str:
parent,
f"{value_name}[{arg_idx}]",
metadata=metadata,
could_be_none=True,
)
for arg_idx, arg_type in enumerate(args)
]
Expand All @@ -1581,6 +1583,7 @@ def _unpack_tuple(self, fname, value_name, args, parent, metadata) -> str:
parent,
value_name,
metadata=metadata,
could_be_none=True,
)
return f"tuple([{unpacker} for value in value])"
else:
Expand All @@ -1591,6 +1594,7 @@ def _unpack_tuple(self, fname, value_name, args, parent, metadata) -> str:
parent,
f"{value_name}[{arg_idx}]",
metadata=metadata,
could_be_none=True,
)
for arg_idx, arg_type in enumerate(args)
]
Expand Down Expand Up @@ -1622,6 +1626,7 @@ def _pack_typed_dict(
parent,
f"{value_name}['{key}']",
metadata=metadata,
could_be_none=True,
)
lines.append(f"d['{key}'] = {packer}")
for key in sorted(optional_keys, key=all_keys.index):
Expand All @@ -1634,6 +1639,7 @@ def _pack_typed_dict(
parent,
"key_value",
metadata=metadata,
could_be_none=True,
)
lines.append(f"d['{key}'] = {packer}")
lines.append("return d")
Expand Down Expand Up @@ -1674,6 +1680,7 @@ def _unpack_typed_dict(
parent,
f"{value_name}['{key}']",
metadata=metadata,
could_be_none=True,
)
lines.append(f"d['{key}'] = {unpacker}")
for key in sorted(optional_keys, key=all_keys.index):
Expand All @@ -1686,6 +1693,7 @@ def _unpack_typed_dict(
parent,
"key_value",
metadata=metadata,
could_be_none=True,
)
lines.append(f"d['{key}'] = {unpacker}")
lines.append("return d")
Expand Down Expand Up @@ -1722,6 +1730,7 @@ def _pack_named_tuple(
parent,
f"{value_name}[{idx}]",
metadata=metadata,
could_be_none=True,
)
packers.append(packer)
if as_dict:
Expand Down Expand Up @@ -1758,6 +1767,7 @@ def _unpack_named_tuple(
parent,
f"{value_name}[{idx}]",
metadata=metadata,
could_be_none=True,
)
unpackers.append(unpacker)

Expand Down
15 changes: 15 additions & 0 deletions tests/entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,16 @@ class TypedDictRequiredAndOptionalKeys(TypedDictRequiredKeys, total=False):
str: str


class TypedDictRequiredKeysWithOptional(TypedDict):
x: Optional[int]
y: int


class TypedDictOptionalKeysWithOptional(TypedDict, total=False):
x: Optional[int]
y: float


class MyNamedTuple(NamedTuple):
i: int
f: float
Expand All @@ -228,6 +238,11 @@ class MyNamedTupleWithDefaults(NamedTuple):
f: float = 2.0


class MyNamedTupleWithOptional(NamedTuple):
i: Optional[int]
f: int


MyUntypedNamedTuple = namedtuple("MyUntypedNamedTuple", ("i", "f"))


Expand Down
72 changes: 72 additions & 0 deletions tests/test_data_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
MyIntFlag,
MyNamedTuple,
MyNamedTupleWithDefaults,
MyNamedTupleWithOptional,
MyStrEnum,
MyUntypedNamedTuple,
SerializableTypeDataClass,
Expand All @@ -82,8 +83,10 @@
TIntStr,
TMyDataClass,
TypedDictOptionalKeys,
TypedDictOptionalKeysWithOptional,
TypedDictRequiredAndOptionalKeys,
TypedDictRequiredKeys,
TypedDictRequiredKeysWithOptional,
)
from .utils import same_types

Expand Down Expand Up @@ -1234,3 +1237,72 @@ class Config(BaseConfig):
instance = DataClass("a", MyStr("b"))
assert DataClass.from_dict({"x": "str_a", "y": "MyStr_b"}) == instance
assert instance.to_dict() == {"x": "str_a", "y": "MyStr_b"}


def test_tuple_with_optional():
@dataclass
class DataClass(DataClassDictMixin):
x: Tuple[Optional[int], int] = field(default_factory=lambda: (None, 7))

assert DataClass.from_dict({"x": [None, 42]}) == DataClass((None, 42))
assert DataClass((None, 42)).to_dict() == {"x": [None, 42]}
assert DataClass.from_dict({}) == DataClass((None, 7))
assert DataClass().to_dict() == {"x": [None, 7]}


def test_tuple_with_optional_and_ellipsis():
@dataclass
class DataClass(DataClassDictMixin):
x: Tuple[Optional[int], ...] = field(default_factory=lambda: (None, 7))

assert DataClass.from_dict({"x": [None, 42]}) == DataClass((None, 42))
assert DataClass((None, 42)).to_dict() == {"x": [None, 42]}
assert DataClass.from_dict({}) == DataClass((None, 7))
assert DataClass().to_dict() == {"x": [None, 7]}


def test_named_tuple_with_optional():
@dataclass
class DataClass(DataClassDictMixin):
x: MyNamedTupleWithOptional = field(
default_factory=lambda: MyNamedTupleWithOptional(None, 7)
)

assert DataClass.from_dict({"x": [None, 42]}) == DataClass(
MyNamedTupleWithOptional(None, 42)
)
assert DataClass(MyNamedTupleWithOptional(None, 42)).to_dict() == {
"x": [None, 42]
}
assert DataClass.from_dict({}) == DataClass(
MyNamedTupleWithOptional(None, 7)
)
assert DataClass().to_dict() == {"x": [None, 7]}


def test_typed_dict_required_keys_with_optional():
@dataclass
class DataClass(DataClassDictMixin):
x: TypedDictRequiredKeysWithOptional

obj = DataClass({"x": None, "y": 42})
assert DataClass.from_dict({"x": {"x": None, "y": 42}}) == obj
assert obj.to_dict() == {"x": {"x": None, "y": 42}}

obj = DataClass({"x": 33, "y": 42})
assert DataClass.from_dict({"x": {"x": 33, "y": 42}}) == obj
assert obj.to_dict()


def test_typed_dict_optional_keys_with_optional():
@dataclass
class DataClass(DataClassDictMixin):
x: TypedDictOptionalKeysWithOptional

obj = DataClass({"x": None, "y": 42})
assert DataClass.from_dict({"x": {"x": None, "y": 42}}) == obj
assert obj.to_dict() == {"x": {"x": None, "y": 42}}

obj = DataClass({"x": 33, "y": 42})
assert DataClass.from_dict({"x": {"x": 33, "y": 42}}) == obj
assert obj.to_dict()

0 comments on commit 8ab5856

Please sign in to comment.