Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Nested schema properties can now be defined as nullable #2488

Merged
merged 7 commits into from
Oct 18, 2024
30 changes: 25 additions & 5 deletions singer_sdk/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,15 +206,18 @@ def __init__(
*,
allowed_values: list[T] | None = None,
examples: list[T] | None = None,
nullable: bool | None = None,
) -> None:
"""Initialize the type helper.

Args:
allowed_values: A list of allowed values.
examples: A list of example values.
nullable: If True, the property may be null.
"""
self.allowed_values = allowed_values
self.examples = examples
self.nullable = nullable

@DefaultInstanceProperty
def type_dict(self) -> dict:
Expand Down Expand Up @@ -273,6 +276,8 @@ class StringType(JSONTypeHelper[str]):
{'type': ['string'], 'enum': ['a', 'b']}
>>> StringType(max_length=10).type_dict
{'type': ['string'], 'maxLength': 10}
>>> StringType(max_length=10, nullable=True).type_dict
{'type': ['string', 'null'], 'maxLength': 10}
"""

string_format: str | None = None
Expand Down Expand Up @@ -321,7 +326,7 @@ def type_dict(self) -> dict:
A dictionary describing the type.
"""
result = {
"type": ["string"],
"type": ["string", "null"] if self.nullable else ["string"],
**self._format,
**self.extras,
}
Expand Down Expand Up @@ -460,7 +465,10 @@ def type_dict(self) -> dict:
Returns:
A dictionary describing the type.
"""
return {"type": ["boolean"], **self.extras}
return {
"type": ["boolean", "null"] if self.nullable else ["boolean"],
**self.extras,
}


class _NumericType(JSONTypeHelper[T]):
Expand Down Expand Up @@ -507,7 +515,12 @@ def type_dict(self) -> dict:
Returns:
A dictionary describing the type.
"""
result = {"type": [self.__type_name__], **self.extras}
result = {
"type": [self.__type_name__, "null"]
if self.nullable
else [self.__type_name__],
**self.extras,
}

if self.minimum is not None:
result["minimum"] = self.minimum
Expand Down Expand Up @@ -592,7 +605,11 @@ def type_dict(self) -> dict: # type: ignore[override]
Returns:
A dictionary describing the type.
"""
return {"type": "array", "items": self.wrapped_type.type_dict, **self.extras}
return {
"type": ["array", "null"] if self.nullable else "array",
"items": self.wrapped_type.type_dict,
**self.extras,
}


class AnyType(JSONTypeHelper):
Expand Down Expand Up @@ -835,7 +852,10 @@ def type_dict(self) -> dict: # type: ignore[override]
merged_props.update(w.to_dict())
if not w.optional:
required.append(w.name)
result: dict[str, t.Any] = {"type": "object", "properties": merged_props}
result: dict[str, t.Any] = {
"type": ["object", "null"] if self.nullable else "object",
"properties": merged_props,
}

if required:
result["required"] = required
Expand Down
Loading