Skip to content

Commit

Permalink
šŸ› [low-code] $options shouldn't overwrite values that are already defā€¦
Browse files Browse the repository at this point in the history
ā€¦ined (#18060)

* fix

* Add missing test

* remove prints

* extract to method

* rename

* Add missing test

* rename

* bump
  • Loading branch information
girarda authored Oct 17, 2022
1 parent 792fa7c commit 0ffd503
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 27 deletions.
4 changes: 4 additions & 0 deletions airbyte-cdk/python/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 0.1.101

- Low-code: $options do not overwrite parameters that are already set

## 0.1.100

- Low-code: Pass stream_slice to read_records when reading from CheckStream
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#

import inspect
from typing import Any, Mapping

OPTIONS_STR = "$options"

Expand All @@ -23,6 +24,7 @@ def create(func, /, *args, **keywords):
"""

def newfunc(*fargs, **fkeywords):

all_keywords = {**keywords}
all_keywords.update(fkeywords)

Expand All @@ -39,8 +41,8 @@ def newfunc(*fargs, **fkeywords):
if config is not None:
all_keywords["config"] = config

kwargs_to_pass_down = _get_kwargs_to_pass_to_func(func, options)
all_keywords_to_pass_down = _get_kwargs_to_pass_to_func(func, all_keywords)
kwargs_to_pass_down = _get_kwargs_to_pass_to_func(func, options, all_keywords)
all_keywords_to_pass_down = _get_kwargs_to_pass_to_func(func, all_keywords, all_keywords)

# options is required as part of creation of all declarative components
dynamic_args = {**all_keywords_to_pass_down, **kwargs_to_pass_down}
Expand All @@ -63,17 +65,23 @@ def newfunc(*fargs, **fkeywords):
return newfunc


def _get_kwargs_to_pass_to_func(func, options):
def _get_kwargs_to_pass_to_func(func, options, existing_keyword_parameters):
argspec = inspect.getfullargspec(func)
kwargs_to_pass_down = set(argspec.kwonlyargs)
args_to_pass_down = set(argspec.args)
all_args = args_to_pass_down.union(kwargs_to_pass_down)
kwargs_to_pass_down = {k: v for k, v in options.items() if k in all_args}
kwargs_to_pass_down = {
k: v for k, v in options.items() if k in all_args and _key_is_unset_or_identical(k, v, existing_keyword_parameters)
}
if "options" in all_args:
kwargs_to_pass_down["options"] = options
return kwargs_to_pass_down


def _key_is_unset_or_identical(key: str, value: Any, mapping: Mapping[str, Any]):
return key not in mapping or mapping[key] == value


def _create_inner_objects(keywords, kwargs):
fully_created = dict()
for k, v in keywords.items():
Expand Down
2 changes: 1 addition & 1 deletion airbyte-cdk/python/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

setup(
name="airbyte-cdk",
version="0.1.100",
version="0.1.101",
description="A framework for writing Airbyte Connectors.",
long_description=README,
long_description_content_type="text/markdown",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
# Copyright (c) 2022 Airbyte, Inc., all rights reserved.
#

from airbyte_cdk.sources.declarative.create_partial import create
import pytest
from airbyte_cdk.sources.declarative.create_partial import _key_is_unset_or_identical, create
from airbyte_cdk.sources.declarative.interpolation.interpolated_string import InterpolatedString


Expand Down Expand Up @@ -33,6 +34,12 @@ def test_pass_parameter_to_create_function():
assert object.another_param == "B"


def test_parameter_not_overwritten_by_options():
object = create(AClass, parameter="A", another_param="B", **{"$options": {"parameter": "C"}})()
assert object.parameter == "A"
assert object.another_param == "B"


def test_overwrite_param():
object = create(AClass, parameter="A", another_param="B")(parameter="C")
assert object.parameter == "C"
Expand All @@ -46,7 +53,7 @@ def test_string_interpolation():
assert interpolated_string.string == s


def test_string_interpolation_through_kwargs():
def test_string_interpolation_through_options():
s = "{{ options['name'] }}"
options = {"name": "airbyte"}
partial = create(InterpolatedString, string=s, **options)
Expand All @@ -60,3 +67,17 @@ def test_string_interpolation_through_options_keyword():
partial = create(InterpolatedString, string=s, **options)
interpolated_string = partial()
assert interpolated_string.eval({}) == "airbyte"


@pytest.mark.parametrize(
"test_name, key, value, expected_result",
[
("test", "key", "value", True),
("test", "key", "a_different_value", False),
("test", "a_different_key", "value", True),
],
)
def test_key_is_unset_or_identical(test_name, key, value, expected_result):
mapping = {"key": "value"}
result = _key_is_unset_or_identical(key, value, mapping)
assert expected_result == result
71 changes: 51 additions & 20 deletions airbyte-cdk/python/unit_tests/sources/declarative/test_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,31 +414,31 @@ def test_create_record_selector(test_name, record_selector, expected_runtime_sel
(
"test_option_in_selector",
"""
extractor:
type: DpathExtractor
field_pointer: ["{{ options['name'] }}"]
selector:
class_name: airbyte_cdk.sources.declarative.extractors.record_selector.RecordSelector
$options:
name: "selector"
extractor: "*ref(extractor)"
""",
extractor:
type: DpathExtractor
field_pointer: ["{{ options['name'] }}"]
selector:
class_name: airbyte_cdk.sources.declarative.extractors.record_selector.RecordSelector
$options:
name: "selector"
extractor: "*ref(extractor)"
""",
"selector",
),
(
"test_option_in_extractor",
"""
extractor:
type: DpathExtractor
$options:
name: "extractor"
field_pointer: ["{{ options['name'] }}"]
selector:
class_name: airbyte_cdk.sources.declarative.extractors.record_selector.RecordSelector
$options:
name: "selector"
extractor: "*ref(extractor)"
""",
extractor:
type: DpathExtractor
$options:
name: "extractor"
field_pointer: ["{{ options['name'] }}"]
selector:
class_name: airbyte_cdk.sources.declarative.extractors.record_selector.RecordSelector
$options:
name: "selector"
extractor: "*ref(extractor)"
""",
"extractor",
),
],
Expand Down Expand Up @@ -682,6 +682,37 @@ def test_add_fields(self):
]
assert expected == component.transformations

def test_add_fields_path_in_options(self):
content = f"""
the_stream:
class_name: airbyte_cdk.sources.declarative.declarative_stream.DeclarativeStream
$options:
{self.base_options}
path: "/wrong_path"
transformations:
- type: AddFields
fields:
- path: ["field1"]
value: "static_value"
"""
config = parser.parse(content)

factory.create_component(config["the_stream"], input_config, False)

component = factory.create_component(config["the_stream"], input_config)()
assert isinstance(component, DeclarativeStream)
expected = [
AddFields(
fields=[
AddedFieldDefinition(
path=["field1"], value=InterpolatedString(string="static_value", default="static_value", options={}), options={}
)
],
options={},
)
]
assert expected == component.transformations


def test_validation_wrong_input_type():
content = """
Expand Down

0 comments on commit 0ffd503

Please sign in to comment.