From 316282e08d04e17d0917e344a8bcdc012e30042b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= <16805946+edgarrmondragon@users.noreply.github.com> Date: Thu, 17 Oct 2024 21:55:20 -0600 Subject: [PATCH] feat: Nested schema properties can now be defined as nullable (#2488) feat: Let nested properties be nullable Closes https://github.com/meltano/sdk/issues/2487 --- singer_sdk/typing.py | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/singer_sdk/typing.py b/singer_sdk/typing.py index 7d0212ca3..845f1dcd0 100644 --- a/singer_sdk/typing.py +++ b/singer_sdk/typing.py @@ -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: @@ -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 @@ -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, } @@ -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]): @@ -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 @@ -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): @@ -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