Skip to content

Commit 6c0f474

Browse files
committed
Interpret strings with respect to schema when conforming properties
1 parent dc7fd0d commit 6c0f474

File tree

2 files changed

+55
-1
lines changed

2 files changed

+55
-1
lines changed

singer_sdk/helpers/_typing.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import copy
66
import datetime
77
import decimal
8+
import json
89
import logging
910
import math
1011
import typing as t
@@ -518,7 +519,7 @@ def _conform_uniform_list(
518519
return output, unmapped_properties
519520

520521

521-
def _conform_primitive_property( # noqa: PLR0911
522+
def _conform_primitive_property( # noqa: PLR0911, C901
522523
elem: t.Any, # noqa: ANN401
523524
property_schema: dict,
524525
) -> t.Any: # noqa: ANN401
@@ -542,6 +543,26 @@ def _conform_primitive_property( # noqa: PLR0911
542543
if math.isnan(elem) or math.isinf(elem):
543544
return None
544545
return elem
546+
if isinstance(elem, str) and not is_string_type(property_schema):
547+
return _interpret_string_property(elem, property_schema)
545548
if _is_exclusive_boolean_type(property_schema):
546549
return None if elem is None else elem != 0
547550
return elem
551+
552+
553+
def _interpret_string_property(
554+
elem: str,
555+
property_schema: dict,
556+
) -> t.Any: # noqa: ANN401
557+
if not elem:
558+
return None
559+
if is_boolean_type(property_schema):
560+
return elem.lower() == "true"
561+
if is_integer_type(property_schema):
562+
return int(elem)
563+
if is_number_type(property_schema):
564+
d = decimal.Decimal(elem)
565+
return None if math.isnan(d) or math.isinf(d) else d
566+
if is_array_type(property_schema) or is_object_type(property_schema):
567+
return json.loads(elem)
568+
return elem

tests/core/test_typing.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,39 @@ def test_conform_object_additional_properties():
353353
pytest.param(
354354
decimal.Decimal("nan"), {"type": "number"}, None, id="decimal_nan_to_number"
355355
),
356+
pytest.param("", {"type": "string"}, "", id="string_empty_to_string"),
357+
pytest.param(
358+
"", {"type": "boolean"}, None, id="string_empty_to_any_non_string"
359+
),
360+
pytest.param("true", {"type": "boolean"}, True, id="string_true_to_boolean"),
361+
pytest.param(
362+
"TRUE", {"type": "boolean"}, True, id="string_true_uppercase_to_boolean"
363+
),
364+
pytest.param("false", {"type": "boolean"}, False, id="string_false_to_boolean"),
365+
pytest.param(
366+
"something else",
367+
{"type": "boolean"},
368+
False,
369+
id="string_not_true_to_boolean",
370+
),
371+
pytest.param("3", {"type": "integer"}, 3, id="string_integer_to_integer"),
372+
pytest.param(
373+
"3.14",
374+
{"type": "number"},
375+
decimal.Decimal("3.14"),
376+
id="string_float_to_number",
377+
),
378+
pytest.param("inf", {"type": "number"}, None, id="string_inf_to_number"),
379+
pytest.param("nan", {"type": "number"}, None, id="string_nan_to_number"),
380+
pytest.param(
381+
"[1, 2, 3]", {"type": "array"}, [1, 2, 3], id="string_json_array_to_array"
382+
),
383+
pytest.param(
384+
'{"a": 1, "b": true, "c": 3.14}',
385+
{"type": "object"},
386+
{"a": 1, "b": True, "c": 3.14},
387+
id="string_json_object_to_object",
388+
),
356389
],
357390
)
358391
def test_conform_primitives(value: t.Any, type_dict: dict, expected: t.Any):

0 commit comments

Comments
 (0)