From 00c5eace57e201b2eb26741ec36d13221f5a5ad4 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2026 14:56:28 +0000 Subject: [PATCH] fix(low-code): keep rendered string as-is when value_type is string Co-Authored-By: bot_apk --- .../declarative/interpolation/jinja.py | 3 ++ .../declarative/interpolation/test_jinja.py | 47 +++++++++++++++++++ .../test_config_components_resolver.py | 11 +++++ .../transformations/test_add_fields.py | 18 +++++++ 4 files changed, 79 insertions(+) diff --git a/airbyte_cdk/sources/declarative/interpolation/jinja.py b/airbyte_cdk/sources/declarative/interpolation/jinja.py index 415a0c8572..e4cc38f373 100644 --- a/airbyte_cdk/sources/declarative/interpolation/jinja.py +++ b/airbyte_cdk/sources/declarative/interpolation/jinja.py @@ -123,6 +123,9 @@ def eval( return self._literal_eval(self._eval(default, context), valid_types) def _literal_eval(self, result: Optional[str], valid_types: Optional[Tuple[Type[Any]]]) -> Any: + if valid_types == (str,): + return result + try: evaluated = ast.literal_eval(result) # type: ignore # result may be None; on error we return it unchanged except (ValueError, SyntaxError, TypeError): diff --git a/unit_tests/sources/declarative/interpolation/test_jinja.py b/unit_tests/sources/declarative/interpolation/test_jinja.py index c358fe4aa5..c058f39db4 100644 --- a/unit_tests/sources/declarative/interpolation/test_jinja.py +++ b/unit_tests/sources/declarative/interpolation/test_jinja.py @@ -68,6 +68,53 @@ def test_literals(s, value): assert val == value +@pytest.mark.parametrize( + "value", + [ + pytest.param('"nike shoes"', id="double-quoted-string"), + pytest.param("'abc'", id="single-quoted-string"), + pytest.param("plain", id="plain-string"), + pytest.param("123", id="integer-looking-string"), + pytest.param("1.5", id="float-looking-string"), + pytest.param("[1, 2]", id="list-looking-string"), + pytest.param("True", id="boolean-looking-string"), + ], +) +def test_eval_with_str_valid_type_returns_rendered_string_as_is(value): + result = JinjaInterpolation().eval( + "{{ record['k'] }}", config={}, valid_types=(str,), record={"k": value} + ) + + assert result == value + assert isinstance(result, str) + + +def test_eval_with_str_valid_type_returns_default_as_is(): + result = JinjaInterpolation().eval( + "{{ record['missing'] }}", + config={}, + default='"quoted default"', + valid_types=(str,), + record={}, + ) + + assert result == '"quoted default"' + + +def test_eval_without_valid_types_still_literal_evaluates(): + result = JinjaInterpolation().eval("{{ record['k'] }}", {}, record={"k": '"abc"'}) + + assert result == "abc" + + +def test_eval_with_int_valid_type_still_literal_evaluates(): + result = JinjaInterpolation().eval( + "{{ record['k'] }}", {}, valid_types=(int,), record={"k": "123"} + ) + + assert result == 123 + + @pytest.mark.parametrize( "context, input_string, expected_value", [ diff --git a/unit_tests/sources/declarative/resolvers/test_config_components_resolver.py b/unit_tests/sources/declarative/resolvers/test_config_components_resolver.py index 2e5f6ceaca..b51e955ee9 100644 --- a/unit_tests/sources/declarative/resolvers/test_config_components_resolver.py +++ b/unit_tests/sources/declarative/resolvers/test_config_components_resolver.py @@ -435,6 +435,13 @@ def test_component_mapping_conditions(manifest, config, expected_conditional_par "value_type": "string", "create_or_update": True, }, + { + "type": "ComponentMappingDefinition", + "field_path": ["retriever", "requester", "$parameters", "quoted_string"], + "value": '"quoted resolver value"', + "value_type": "string", + "create_or_update": True, + }, { "type": "ComponentMappingDefinition", "field_path": ["retriever", "requester", "$parameters", "json_parsed"], @@ -480,6 +487,10 @@ def test_value_type_str_avoids_yaml_parsing(): assert isinstance(params["json_string"], str) assert params["json_string"] == "[1, 2]" + assert "quoted_string" in params + assert isinstance(params["quoted_string"], str) + assert params["quoted_string"] == '"quoted resolver value"' + # No value_type -> YAML parsed assert "as_yaml" in params assert isinstance(params["as_yaml"], bool) diff --git a/unit_tests/sources/declarative/transformations/test_add_fields.py b/unit_tests/sources/declarative/transformations/test_add_fields.py index 94b6f07ed8..3cee15ef02 100644 --- a/unit_tests/sources/declarative/transformations/test_add_fields.py +++ b/unit_tests/sources/declarative/transformations/test_add_fields.py @@ -41,6 +41,24 @@ {"k": "v", "path": "1"}, id="add an expression evaluated as a string using the value_type field", ), + pytest.param( + {"keys": ['"nike shoes"']}, + [(["path"], "{{ record['keys'][0] }}")], + str, + "", + {}, + {"keys": ['"nike shoes"'], "path": '"nike shoes"'}, + id="keep double-quoted rendered expression as a string", + ), + pytest.param( + {"keys": ["'abc'"]}, + [(["path"], "{{ record['keys'][0] }}")], + str, + "", + {}, + {"keys": ["'abc'"], "path": "'abc'"}, + id="keep single-quoted rendered expression as a string", + ), pytest.param( {"k": "v"}, [(["path"], "static_value"), (["path2"], "static_value2")],