diff --git a/hologram/__init__.py b/hologram/__init__.py index b550ff9..fe4a4cf 100644 --- a/hologram/__init__.py +++ b/hologram/__init__.py @@ -793,9 +793,12 @@ def _get_field_schema( @classmethod def _get_field_definitions(cls, field_type: Any, definitions: JsonDict): field_type_name = cls._get_field_type_name(field_type) - if ( - is_optional(field_type) and len(field_type.__args__) == 2 - ) or field_type_name in ("Sequence", "List", "Tuple",): + if field_type_name == "Tuple": + # tuples are either like Tuple[T, ...] or Tuple[T1, T2, T3]. + for member in field_type.__args__: + if member is not ...: + cls._get_field_definitions(member, definitions) + elif field_type_name in ("Sequence", "List"): cls._get_field_definitions(field_type.__args__[0], definitions) elif field_type_name in ("Dict", "Mapping"): cls._get_field_definitions(field_type.__args__[1], definitions) diff --git a/tests/test_multi_optional_definitions.py b/tests/test_multi_optional_definitions.py index 566b875..61e5e6a 100644 --- a/tests/test_multi_optional_definitions.py +++ b/tests/test_multi_optional_definitions.py @@ -26,10 +26,12 @@ class RestrictC(JsonSchemaMixin): foo: MySelector = field(metadata={"restrict": [MySelector.C]}) baz: str + @dataclass class A(JsonSchemaMixin): baz: str + @dataclass class B(JsonSchemaMixin): baz: str @@ -77,9 +79,18 @@ class IHaveExtremelyAnnoyingUnions(JsonSchemaMixin): def test_evil_union(): pairs = [ - (IHaveExtremelyAnnoyingUnions(my_field=True).to_dict(), {"my_field": True}), - (IHaveExtremelyAnnoyingUnions(my_field='1').to_dict(), {"my_field": '1'}), - (IHaveExtremelyAnnoyingUnions(my_field=1.0).to_dict(), {"my_field": 1.0}), + ( + IHaveExtremelyAnnoyingUnions(my_field=True).to_dict(), + {"my_field": True}, + ), + ( + IHaveExtremelyAnnoyingUnions(my_field="1").to_dict(), + {"my_field": "1"}, + ), + ( + IHaveExtremelyAnnoyingUnions(my_field=1.0).to_dict(), + {"my_field": 1.0}, + ), (IHaveExtremelyAnnoyingUnions().to_dict(), {}), ] for a, b in pairs: diff --git a/tests/test_tuple.py b/tests/test_tuple.py new file mode 100644 index 0000000..a835178 --- /dev/null +++ b/tests/test_tuple.py @@ -0,0 +1,47 @@ +from dataclasses import dataclass +from typing import Tuple + +from hologram import JsonSchemaMixin + + +@dataclass +class TupleMember(JsonSchemaMixin): + a: int + + +@dataclass +class TupleEllipsisHolder(JsonSchemaMixin): + member: Tuple[TupleMember, ...] + + +@dataclass +class TupleMemberFirstHolder(JsonSchemaMixin): + member: Tuple[TupleMember, str] + + +@dataclass +class TupleMemberSecondHolder(JsonSchemaMixin): + member: Tuple[str, TupleMember] + + +def test_ellipsis_tuples(): + dct = {"member": [{"a": 1}, {"a": 2}, {"a": 3}]} + value = TupleEllipsisHolder( + member=(TupleMember(1), TupleMember(2), TupleMember(3)) + ) + assert value.to_dict() == dct + assert TupleEllipsisHolder.from_dict(dct) == value + + +def test_member_first_tuple(): + dct = {"member": [{"a": 1}, "a"]} + value = TupleMemberFirstHolder(member=(TupleMember(1), "a")) + TupleMemberFirstHolder.from_dict(dct) == value + value.to_dict() == dct + + +def test_member_second_tuple(): + dct = {"member": ["a", {"a": 1}]} + value = TupleMemberSecondHolder(member=("a", TupleMember(1))) + TupleMemberSecondHolder.from_dict(dct) == value + value.to_dict() == dct diff --git a/tests/test_union.py b/tests/test_union.py index 5138969..a2f5677 100644 --- a/tests/test_union.py +++ b/tests/test_union.py @@ -49,3 +49,24 @@ def test_union_decode_error(): with pytest.raises(ValidationError): IHaveAnnoyingUnions.from_dict({"my_field": {">=0.0.0"}}) + + +@dataclass +class UnionMember(JsonSchemaMixin): + a: int + + +@dataclass +class LongOptionalUnion(JsonSchemaMixin): + # this devolves into Union[None, UnionMember] + member: Optional[Union[None, UnionMember]] + + +def test_long_union_decoding(): + x = LongOptionalUnion(None) + x.to_dict() == {"member": None} + LongOptionalUnion.from_dict({"member": None}) + + x = LongOptionalUnion(UnionMember(1)) + x.to_dict() == {"member": {"a": 1}} + LongOptionalUnion.from_dict({"member": {"a": 1}}) == x