Skip to content

Commit 25132b6

Browse files
authored
feat(dynamic-streams): Avoid parsing strings in ConfigComponentResolver (#769)
1 parent 90fdec5 commit 25132b6

File tree

2 files changed

+96
-1
lines changed

2 files changed

+96
-1
lines changed

airbyte_cdk/sources/declarative/resolvers/config_components_resolver.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,11 @@ def resolve_components(
177177
)
178178

179179
path = [path.eval(self.config, **kwargs) for path in resolved_component.field_path]
180-
parsed_value = self._parse_yaml_if_possible(value)
180+
# Avoid parsing strings that are meant to be strings
181+
if not (isinstance(value, str) and valid_types == (str,)):
182+
parsed_value = self._parse_yaml_if_possible(value)
183+
else:
184+
parsed_value = value
181185
updated = dpath.set(updated_config, path, parsed_value)
182186

183187
if parsed_value and not updated and resolved_component.create_or_update:

unit_tests/sources/declarative/resolvers/test_config_components_resolver.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import json
66
from copy import deepcopy
7+
from datetime import date as dt_date
78
from unittest.mock import MagicMock
89

910
import pytest
@@ -386,3 +387,93 @@ def test_component_mapping_conditions(manifest, config, expected_conditional_par
386387
stream._stream_partition_generator._partition_factory._retriever.requester._parameters
387388
== expected_conditional_params[stream.name]
388389
)
390+
391+
392+
_MANIFEST_WITH_VALUE_TYPE_STR = deepcopy(_MANIFEST)
393+
_MANIFEST_WITH_VALUE_TYPE_STR["dynamic_streams"][0]["components_resolver"][
394+
"components_mapping"
395+
].extend(
396+
[
397+
{
398+
"type": "ComponentMappingDefinition",
399+
"field_path": ["retriever", "requester", "$parameters", "as_string"],
400+
"value": "true", # valid YAML, but we want to keep it as *string*
401+
"value_type": "string",
402+
"create_or_update": True,
403+
},
404+
{
405+
"type": "ComponentMappingDefinition",
406+
"field_path": ["retriever", "requester", "$parameters", "as_yaml"],
407+
"value": "true", # no value_type -> should be parsed to boolean True
408+
"create_or_update": True,
409+
},
410+
{
411+
"type": "ComponentMappingDefinition",
412+
"field_path": ["retriever", "requester", "$parameters", "json_string"],
413+
"value": "[1, 2]", # valid YAML/JSON-looking text; keep as string
414+
"value_type": "string",
415+
"create_or_update": True,
416+
},
417+
{
418+
"type": "ComponentMappingDefinition",
419+
"field_path": ["retriever", "requester", "$parameters", "json_parsed"],
420+
"value": "[1, 2]", # no value_type -> should parse to a list
421+
"create_or_update": True,
422+
},
423+
{
424+
"type": "ComponentMappingDefinition",
425+
"field_path": ["retriever", "requester", "$parameters", "date_as_string"],
426+
"value": "2024-07-10", # date-like text that YAML would parse; force keep as string
427+
"value_type": "string",
428+
"create_or_update": True,
429+
},
430+
{
431+
"type": "ComponentMappingDefinition",
432+
"field_path": ["retriever", "requester", "$parameters", "date_yaml_parsed"],
433+
"value": "2024-07-10", # no value_type -> YAML should parse to datetime.date
434+
"create_or_update": True,
435+
},
436+
]
437+
)
438+
439+
440+
def test_value_type_str_avoids_yaml_parsing():
441+
source = ConcurrentDeclarativeSource(
442+
source_config=_MANIFEST_WITH_VALUE_TYPE_STR, config=_CONFIG, catalog=None, state=None
443+
)
444+
445+
for stream in source.streams(_CONFIG):
446+
params = (
447+
stream._stream_partition_generator._partition_factory._retriever.requester._parameters
448+
)
449+
450+
# Confirm the usual param is still present
451+
assert "item_id" in params
452+
453+
# value_type="string" -> keep as string
454+
assert "as_string" in params
455+
assert isinstance(params["as_string"], str)
456+
assert params["as_string"] == "true"
457+
458+
assert "json_string" in params
459+
assert isinstance(params["json_string"], str)
460+
assert params["json_string"] == "[1, 2]"
461+
462+
# No value_type -> YAML parsed
463+
assert "as_yaml" in params
464+
assert isinstance(params["as_yaml"], bool)
465+
assert params["as_yaml"] is True
466+
467+
assert "json_parsed" in params
468+
assert isinstance(params["json_parsed"], list)
469+
assert params["json_parsed"] == [1, 2]
470+
471+
# value_type="string" -> remains a plain string
472+
assert "date_as_string" in params
473+
assert isinstance(params["date_as_string"], str)
474+
assert params["date_as_string"] == "2024-07-10"
475+
476+
# no value_type -> YAML parses to datetime.date
477+
assert "date_yaml_parsed" in params
478+
assert isinstance(params["date_yaml_parsed"], dt_date)
479+
assert params["date_yaml_parsed"].isoformat() == "2024-07-10"

0 commit comments

Comments
 (0)