Skip to content

Commit c6e08ec

Browse files
committed
Merge main to branch
2 parents 706ba21 + 650165f commit c6e08ec

File tree

4 files changed

+82
-80
lines changed

4 files changed

+82
-80
lines changed

airbyte_cdk/sources/declarative/checks/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
from pydantic.v1 import BaseModel
88

99
from airbyte_cdk.sources.declarative.checks.check_dynamic_stream import CheckDynamicStream
10-
from airbyte_cdk.sources.declarative.checks.check_stream import CheckStream, DynamicStreamCheckConfig
10+
from airbyte_cdk.sources.declarative.checks.check_stream import (
11+
CheckStream,
12+
DynamicStreamCheckConfig,
13+
)
1114
from airbyte_cdk.sources.declarative.checks.connection_checker import ConnectionChecker
1215
from airbyte_cdk.sources.declarative.models import (
1316
CheckDynamicStream as CheckDynamicStreamModel,

airbyte_cdk/sources/declarative/models/declarative_component_schema.py

Lines changed: 27 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -611,9 +611,7 @@ class OAuthAuthenticator(BaseModel):
611611
scopes: Optional[List[str]] = Field(
612612
None,
613613
description="List of scopes that should be granted to the access token.",
614-
examples=[
615-
["crm.list.read", "crm.objects.contacts.read", "crm.schema.contacts.read"]
616-
],
614+
examples=[["crm.list.read", "crm.objects.contacts.read", "crm.schema.contacts.read"]],
617615
title="Scopes",
618616
)
619617
token_expiry_date: Optional[str] = Field(
@@ -1087,28 +1085,24 @@ class OAuthConfigSpecification(BaseModel):
10871085
class Config:
10881086
extra = Extra.allow
10891087

1090-
oauth_user_input_from_connector_config_specification: Optional[Dict[str, Any]] = (
1091-
Field(
1092-
None,
1093-
description="OAuth specific blob. This is a Json Schema used to validate Json configurations used as input to OAuth.\nMust be a valid non-nested JSON that refers to properties from ConnectorSpecification.connectionSpecification\nusing special annotation 'path_in_connector_config'.\nThese are input values the user is entering through the UI to authenticate to the connector, that might also shared\nas inputs for syncing data via the connector.\nExamples:\nif no connector values is shared during oauth flow, oauth_user_input_from_connector_config_specification=[]\nif connector values such as 'app_id' inside the top level are used to generate the API url for the oauth flow,\n oauth_user_input_from_connector_config_specification={\n app_id: {\n type: string\n path_in_connector_config: ['app_id']\n }\n }\nif connector values such as 'info.app_id' nested inside another object are used to generate the API url for the oauth flow,\n oauth_user_input_from_connector_config_specification={\n app_id: {\n type: string\n path_in_connector_config: ['info', 'app_id']\n }\n }",
1094-
examples=[
1095-
{"app_id": {"type": "string", "path_in_connector_config": ["app_id"]}},
1096-
{
1097-
"app_id": {
1098-
"type": "string",
1099-
"path_in_connector_config": ["info", "app_id"],
1100-
}
1101-
},
1102-
],
1103-
title="OAuth user input",
1104-
)
1088+
oauth_user_input_from_connector_config_specification: Optional[Dict[str, Any]] = Field(
1089+
None,
1090+
description="OAuth specific blob. This is a Json Schema used to validate Json configurations used as input to OAuth.\nMust be a valid non-nested JSON that refers to properties from ConnectorSpecification.connectionSpecification\nusing special annotation 'path_in_connector_config'.\nThese are input values the user is entering through the UI to authenticate to the connector, that might also shared\nas inputs for syncing data via the connector.\nExamples:\nif no connector values is shared during oauth flow, oauth_user_input_from_connector_config_specification=[]\nif connector values such as 'app_id' inside the top level are used to generate the API url for the oauth flow,\n oauth_user_input_from_connector_config_specification={\n app_id: {\n type: string\n path_in_connector_config: ['app_id']\n }\n }\nif connector values such as 'info.app_id' nested inside another object are used to generate the API url for the oauth flow,\n oauth_user_input_from_connector_config_specification={\n app_id: {\n type: string\n path_in_connector_config: ['info', 'app_id']\n }\n }",
1091+
examples=[
1092+
{"app_id": {"type": "string", "path_in_connector_config": ["app_id"]}},
1093+
{
1094+
"app_id": {
1095+
"type": "string",
1096+
"path_in_connector_config": ["info", "app_id"],
1097+
}
1098+
},
1099+
],
1100+
title="OAuth user input",
11051101
)
1106-
oauth_connector_input_specification: Optional[OauthConnectorInputSpecification] = (
1107-
Field(
1108-
None,
1109-
description='The DeclarativeOAuth specific blob.\nPertains to the fields defined by the connector relating to the OAuth flow.\n\nInterpolation capabilities:\n- The variables placeholders are declared as `{{my_var}}`.\n- The nested resolution variables like `{{ {{my_nested_var}} }}` is allowed as well.\n\n- The allowed interpolation context is:\n + base64Encoder - encode to `base64`, {{ {{my_var_a}}:{{my_var_b}} | base64Encoder }}\n + base64Decorer - decode from `base64` encoded string, {{ {{my_string_variable_or_string_value}} | base64Decoder }}\n + urlEncoder - encode the input string to URL-like format, {{ https://test.host.com/endpoint | urlEncoder}}\n + urlDecorer - decode the input url-encoded string into text format, {{ urlDecoder:https%3A%2F%2Fairbyte.io | urlDecoder}}\n + codeChallengeS256 - get the `codeChallenge` encoded value to provide additional data-provider specific authorisation values, {{ {{state_value}} | codeChallengeS256 }}\n\nExamples:\n - The TikTok Marketing DeclarativeOAuth spec:\n {\n "oauth_connector_input_specification": {\n "type": "object",\n "additionalProperties": false,\n "properties": {\n "consent_url": "https://ads.tiktok.com/marketing_api/auth?{{client_id_key}}={{client_id_value}}&{{redirect_uri_key}}={{ {{redirect_uri_value}} | urlEncoder}}&{{state_key}}={{state_value}}",\n "access_token_url": "https://business-api.tiktok.com/open_api/v1.3/oauth2/access_token/",\n "access_token_params": {\n "{{ auth_code_key }}": "{{ auth_code_value }}",\n "{{ client_id_key }}": "{{ client_id_value }}",\n "{{ client_secret_key }}": "{{ client_secret_value }}"\n },\n "access_token_headers": {\n "Content-Type": "application/json",\n "Accept": "application/json"\n },\n "extract_output": ["data.access_token"],\n "client_id_key": "app_id",\n "client_secret_key": "secret",\n "auth_code_key": "auth_code"\n }\n }\n }',
1110-
title="DeclarativeOAuth Connector Specification",
1111-
)
1102+
oauth_connector_input_specification: Optional[OauthConnectorInputSpecification] = Field(
1103+
None,
1104+
description='The DeclarativeOAuth specific blob.\nPertains to the fields defined by the connector relating to the OAuth flow.\n\nInterpolation capabilities:\n- The variables placeholders are declared as `{{my_var}}`.\n- The nested resolution variables like `{{ {{my_nested_var}} }}` is allowed as well.\n\n- The allowed interpolation context is:\n + base64Encoder - encode to `base64`, {{ {{my_var_a}}:{{my_var_b}} | base64Encoder }}\n + base64Decorer - decode from `base64` encoded string, {{ {{my_string_variable_or_string_value}} | base64Decoder }}\n + urlEncoder - encode the input string to URL-like format, {{ https://test.host.com/endpoint | urlEncoder}}\n + urlDecorer - decode the input url-encoded string into text format, {{ urlDecoder:https%3A%2F%2Fairbyte.io | urlDecoder}}\n + codeChallengeS256 - get the `codeChallenge` encoded value to provide additional data-provider specific authorisation values, {{ {{state_value}} | codeChallengeS256 }}\n\nExamples:\n - The TikTok Marketing DeclarativeOAuth spec:\n {\n "oauth_connector_input_specification": {\n "type": "object",\n "additionalProperties": false,\n "properties": {\n "consent_url": "https://ads.tiktok.com/marketing_api/auth?{{client_id_key}}={{client_id_value}}&{{redirect_uri_key}}={{ {{redirect_uri_value}} | urlEncoder}}&{{state_key}}={{state_value}}",\n "access_token_url": "https://business-api.tiktok.com/open_api/v1.3/oauth2/access_token/",\n "access_token_params": {\n "{{ auth_code_key }}": "{{ auth_code_value }}",\n "{{ client_id_key }}": "{{ client_id_value }}",\n "{{ client_secret_key }}": "{{ client_secret_value }}"\n },\n "access_token_headers": {\n "Content-Type": "application/json",\n "Accept": "application/json"\n },\n "extract_output": ["data.access_token"],\n "client_id_key": "app_id",\n "client_secret_key": "secret",\n "auth_code_key": "auth_code"\n }\n }\n }',
1105+
title="DeclarativeOAuth Connector Specification",
11121106
)
11131107
complete_oauth_output_specification: Optional[Dict[str, Any]] = Field(
11141108
None,
@@ -1126,9 +1120,7 @@ class Config:
11261120
complete_oauth_server_input_specification: Optional[Dict[str, Any]] = Field(
11271121
None,
11281122
description="OAuth specific blob. This is a Json Schema used to validate Json configurations persisted as Airbyte Server configurations.\nMust be a valid non-nested JSON describing additional fields configured by the Airbyte Instance or Workspace Admins to be used by the\nserver when completing an OAuth flow (typically exchanging an auth code for refresh token).\nExamples:\n complete_oauth_server_input_specification={\n client_id: {\n type: string\n },\n client_secret: {\n type: string\n }\n }",
1129-
examples=[
1130-
{"client_id": {"type": "string"}, "client_secret": {"type": "string"}}
1131-
],
1123+
examples=[{"client_id": {"type": "string"}, "client_secret": {"type": "string"}}],
11321124
title="OAuth input specification",
11331125
)
11341126
complete_oauth_server_output_specification: Optional[Dict[str, Any]] = Field(
@@ -1802,9 +1794,7 @@ class RecordSelector(BaseModel):
18021794
description="Responsible for filtering records to be emitted by the Source.",
18031795
title="Record Filter",
18041796
)
1805-
schema_normalization: Optional[
1806-
Union[SchemaNormalization, CustomSchemaNormalization]
1807-
] = Field(
1797+
schema_normalization: Optional[Union[SchemaNormalization, CustomSchemaNormalization]] = Field(
18081798
SchemaNormalization.None_,
18091799
description="Responsible for normalization according to the schema.",
18101800
title="Schema Normalization",
@@ -2031,9 +2021,7 @@ class Config:
20312021
description="Component used to fetch data incrementally based on a time field in the data.",
20322022
title="Incremental Sync",
20332023
)
2034-
name: Optional[str] = Field(
2035-
"", description="The stream name.", example=["Users"], title="Name"
2036-
)
2024+
name: Optional[str] = Field("", description="The stream name.", example=["Users"], title="Name")
20372025
primary_key: Optional[PrimaryKey] = Field(
20382026
"", description="The primary key of the stream.", title="Primary Key"
20392027
)
@@ -2291,9 +2279,7 @@ class ParentStreamConfig(BaseModel):
22912279

22922280
class StateDelegatingStream(BaseModel):
22932281
type: Literal["StateDelegatingStream"]
2294-
name: str = Field(
2295-
..., description="The stream name.", example=["Users"], title="Name"
2296-
)
2282+
name: str = Field(..., description="The stream name.", example=["Users"], title="Name")
22972283
full_refresh_stream: DeclarativeStream = Field(
22982284
...,
22992285
description="Component used to coordinate how records are extracted across stream slices and request pages when the state is empty or not provided.",
@@ -2382,9 +2368,7 @@ class AsyncRetriever(BaseModel):
23822368
)
23832369
download_extractor: Optional[
23842370
Union[CustomRecordExtractor, DpathExtractor, ResponseToFileExtractor]
2385-
] = Field(
2386-
None, description="Responsible for fetching the records from provided urls."
2387-
)
2371+
] = Field(None, description="Responsible for fetching the records from provided urls.")
23882372
creation_requester: Union[CustomRequester, HttpRequester] = Field(
23892373
...,
23902374
description="Requester component that describes how to prepare HTTP requests to send to the source API to create the async server-side job.",
@@ -2524,12 +2508,10 @@ class DynamicDeclarativeStream(BaseModel):
25242508
stream_template: DeclarativeStream = Field(
25252509
..., description="Reference to the stream template.", title="Stream Template"
25262510
)
2527-
components_resolver: Union[HttpComponentsResolver, ConfigComponentsResolver] = (
2528-
Field(
2529-
...,
2530-
description="Component resolve and populates stream templates with components values.",
2531-
title="Components Resolver",
2532-
)
2511+
components_resolver: Union[HttpComponentsResolver, ConfigComponentsResolver] = Field(
2512+
...,
2513+
description="Component resolve and populates stream templates with components values.",
2514+
title="Components Resolver",
25332515
)
25342516

25352517

airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,11 @@
5454
SessionTokenProvider,
5555
TokenProvider,
5656
)
57-
from airbyte_cdk.sources.declarative.checks import CheckDynamicStream, CheckStream, DynamicStreamCheckConfig
57+
from airbyte_cdk.sources.declarative.checks import (
58+
CheckDynamicStream,
59+
CheckStream,
60+
DynamicStreamCheckConfig,
61+
)
5862
from airbyte_cdk.sources.declarative.concurrency_level import ConcurrencyLevel
5963
from airbyte_cdk.sources.declarative.datetime.min_max_datetime import MinMaxDatetime
6064
from airbyte_cdk.sources.declarative.declarative_stream import DeclarativeStream
@@ -131,9 +135,6 @@
131135
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
132136
CheckStream as CheckStreamModel,
133137
)
134-
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
135-
DynamicStreamCheckConfig as DynamicStreamCheckConfigModel,
136-
)
137138
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
138139
ComplexFieldType as ComplexFieldTypeModel,
139140
)
@@ -221,6 +222,9 @@
221222
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
222223
DynamicSchemaLoader as DynamicSchemaLoaderModel,
223224
)
225+
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
226+
DynamicStreamCheckConfig as DynamicStreamCheckConfigModel,
227+
)
224228
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
225229
ExponentialBackoffStrategy as ExponentialBackoffStrategyModel,
226230
)
@@ -941,25 +945,35 @@ def create_bearer_authenticator(
941945

942946
@staticmethod
943947
def create_dynamic_stream_check_config(
944-
model: DynamicStreamCheckConfigModel, config: Config, **kwargs: Any
948+
model: DynamicStreamCheckConfigModel, config: Config, **kwargs: Any
945949
) -> DynamicStreamCheckConfig:
946950
return DynamicStreamCheckConfig(
947951
dynamic_stream_name=model.dynamic_stream_name,
948952
stream_count=model.stream_count or 0,
949953
)
950954

951-
def create_check_stream(self, model: CheckStreamModel, config: Config, **kwargs: Any) -> CheckStream:
955+
def create_check_stream(
956+
self, model: CheckStreamModel, config: Config, **kwargs: Any
957+
) -> CheckStream:
952958
if model.dynamic_streams_check_configs is None and model.stream_names is None:
953959
raise ValueError(
954960
"Expected either stream_names or dynamic_streams_check_configs to be set for CheckStream"
955961
)
956962

957-
dynamic_streams_check_configs = [
958-
self._create_component_from_model(model=dynamic_stream_check_config, config=config)
959-
for dynamic_stream_check_config in model.dynamic_streams_check_configs
960-
] if model.dynamic_streams_check_configs else []
963+
dynamic_streams_check_configs = (
964+
[
965+
self._create_component_from_model(model=dynamic_stream_check_config, config=config)
966+
for dynamic_stream_check_config in model.dynamic_streams_check_configs
967+
]
968+
if model.dynamic_streams_check_configs
969+
else []
970+
)
961971

962-
return CheckStream(stream_names=model.stream_names or [], dynamic_streams_check_configs=dynamic_streams_check_configs, parameters={})
972+
return CheckStream(
973+
stream_names=model.stream_names or [],
974+
dynamic_streams_check_configs=dynamic_streams_check_configs,
975+
parameters={},
976+
)
963977

964978
@staticmethod
965979
def create_check_dynamic_stream(

unit_tests/sources/declarative/checks/test_check_stream.py

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,19 @@
44

55
import json
66
import logging
7+
from copy import deepcopy
78
from typing import Any, Iterable, Mapping, Optional
89
from unittest.mock import MagicMock
9-
from copy import deepcopy
1010

1111
import pytest
1212
import requests
1313

1414
from jsonschema.exceptions import ValidationError
1515
from airbyte_cdk.sources.declarative.checks.check_stream import CheckStream
16-
from airbyte_cdk.sources.streams.http import HttpStream
1716
from airbyte_cdk.sources.declarative.concurrent_declarative_source import (
1817
ConcurrentDeclarativeSource,
1918
)
19+
from airbyte_cdk.sources.streams.http import HttpStream
2020
from airbyte_cdk.test.mock_http import HttpMocker, HttpRequest, HttpResponse
2121

2222
logger = logging.getLogger("test")
@@ -31,19 +31,19 @@
3131
[
3232
("test_success_check", record, stream_names, {}, (True, None)),
3333
(
34-
"test_success_check_stream_slice",
35-
record,
36-
stream_names,
37-
{"slice": "slice_value"},
38-
(True, None),
34+
"test_success_check_stream_slice",
35+
record,
36+
stream_names,
37+
{"slice": "slice_value"},
38+
(True, None),
3939
),
4040
("test_fail_check", None, stream_names, {}, (True, None)),
4141
("test_try_to_check_invalid stream", record, ["invalid_stream_name"], {}, None),
4242
],
4343
)
4444
@pytest.mark.parametrize("slices_as_list", [True, False])
4545
def test_check_stream_with_slices_as_list(
46-
test_name, record, streams_to_check, stream_slice, expectation, slices_as_list
46+
test_name, record, streams_to_check, stream_slice, expectation, slices_as_list
4747
):
4848
stream = MagicMock()
4949
stream.name = "s1"
@@ -108,22 +108,22 @@ def test_check_stream_with_no_stream_slices_aborts():
108108
"test_name, response_code, available_expectation, expected_messages",
109109
[
110110
(
111-
"test_stream_unavailable_unhandled_error",
112-
404,
113-
False,
114-
["Not found. The requested resource was not found on the server."],
111+
"test_stream_unavailable_unhandled_error",
112+
404,
113+
False,
114+
["Not found. The requested resource was not found on the server."],
115115
),
116116
(
117-
"test_stream_unavailable_handled_error",
118-
403,
119-
False,
120-
["Forbidden. You don't have permission to access this resource."],
117+
"test_stream_unavailable_handled_error",
118+
403,
119+
False,
120+
["Forbidden. You don't have permission to access this resource."],
121121
),
122122
("test_stream_available", 200, True, []),
123123
],
124124
)
125125
def test_check_http_stream_via_availability_strategy(
126-
mocker, test_name, response_code, available_expectation, expected_messages
126+
mocker, test_name, response_code, available_expectation, expected_messages
127127
):
128128
class MockHttpStream(HttpStream):
129129
url_base = "https://test_base_url.com"
@@ -166,10 +166,13 @@ def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapp
166166
assert message in reason
167167

168168

169-
_CONFIG = {"start_date": "2024-07-01T00:00:00.000Z", "custom_streams": [
170-
{"id": 3, "name": "item_3"},
171-
{"id": 4, "name": "item_4"},
172-
]}
169+
_CONFIG = {
170+
"start_date": "2024-07-01T00:00:00.000Z",
171+
"custom_streams": [
172+
{"id": 3, "name": "item_3"},
173+
{"id": 4, "name": "item_4"},
174+
],
175+
}
173176

174177
_MANIFEST_WITHOUT_CHECK_COMPONENT = {
175178
"version": "6.7.0",
@@ -316,7 +319,7 @@ def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapp
316319
},
317320
],
318321
},
319-
}
322+
},
320323
],
321324
"streams": [
322325
{
@@ -355,7 +358,7 @@ def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapp
355358
},
356359
}
357360
}
358-
]
361+
],
359362
}
360363

361364

0 commit comments

Comments
 (0)