Skip to content

Commit ceebfda

Browse files
authored
feat(low-code cdk): add config component resolver (#149)
1 parent 5801cd8 commit ceebfda

10 files changed

+579
-15
lines changed

airbyte_cdk/sources/declarative/auth/selective_authenticator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def __new__( # type: ignore[misc]
3030
try:
3131
selected_key = str(
3232
dpath.get(
33-
config, # type: ignore [arg-type] # Dpath wants mutable mapping but doesn't need it.
33+
config, # type: ignore[arg-type] # Dpath wants mutable mapping but doesn't need it.
3434
authenticator_selection_path,
3535
)
3636
)

airbyte_cdk/sources/declarative/declarative_component_schema.yaml

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3015,8 +3015,10 @@ definitions:
30153015
examples:
30163016
- ["data"]
30173017
- ["data", "records"]
3018-
- ["data", "{{ parameters.name }}"]
3018+
- ["data", 1, "name"]
3019+
- ["data", "{{ components_values.name }}"]
30193020
- ["data", "*", "record"]
3021+
- ["*", "**", "name"]
30203022
value:
30213023
title: Value
30223024
description: The dynamic or static value to assign to the key. Interpolated values can be used to dynamically determine the value during runtime.
@@ -3061,6 +3063,52 @@ definitions:
30613063
- type
30623064
- retriever
30633065
- components_mapping
3066+
StreamConfig:
3067+
title: Stream Config
3068+
description: (This component is experimental. Use at your own risk.) Describes how to get streams config from the source config.
3069+
type: object
3070+
required:
3071+
- type
3072+
- configs_pointer
3073+
properties:
3074+
type:
3075+
type: string
3076+
enum: [StreamConfig]
3077+
configs_pointer:
3078+
title: Configs Pointer
3079+
description: A list of potentially nested fields indicating the full path in source config file where streams configs located.
3080+
type: array
3081+
items:
3082+
- type: string
3083+
interpolation_context:
3084+
- parameters
3085+
examples:
3086+
- ["data"]
3087+
- ["data", "streams"]
3088+
- ["data", "{{ parameters.name }}"]
3089+
$parameters:
3090+
type: object
3091+
additionalProperties: true
3092+
ConfigComponentsResolver:
3093+
type: object
3094+
description: (This component is experimental. Use at your own risk.) Resolves and populates stream templates with components fetched from the source config.
3095+
properties:
3096+
type:
3097+
type: string
3098+
enum: [ConfigComponentsResolver]
3099+
stream_config:
3100+
"$ref": "#/definitions/StreamConfig"
3101+
components_mapping:
3102+
type: array
3103+
items:
3104+
"$ref": "#/definitions/ComponentMappingDefinition"
3105+
$parameters:
3106+
type: object
3107+
additionalProperties: true
3108+
required:
3109+
- type
3110+
- stream_config
3111+
- components_mapping
30643112
DynamicDeclarativeStream:
30653113
type: object
30663114
description: (This component is experimental. Use at your own risk.) A component that described how will be created declarative streams based on stream template.
@@ -3075,7 +3123,9 @@ definitions:
30753123
components_resolver:
30763124
title: Components Resolver
30773125
description: Component resolve and populates stream templates with components values.
3078-
"$ref": "#/definitions/HttpComponentsResolver"
3126+
anyOf:
3127+
- "$ref": "#/definitions/HttpComponentsResolver"
3128+
- "$ref": "#/definitions/ConfigComponentsResolver"
30793129
required:
30803130
- type
30813131
- stream_template

airbyte_cdk/sources/declarative/manifest_declarative_source.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import pkgutil
88
from copy import deepcopy
99
from importlib import metadata
10-
from typing import Any, Dict, Iterator, List, Mapping, Optional
10+
from typing import Any, Dict, Iterator, List, Mapping, Optional, Set
1111

1212
import yaml
1313
from jsonschema.exceptions import ValidationError
@@ -20,6 +20,7 @@
2020
AirbyteStateMessage,
2121
ConfiguredAirbyteCatalog,
2222
ConnectorSpecification,
23+
FailureType,
2324
)
2425
from airbyte_cdk.sources.declarative.checks.connection_checker import ConnectionChecker
2526
from airbyte_cdk.sources.declarative.declarative_source import DeclarativeSource
@@ -48,6 +49,7 @@
4849
DebugSliceLogger,
4950
SliceLogger,
5051
)
52+
from airbyte_cdk.utils.traced_exception import AirbyteTracedException
5153

5254

5355
class ManifestDeclarativeSource(DeclarativeSource):
@@ -313,6 +315,7 @@ def _dynamic_stream_configs(
313315
) -> List[Dict[str, Any]]:
314316
dynamic_stream_definitions: List[Dict[str, Any]] = manifest.get("dynamic_streams", [])
315317
dynamic_stream_configs: List[Dict[str, Any]] = []
318+
seen_dynamic_streams: Set[str] = set()
316319

317320
for dynamic_definition in dynamic_stream_definitions:
318321
components_resolver_config = dynamic_definition["components_resolver"]
@@ -350,6 +353,24 @@ def _dynamic_stream_configs(
350353
if "type" not in dynamic_stream:
351354
dynamic_stream["type"] = "DeclarativeStream"
352355

356+
# Ensure that each stream is created with a unique name
357+
name = dynamic_stream.get("name")
358+
359+
if name in seen_dynamic_streams:
360+
error_message = f"Dynamic streams list contains a duplicate name: {name}. Please contact Airbyte Support."
361+
failure_type = FailureType.system_error
362+
363+
if resolver_type == "ConfigComponentsResolver":
364+
error_message = f"Dynamic streams list contains a duplicate name: {name}. Please check your configuration."
365+
failure_type = FailureType.config_error
366+
367+
raise AirbyteTracedException(
368+
message=error_message,
369+
internal_message=error_message,
370+
failure_type=failure_type,
371+
)
372+
373+
seen_dynamic_streams.add(name)
353374
dynamic_stream_configs.append(dynamic_stream)
354375

355376
return dynamic_stream_configs

airbyte_cdk/sources/declarative/models/declarative_component_schema.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,8 +1192,10 @@ class ComponentMappingDefinition(BaseModel):
11921192
examples=[
11931193
["data"],
11941194
["data", "records"],
1195-
["data", "{{ parameters.name }}"],
1195+
["data", 1, "name"],
1196+
["data", "{{ components_values.name }}"],
11961197
["data", "*", "record"],
1198+
["*", "**", "name"],
11971199
],
11981200
title="Field Path",
11991201
)
@@ -1215,6 +1217,24 @@ class ComponentMappingDefinition(BaseModel):
12151217
parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters")
12161218

12171219

1220+
class StreamConfig(BaseModel):
1221+
type: Literal["StreamConfig"]
1222+
configs_pointer: List[str] = Field(
1223+
...,
1224+
description="A list of potentially nested fields indicating the full path in source config file where streams configs located.",
1225+
examples=[["data"], ["data", "streams"], ["data", "{{ parameters.name }}"]],
1226+
title="Configs Pointer",
1227+
)
1228+
parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters")
1229+
1230+
1231+
class ConfigComponentsResolver(BaseModel):
1232+
type: Literal["ConfigComponentsResolver"]
1233+
stream_config: StreamConfig
1234+
components_mapping: List[ComponentMappingDefinition]
1235+
parameters: Optional[Dict[str, Any]] = Field(None, alias="$parameters")
1236+
1237+
12181238
class AddedFieldDefinition(BaseModel):
12191239
type: Literal["AddedFieldDefinition"]
12201240
path: List[str] = Field(
@@ -2010,7 +2030,7 @@ class DynamicDeclarativeStream(BaseModel):
20102030
stream_template: DeclarativeStream = Field(
20112031
..., description="Reference to the stream template.", title="Stream Template"
20122032
)
2013-
components_resolver: HttpComponentsResolver = Field(
2033+
components_resolver: Union[HttpComponentsResolver, ConfigComponentsResolver] = Field(
20142034
...,
20152035
description="Component resolve and populates stream templates with components values.",
20162036
title="Components Resolver",

airbyte_cdk/sources/declarative/parsers/manifest_component_transformer.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,13 @@
3333
"DeclarativeStream.schema_loader": "JsonFileSchemaLoader",
3434
# DynamicDeclarativeStream
3535
"DynamicDeclarativeStream.stream_template": "DeclarativeStream",
36-
"DynamicDeclarativeStream.components_resolver": "HttpComponentsResolver",
36+
"DynamicDeclarativeStream.components_resolver": "ConfigComponentResolver",
3737
# HttpComponentsResolver
3838
"HttpComponentsResolver.retriever": "SimpleRetriever",
3939
"HttpComponentsResolver.components_mapping": "ComponentMappingDefinition",
40+
# ConfigComponentResolver
41+
"ConfigComponentsResolver.stream_config": "StreamConfig",
42+
"ConfigComponentsResolver.components_mapping": "ComponentMappingDefinition",
4043
# DefaultErrorHandler
4144
"DefaultErrorHandler.response_filters": "HttpResponseFilter",
4245
# DefaultPaginator

airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,9 @@
128128
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
129129
ConcurrencyLevel as ConcurrencyLevelModel,
130130
)
131+
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
132+
ConfigComponentsResolver as ConfigComponentsResolverModel,
133+
)
131134
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
132135
ConstantBackoffStrategy as ConstantBackoffStrategyModel,
133136
)
@@ -294,6 +297,9 @@
294297
SimpleRetriever as SimpleRetrieverModel,
295298
)
296299
from airbyte_cdk.sources.declarative.models.declarative_component_schema import Spec as SpecModel
300+
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
301+
StreamConfig as StreamConfigModel,
302+
)
297303
from airbyte_cdk.sources.declarative.models.declarative_component_schema import (
298304
SubstreamPartitionRouter as SubstreamPartitionRouterModel,
299305
)
@@ -356,7 +362,9 @@
356362
from airbyte_cdk.sources.declarative.requesters.requester import HttpMethod
357363
from airbyte_cdk.sources.declarative.resolvers import (
358364
ComponentMappingDefinition,
365+
ConfigComponentsResolver,
359366
HttpComponentsResolver,
367+
StreamConfig,
360368
)
361369
from airbyte_cdk.sources.declarative.retrievers import (
362370
AsyncRetriever,
@@ -494,6 +502,8 @@ def _init_mappings(self) -> None:
494502
WaitUntilTimeFromHeaderModel: self.create_wait_until_time_from_header,
495503
AsyncRetrieverModel: self.create_async_retriever,
496504
HttpComponentsResolverModel: self.create_http_components_resolver,
505+
ConfigComponentsResolverModel: self.create_config_components_resolver,
506+
StreamConfigModel: self.create_stream_config,
497507
ComponentMappingDefinitionModel: self.create_components_mapping_definition,
498508
}
499509

@@ -1884,8 +1894,8 @@ def create_record_selector(
18841894
self,
18851895
model: RecordSelectorModel,
18861896
config: Config,
1887-
name: str,
18881897
*,
1898+
name: str,
18891899
transformations: List[RecordTransformation],
18901900
decoder: Optional[Decoder] = None,
18911901
client_side_incremental_sync: Optional[Dict[str, Any]] = None,
@@ -2364,3 +2374,41 @@ def create_http_components_resolver(
23642374
components_mapping=components_mapping,
23652375
parameters=model.parameters or {},
23662376
)
2377+
2378+
@staticmethod
2379+
def create_stream_config(
2380+
model: StreamConfigModel, config: Config, **kwargs: Any
2381+
) -> StreamConfig:
2382+
model_configs_pointer: List[Union[InterpolatedString, str]] = (
2383+
[x for x in model.configs_pointer] if model.configs_pointer else []
2384+
)
2385+
2386+
return StreamConfig(
2387+
configs_pointer=model_configs_pointer,
2388+
parameters=model.parameters or {},
2389+
)
2390+
2391+
def create_config_components_resolver(
2392+
self, model: ConfigComponentsResolverModel, config: Config
2393+
) -> Any:
2394+
stream_config = self._create_component_from_model(
2395+
model.stream_config, config=config, parameters=model.parameters or {}
2396+
)
2397+
2398+
components_mapping = [
2399+
self._create_component_from_model(
2400+
model=components_mapping_definition_model,
2401+
value_type=ModelToComponentFactory._json_schema_type_name_to_type(
2402+
components_mapping_definition_model.value_type
2403+
),
2404+
config=config,
2405+
)
2406+
for components_mapping_definition_model in model.components_mapping
2407+
]
2408+
2409+
return ConfigComponentsResolver(
2410+
stream_config=stream_config,
2411+
config=config,
2412+
components_mapping=components_mapping,
2413+
parameters=model.parameters or {},
2414+
)

airbyte_cdk/sources/declarative/resolvers/__init__.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@
44

55
from airbyte_cdk.sources.declarative.resolvers.components_resolver import ComponentsResolver, ComponentMappingDefinition, ResolvedComponentMappingDefinition
66
from airbyte_cdk.sources.declarative.resolvers.http_components_resolver import HttpComponentsResolver
7+
from airbyte_cdk.sources.declarative.resolvers.config_components_resolver import ConfigComponentsResolver, StreamConfig
78
from airbyte_cdk.sources.declarative.models import HttpComponentsResolver as HttpComponentsResolverModel
9+
from airbyte_cdk.sources.declarative.models import ConfigComponentsResolver as ConfigComponentsResolverModel
10+
from pydantic.v1 import BaseModel
11+
from typing import Mapping
812

9-
COMPONENTS_RESOLVER_TYPE_MAPPING = {
10-
"HttpComponentsResolver": HttpComponentsResolverModel
13+
COMPONENTS_RESOLVER_TYPE_MAPPING: Mapping[str, type[BaseModel]] = {
14+
"HttpComponentsResolver": HttpComponentsResolverModel,
15+
"ConfigComponentsResolver": ConfigComponentsResolverModel
1116
}
1217

13-
__all__ = ["ComponentsResolver", "HttpComponentsResolver", "ComponentMappingDefinition", "ResolvedComponentMappingDefinition", "COMPONENTS_RESOLVER_TYPE_MAPPING"]
18+
__all__ = ["ComponentsResolver", "HttpComponentsResolver", "ComponentMappingDefinition", "ResolvedComponentMappingDefinition", "StreamConfig", "ConfigComponentsResolver", "COMPONENTS_RESOLVER_TYPE_MAPPING"]

0 commit comments

Comments
 (0)