1
0
mirror of synced 2026-01-01 18:02:53 -05:00
Files
airbyte/airbyte-cdk/python/airbyte_cdk/sources/declarative/create_partial.py
Alexandre Girard 0ffd503b21 🐛 [low-code] $options shouldn't overwrite values that are already defined (#18060)
* fix

* Add missing test

* remove prints

* extract to method

* rename

* Add missing test

* rename

* bump
2022-10-17 11:07:04 -07:00

93 lines
3.2 KiB
Python

#
# Copyright (c) 2022 Airbyte, Inc., all rights reserved.
#
import inspect
from typing import Any, Mapping
OPTIONS_STR = "$options"
def create(func, /, *args, **keywords):
"""
Create a partial on steroids.
Returns a partial object which when called will behave like func called with the arguments supplied.
Parameters will be interpolated before the creation of the object
The interpolation will take in kwargs, and config as parameters that can be accessed through interpolating.
If any of the parameters are also create functions, they will also be created.
kwargs are propagated to the recursive method calls
:param func: Function
:param args:
:param keywords:
:return: partially created object
"""
def newfunc(*fargs, **fkeywords):
all_keywords = {**keywords}
all_keywords.update(fkeywords)
# config is a special keyword used for interpolation
config = all_keywords.pop("config", None)
# $options is a special keyword used for interpolation and propagation
if OPTIONS_STR in all_keywords:
options = all_keywords.get(OPTIONS_STR)
else:
options = dict()
# if config is not none, add it back to the keywords mapping
if config is not None:
all_keywords["config"] = config
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}
if "options" not in dynamic_args:
dynamic_args["options"] = {}
else:
# Handles the case where kwarg options and keyword $options both exist. We should merge both sets of options
# before creating the component
dynamic_args["options"] = {**all_keywords_to_pass_down["options"], **kwargs_to_pass_down["options"]}
try:
ret = func(*args, *fargs, **dynamic_args)
except TypeError as e:
raise Exception(f"failed to create object of type {func} because {e}")
return ret
newfunc.func = func
newfunc.args = args
newfunc.kwargs = keywords
return newfunc
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 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():
if type(v) == type(create):
fully_created[k] = v(kwargs=kwargs)
else:
fully_created[k] = v
return fully_created