Skip to content

Commit a3ff80c

Browse files
authored
[low-code-connectors] Disable parse-time interpolation in favor of runtime-only (#14923)
* abstract auth token * basichttp * remove prints * docstrings * get rid of parse-time interpolation * always pass options through * delete print * delete misleading comment * delete note * reset * pass down options * delete duplicate file * missing test * refactor test * rename to '$options' * rename to '' * interpolatedauth * fix tests * fix * docstrings * update docstring * docstring * update docstring * remove extra field * undo * rename to runtime_parameters * docstring * update * / -> * * update template * rename to options * Add examples * update docstring * Update test * newlines * rename kwargs to options * options init param * delete duplicate line * type hints * update docstring * Revert "delete duplicate line" This reverts commit 4255d5b. * delete duplicate code from bad merge * rename file * bump cdk version
1 parent 6b35b23 commit a3ff80c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+259
-284
lines changed

airbyte-cdk/python/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Changelog
22

3+
## 0.1.68
4+
- Replace parse-time string interpolation with run-time interpolation in YAML-based sources
5+
36
## 0.1.67
47
- Add support declarative token authenticator.
58

airbyte-cdk/python/airbyte_cdk/sources/declarative/auth/oauth.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@ def __init__(
2626
config: Mapping[str, Any],
2727
scopes: Optional[List[str]] = None,
2828
token_expiry_date: Optional[Union[InterpolatedString, str]] = None,
29-
access_token_name: Union[InterpolatedString, str] = InterpolatedString("access_token"),
30-
expires_in_name: Union[InterpolatedString, str] = InterpolatedString("expires_in"),
29+
access_token_name: Union[InterpolatedString, str] = "access_token",
30+
expires_in_name: Union[InterpolatedString, str] = "expires_in",
3131
refresh_request_body: Optional[Mapping[str, Any]] = None,
32+
**options: Optional[Mapping[str, Any]],
3233
):
3334
"""
3435
:param token_refresh_endpoint: The endpoint to refresh the access token
@@ -41,19 +42,20 @@ def __init__(
4142
:param access_token_name: THe field to extract access token from in the response
4243
:param expires_in_name:The field to extract expires_in from in the response
4344
:param refresh_request_body: The request body to send in the refresh request
45+
:param options: Additional runtime parameters to be used for string interpolation
4446
"""
4547
self.config = config
46-
self.token_refresh_endpoint = InterpolatedString.create(token_refresh_endpoint)
47-
self.client_secret = InterpolatedString.create(client_secret)
48-
self.client_id = InterpolatedString.create(client_id)
49-
self.refresh_token = InterpolatedString.create(refresh_token)
48+
self.token_refresh_endpoint = InterpolatedString.create(token_refresh_endpoint, options=options)
49+
self.client_secret = InterpolatedString.create(client_secret, options=options)
50+
self.client_id = InterpolatedString.create(client_id, options=options)
51+
self.refresh_token = InterpolatedString.create(refresh_token, options=options)
5052
self.scopes = scopes
51-
self.access_token_name = InterpolatedString.create(access_token_name)
52-
self.expires_in_name = InterpolatedString.create(expires_in_name)
53-
self.refresh_request_body = InterpolatedMapping(refresh_request_body or {})
53+
self.access_token_name = InterpolatedString.create(access_token_name, options=options)
54+
self.expires_in_name = InterpolatedString.create(expires_in_name, options=options)
55+
self.refresh_request_body = InterpolatedMapping(refresh_request_body or {}, options=options)
5456

5557
self.token_expiry_date = (
56-
pendulum.parse(InterpolatedString.create(token_expiry_date).eval(self.config))
58+
pendulum.parse(InterpolatedString.create(token_expiry_date, options=options).eval(self.config))
5759
if token_expiry_date
5860
else pendulum.now().subtract(days=1)
5961
)

airbyte-cdk/python/airbyte_cdk/sources/declarative/auth/token.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
#
44

55
import base64
6-
from typing import Union
6+
from typing import Any, Mapping, Optional, Union
77

88
from airbyte_cdk.sources.declarative.interpolation.interpolated_string import InterpolatedString
99
from airbyte_cdk.sources.declarative.types import Config
10-
from airbyte_cdk.sources.streams.http.requests_native_auth.abtract_token import AbstractHeaderAuthenticator
10+
from airbyte_cdk.sources.streams.http.requests_native_auth.abstract_token import AbstractHeaderAuthenticator
1111

1212

1313
class ApiKeyAuthenticator(AbstractHeaderAuthenticator):
@@ -24,14 +24,21 @@ class ApiKeyAuthenticator(AbstractHeaderAuthenticator):
2424
2525
"""
2626

27-
def __init__(self, header: Union[InterpolatedString, str], token: Union[InterpolatedString, str], config: Config):
27+
def __init__(
28+
self,
29+
header: Union[InterpolatedString, str],
30+
token: Union[InterpolatedString, str],
31+
config: Config,
32+
**options: Optional[Mapping[str, Any]],
33+
):
2834
"""
2935
:param header: Header key to set on the HTTP requests
3036
:param token: Header value to set on the HTTP requests
3137
:param config: The user-provided configuration as specified by the source's spec
38+
:param options: Additional runtime parameters to be used for string interpolation
3239
"""
33-
self._header = InterpolatedString.create(header)
34-
self._token = InterpolatedString.create(token)
40+
self._header = InterpolatedString.create(header, options=options)
41+
self._token = InterpolatedString.create(token, options=options)
3542
self._config = config
3643

3744
@property
@@ -51,12 +58,13 @@ class BearerAuthenticator(AbstractHeaderAuthenticator):
5158
`"Authorization": "Bearer <token>"`
5259
"""
5360

54-
def __init__(self, token: Union[InterpolatedString, str], config: Config):
61+
def __init__(self, token: Union[InterpolatedString, str], config: Config, **options: Optional[Mapping[str, Any]]):
5562
"""
5663
:param token: The bearer token
5764
:param config: The user-provided configuration as specified by the source's spec
65+
:param options: Additional runtime parameters to be used for string interpolation
5866
"""
59-
self._token = InterpolatedString.create(token)
67+
self._token = InterpolatedString.create(token, options=options)
6068
self._config = config
6169

6270
@property
@@ -77,14 +85,21 @@ class BasicHttpAuthenticator(AbstractHeaderAuthenticator):
7785
`"Authorization": "Basic <encoded_credentials>"`
7886
"""
7987

80-
def __init__(self, username: Union[InterpolatedString, str], config: Config, password: Union[InterpolatedString, str] = ""):
88+
def __init__(
89+
self,
90+
username: Union[InterpolatedString, str],
91+
config: Config,
92+
password: Union[InterpolatedString, str] = "",
93+
**options: Optional[Mapping[str, Any]],
94+
):
8195
"""
8296
:param username: The username
8397
:param config: The user-provided configuration as specified by the source's spec
8498
:param password: The password
99+
:param options: Additional runtime parameters to be used for string interpolation
85100
"""
86-
self._username = InterpolatedString.create(username)
87-
self._password = InterpolatedString.create(password)
101+
self._username = InterpolatedString.create(username, options=options)
102+
self._password = InterpolatedString.create(password, options=options)
88103
self._config = config
89104

90105
@property

airbyte-cdk/python/airbyte_cdk/sources/declarative/checks/check_stream.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#
44

55
import logging
6-
from typing import Any, List, Mapping, Tuple
6+
from typing import Any, List, Mapping, Optional, Tuple
77

88
from airbyte_cdk.models.airbyte_protocol import SyncMode
99
from airbyte_cdk.sources.declarative.checks.connection_checker import ConnectionChecker
@@ -15,11 +15,13 @@ class CheckStream(ConnectionChecker):
1515
Checks the connections by trying to read records from one or many of the streams selected by the developer
1616
"""
1717

18-
def __init__(self, stream_names: List[str]):
18+
def __init__(self, stream_names: List[str], **options: Optional[Mapping[str, Any]]):
1919
"""
2020
:param stream_names: name of streams to read records from
21+
:param options: Additional runtime parameters to be used for string interpolation
2122
"""
2223
self._stream_names = set(stream_names)
24+
self._options = options
2325

2426
def check_connection(self, source: Source, logger: logging.Logger, config: Mapping[str, Any]) -> Tuple[bool, any]:
2527
streams = source.streams(config)

airbyte-cdk/python/airbyte_cdk/sources/declarative/create_partial.py

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import inspect
66

7-
from airbyte_cdk.sources.declarative.interpolation.interpolated_mapping import InterpolatedMapping
7+
OPTIONS_STR = "$options"
88

99

1010
def create(func, /, *args, **keywords):
@@ -15,6 +15,7 @@ def create(func, /, *args, **keywords):
1515
The interpolation will take in kwargs, and config as parameters that can be accessed through interpolating.
1616
If any of the parameters are also create functions, they will also be created.
1717
kwargs are propagated to the recursive method calls
18+
1819
:param func: Function
1920
:param args:
2021
:param keywords:
@@ -28,21 +29,12 @@ def newfunc(*fargs, **fkeywords):
2829
# config is a special keyword used for interpolation
2930
config = all_keywords.pop("config", None)
3031

31-
# options is a special keyword used for interpolation and propagation
32-
if "options" in all_keywords:
33-
options = all_keywords.pop("options")
32+
# $options is a special keyword used for interpolation and propagation
33+
if OPTIONS_STR in all_keywords:
34+
options = all_keywords.get(OPTIONS_STR)
3435
else:
3536
options = dict()
3637

37-
# create object's partial parameters
38-
fully_created = _create_inner_objects(all_keywords, options)
39-
40-
# interpolate the parameters
41-
interpolated_keywords = InterpolatedMapping(fully_created).eval(config, **{"options": options})
42-
interpolated_keywords = {k: v for k, v in interpolated_keywords.items() if v}
43-
44-
all_keywords.update(interpolated_keywords)
45-
4638
# if config is not none, add it back to the keywords mapping
4739
if config is not None:
4840
all_keywords["config"] = config

airbyte-cdk/python/airbyte_cdk/sources/declarative/datetime/min_max_datetime.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#
44

55
import datetime as dt
6-
from typing import Union
6+
from typing import Any, Mapping, Optional, Union
77

88
from airbyte_cdk.sources.declarative.interpolation.interpolated_string import InterpolatedString
99

@@ -21,40 +21,44 @@ def __init__(
2121
datetime_format: str = "",
2222
min_datetime: Union[InterpolatedString, str] = "",
2323
max_datetime: Union[InterpolatedString, str] = "",
24+
**options: Optional[Mapping[str, Any]],
2425
):
2526
"""
2627
:param datetime: InterpolatedString or string representing the datetime in the format specified by `datetime_format`
2728
:param datetime_format: Format of the datetime passed as argument
2829
:param min_datetime: InterpolatedString or string representing the min datetime
2930
:param max_datetime: InterpolatedString or string representing the max datetime
31+
:param options: Additional runtime parameters to be used for string interpolation
3032
"""
31-
self._datetime_interpolator = InterpolatedString.create(datetime)
33+
self._datetime_interpolator = InterpolatedString.create(datetime, options=options)
3234
self._datetime_format = datetime_format
3335
self._timezone = dt.timezone.utc
34-
self._min_datetime_interpolator = InterpolatedString.create(min_datetime) if min_datetime else None
35-
self._max_datetime_interpolator = InterpolatedString.create(max_datetime) if max_datetime else None
36+
self._min_datetime_interpolator = InterpolatedString.create(min_datetime, options=options) if min_datetime else None
37+
self._max_datetime_interpolator = InterpolatedString.create(max_datetime, options=options) if max_datetime else None
3638

37-
def get_datetime(self, config, **kwargs) -> dt.datetime:
39+
def get_datetime(self, config, **additional_options) -> dt.datetime:
3840
"""
3941
Evaluates and returns the datetime
4042
:param config: The user-provided configuration as specified by the source's spec
41-
:param kwargs: Additional arguments to be passed to the strings for interpolation
43+
:param additional_options: Additional arguments to be passed to the strings for interpolation
4244
:return: The evaluated datetime
4345
"""
4446
# We apply a default datetime format here instead of at instantiation, so it can be set by the parent first
4547
datetime_format = self._datetime_format
4648
if not datetime_format:
4749
datetime_format = "%Y-%m-%dT%H:%M:%S.%f%z"
4850

49-
time = dt.datetime.strptime(self._datetime_interpolator.eval(config, **kwargs), datetime_format).replace(tzinfo=self._timezone)
51+
time = dt.datetime.strptime(self._datetime_interpolator.eval(config, **additional_options), datetime_format).replace(
52+
tzinfo=self._timezone
53+
)
5054

5155
if self._min_datetime_interpolator:
52-
min_time = dt.datetime.strptime(self._min_datetime_interpolator.eval(config, **kwargs), datetime_format).replace(
56+
min_time = dt.datetime.strptime(self._min_datetime_interpolator.eval(config, **additional_options), datetime_format).replace(
5357
tzinfo=self._timezone
5458
)
5559
time = max(time, min_time)
5660
if self._max_datetime_interpolator:
57-
max_time = dt.datetime.strptime(self._max_datetime_interpolator.eval(config, **kwargs), datetime_format).replace(
61+
max_time = dt.datetime.strptime(self._max_datetime_interpolator.eval(config, **additional_options), datetime_format).replace(
5862
tzinfo=self._timezone
5963
)
6064
time = min(time, max_time)

airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/jello.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,11 @@ class JelloExtractor:
2121

2222
default_transform = "_"
2323

24-
def __init__(self, transform: Union[InterpolatedString, str], config: Config, decoder: Decoder = JsonDecoder(), kwargs=None):
24+
def __init__(self, transform: Union[InterpolatedString, str], config: Config, decoder: Decoder = JsonDecoder()):
2525
"""
2626
:param transform: The Jello query to evaluate on the decoded response
2727
:param config: The user-provided configuration as specified by the source's spec
2828
:param decoder: The decoder responsible to transfom the response in a Mapping
29-
:param kwargs: Additional arguments to be passed to the strings for interpolation
3029
"""
3130

3231
if isinstance(transform, str):
@@ -35,9 +34,8 @@ def __init__(self, transform: Union[InterpolatedString, str], config: Config, de
3534
self._transform = transform
3635
self._decoder = decoder
3736
self._config = config
38-
self._kwargs = kwargs or dict()
3937

4038
def extract_records(self, response: requests.Response) -> List[Record]:
4139
response_body = self._decoder.decode(response)
42-
script = self._transform.eval(self._config, **{"kwargs": self._kwargs})
40+
script = self._transform.eval(self._config)
4341
return jello_lib.pyquery(response_body, script)

airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_filter.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ class RecordFilter:
1313
Filter applied on a list of Records
1414
"""
1515

16-
def __init__(self, config: Config, condition: str = ""):
16+
def __init__(self, config: Config, condition: str = "", **options: Optional[Mapping[str, Any]]):
1717
"""
1818
:param config: The user-provided configuration as specified by the source's spec
1919
:param condition: The string representing the predicate to filter a record. Records will be removed if evaluated to False
20+
:param options: Additional runtime parameters to be used for string interpolation
2021
"""
2122
self._config = config
2223
self._filter_interpolator = InterpolatedBoolean(condition)
24+
self._options = options
2325

2426
def filter_records(
2527
self,

airbyte-cdk/python/airbyte_cdk/sources/declarative/extractors/record_selector.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@ class RecordSelector(HttpSelector):
1717
records based on a heuristic.
1818
"""
1919

20-
def __init__(self, extractor: JelloExtractor, record_filter: RecordFilter = None):
20+
def __init__(self, extractor: JelloExtractor, record_filter: RecordFilter = None, **options: Optional[Mapping[str, Any]]):
2121
"""
2222
:param extractor: The record extractor responsible for extracting records from a response
2323
:param record_filter: The record filter responsible for filtering extracted records
24+
:param options: Additional runtime parameters to be used for string interpolation
2425
"""
2526
self._extractor = extractor
2627
self._record_filter = record_filter
28+
self._options = options
2729

2830
def select_records(
2931
self,

airbyte-cdk/python/airbyte_cdk/sources/declarative/interpolation/interpolated_boolean.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Copyright (c) 2022 Airbyte, Inc., all rights reserved.
33
#
44

5-
from typing import Any, Final, List
5+
from typing import Any, Final, List, Mapping, Optional
66

77
from airbyte_cdk.sources.declarative.interpolation.jinja import JinjaInterpolation
88
from airbyte_cdk.sources.declarative.types import Config
@@ -16,26 +16,28 @@ class InterpolatedBoolean:
1616
The string will be evaluated as False if it interpolates to a value in {FALSE_VALUES}
1717
"""
1818

19-
def __init__(self, condition: str):
19+
def __init__(self, condition: str, **options: Optional[Mapping[str, Any]]):
2020
"""
2121
:param condition: The string representing the condition to evaluate to a boolean
22+
:param options: Additional runtime parameters to be used for string interpolation
2223
"""
2324
self._condition = condition
2425
self._default = "False"
2526
self._interpolation = JinjaInterpolation()
27+
self._options = options
2628

27-
def eval(self, config: Config, **kwargs):
29+
def eval(self, config: Config, **additional_options):
2830
"""
2931
Interpolates the predicate condition string using the config and other optional arguments passed as parameter.
3032
3133
:param config: The user-provided configuration as specified by the source's spec
32-
:param kwargs: Optional parameters used for interpolation
34+
:param additional_options: Optional parameters used for interpolation
3335
:return: The interpolated string
3436
"""
3537
if isinstance(self._condition, bool):
3638
return self._condition
3739
else:
38-
evaluated = self._interpolation.eval(self._condition, config, self._default, **kwargs)
40+
evaluated = self._interpolation.eval(self._condition, config, self._default, options=self._options, **additional_options)
3941
if evaluated in FALSE_VALUES:
4042
return False
4143
# The presence of a value is generally regarded as truthy, so we treat it as such

0 commit comments

Comments
 (0)