From bb98db5782ab8ce3e0a078b4ae3777c1c9b514a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= Date: Fri, 22 Sep 2023 08:30:29 -0600 Subject: [PATCH] fix: Warn instead of crashing when schema helpers cannot append `null` to types (#1970) * Add failing test * Warn instead of crashing --------- Co-authored-by: Ken Payne --- singer_sdk/helpers/_typing.py | 13 +++++++------ singer_sdk/typing.py | 20 ++++++++++++++++++++ tests/core/test_jsonschema_helpers.py | 22 ++++++++++++++++++++++ 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/singer_sdk/helpers/_typing.py b/singer_sdk/helpers/_typing.py index d3df38a5b..6eb7b3879 100644 --- a/singer_sdk/helpers/_typing.py +++ b/singer_sdk/helpers/_typing.py @@ -4,21 +4,21 @@ import copy import datetime +import logging import typing as t from enum import Enum from functools import lru_cache import pendulum -if t.TYPE_CHECKING: - import logging - _MAX_TIMESTAMP = "9999-12-31 23:59:59.999999" _MAX_TIME = "23:59:59.999999" JSONSCHEMA_ANNOTATION_SECRET = "secret" # noqa: S105 JSONSCHEMA_ANNOTATION_WRITEONLY = "writeOnly" UTC = datetime.timezone.utc +logger = logging.getLogger(__name__) + class DatetimeErrorTreatmentEnum(Enum): """Enum for treatment options for date parsing error.""" @@ -67,11 +67,12 @@ def append_type(type_dict: dict, new_type: str) -> dict: result["type"] = [*type_array, new_type] return result - msg = ( + logger.warning( "Could not append type because the JSON schema for the dictionary " - f"`{type_dict}` appears to be invalid." + "`%s` appears to be invalid.", + type_dict, ) - raise ValueError(msg) + return result def is_secret_type(type_dict: dict) -> bool: diff --git a/singer_sdk/typing.py b/singer_sdk/typing.py index fd17b9d3d..a9c48303e 100644 --- a/singer_sdk/typing.py +++ b/singer_sdk/typing.py @@ -493,6 +493,26 @@ def type_dict(self) -> dict: # type: ignore[override] return {"type": "array", "items": self.wrapped_type.type_dict, **self.extras} +class AnyType(JSONTypeHelper): + """Any type.""" + + def __init__( + self, + *args: t.Any, + **kwargs: t.Any, + ) -> None: + super().__init__(*args, **kwargs) + + @DefaultInstanceProperty + def type_dict(self) -> dict: + """Get type dictionary. + + Returns: + A dictionary describing the type. + """ + return {**self.extras} + + class Property(JSONTypeHelper[T], t.Generic[T]): """Generic Property. Should be nested within a `PropertiesList`.""" diff --git a/tests/core/test_jsonschema_helpers.py b/tests/core/test_jsonschema_helpers.py index e1369dcba..8438a6168 100644 --- a/tests/core/test_jsonschema_helpers.py +++ b/tests/core/test_jsonschema_helpers.py @@ -4,6 +4,7 @@ import re import typing as t +from logging import WARNING from textwrap import dedent import pytest @@ -26,6 +27,7 @@ ) from singer_sdk.tap_base import Tap from singer_sdk.typing import ( + AnyType, ArrayType, BooleanType, CustomType, @@ -130,6 +132,26 @@ def test_to_json(): ) +def test_any_type(caplog: pytest.LogCaptureFixture): + schema = PropertiesList( + Property("any_type", AnyType, description="Can be anything"), + ) + with caplog.at_level(WARNING): + assert schema.to_dict() == { + "type": "object", + "properties": { + "any_type": { + "description": "Can be anything", + }, + }, + } + assert caplog.records[0].levelname == "WARNING" + assert caplog.records[0].message == ( + "Could not append type because the JSON schema for the dictionary `{}` " + "appears to be invalid." + ) + + def test_nested_complex_objects(): test1a = Property( "Datasets",