Skip to content

Commit

Permalink
RecapType.is_nullable should iterate on union types
Browse files Browse the repository at this point in the history
There was a bug in #423. Null types in a union with `extra_attrs` or other
attributes set would result in `False` returned when there was in fact a
NullType in the UnionType's `types` list. `__eq__` checks all attributes, so you
must iterate over the UnionType's `types` attribute and look for any type with
`isinstance(..., NullType) is True`. I updated the logic accordingly.

In doing so, I discovered that the JSON converter logic was converting JSON
schema fields of `null` type to a UnionType with a single nested NullType type.
This seems wrong; I updated the test to validate that `null` JSON fields are
returned as `NullType` with a default of `None`.
  • Loading branch information
criccomini committed Feb 12, 2024
1 parent 22d0e73 commit 00541ac
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 14 deletions.
10 changes: 6 additions & 4 deletions recap/converters/json_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ def _parse(
alias_strategy: AliasStrategy,
) -> RecapType:
extra_attrs = {}
# Check if json_schema is just a string representing a basic type, and convert
# to a dict with a "type" property if so
# Check if json_schema is just a string representing a basic type, and
# convert to a dict with a "type" property if so
if isinstance(json_schema, str):
json_schema = {"type": json_schema}
if "description" in json_schema:
Expand All @@ -79,11 +79,13 @@ def _parse(
fields = []
for name, prop in properties.items():
field = self._parse(prop, alias_strategy)
# If not explicitly required, make optional by ensuring the field is
# nullable, and has a default
# If not explicitly required, make optional by ensuring the
# field is nullable, and has a default
if name not in json_schema.get("required", []):
if not field.is_nullable():
field = field.make_nullable()
# is_nullable doesn't guarantee a default of none, so
# set it here.
if "default" not in field.extra_attrs:
field.extra_attrs["default"] = None

Expand Down
12 changes: 11 additions & 1 deletion recap/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,17 @@ def is_nullable(self) -> bool:
:return: True if the type is nullable.
"""

return isinstance(self, UnionType) and NullType() in self.types
if isinstance(self, UnionType):
# Can't do `NullType() in type_copy.types` because equality checks
# extra_attrs, which can vary. Instead, just look for any NullType
# instance.
for t in self.types:
if isinstance(t, NullType):
return True

return False

return isinstance(self, NullType)

def validate(self) -> None:
# Default to valid type
Expand Down
14 changes: 5 additions & 9 deletions tests/unit/converters/test_json_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,29 +39,25 @@ def test_all_basic_types():
assert struct_type.fields == [
UnionType(
[NullType(), StringType()],
name="a_string",
default=None,
name="a_string",
),
UnionType(
[NullType(), FloatType(bits=64)],
name="a_number",
default=None,
name="a_number",
),
UnionType(
[NullType(), IntType(bits=32, signed=True)],
name="an_integer",
default=None,
name="an_integer",
),
UnionType(
[NullType(), BoolType()],
name="a_boolean",
default=None,
),
UnionType(
[NullType()],
name="a_null",
default=None,
name="a_boolean",
),
NullType(default=None, name="a_null"),
]


Expand Down
36 changes: 36 additions & 0 deletions tests/unit/test_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1639,3 +1639,39 @@ def test_make_nullable_of_default_none():
],
default=None,
)


def test_is_nullable():
assert NullType().is_nullable() is True
assert NullType(doc="Something", foo=123).is_nullable() is True
assert IntType(bits=32).is_nullable() is False
assert (
UnionType(
types=[
NullType(),
IntType(bits=32),
],
default=None,
).is_nullable()
is True
)
assert (
UnionType(
types=[
IntType(bits=32),
NullType(doc="Something", foo=123),
],
default=123,
).is_nullable()
is True
)
assert (
UnionType(
types=[
IntType(bits=32),
StringType(bytes_=50),
],
default=123,
).is_nullable()
is False
)

0 comments on commit 00541ac

Please sign in to comment.