Skip to content

Commit 901a313

Browse files
committed
Merge branch 'main' into brian/property_chunking_only_fetch_enabled_fields
2 parents 15d8769 + 041c201 commit 901a313

File tree

15 files changed

+477
-205
lines changed

15 files changed

+477
-205
lines changed

airbyte_cdk/manifest_server/api_models/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
CheckResponse,
1111
DiscoverRequest,
1212
DiscoverResponse,
13+
ErrorResponse,
1314
FullResolveRequest,
1415
ManifestResponse,
1516
RequestContext,
@@ -40,6 +41,7 @@
4041
"CheckResponse",
4142
"DiscoverRequest",
4243
"DiscoverResponse",
44+
"ErrorResponse",
4345
# Stream models
4446
"AuxiliaryRequest",
4547
"HttpRequest",

airbyte_cdk/manifest_server/api_models/manifest.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,9 @@ class FullResolveRequest(BaseModel):
8383
config: ConnectorConfig
8484
stream_limit: int = Field(default=100, ge=1, le=100)
8585
context: Optional[RequestContext] = None
86+
87+
88+
class ErrorResponse(BaseModel):
89+
"""Error response for API requests."""
90+
91+
detail: str

airbyte_cdk/manifest_server/app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
app = FastAPI(
1212
title="Manifest Server",
1313
description="A service for running low-code Airbyte connectors",
14-
version="0.1.0",
14+
version="0.2.0",
1515
contact={
1616
"name": "Airbyte",
1717
"url": "https://airbyte.com",

airbyte_cdk/manifest_server/command_processor/utils.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,7 @@ def build_source(
6464
page_limit: Optional[int] = None,
6565
slice_limit: Optional[int] = None,
6666
) -> ConcurrentDeclarativeSource:
67-
# We enforce a concurrency level of 1 so that the stream is processed on a single thread
68-
# to retain ordering for the grouping of the builder message responses.
6967
definition = copy.deepcopy(manifest)
70-
if "concurrency_level" in definition:
71-
definition["concurrency_level"]["default_concurrency"] = 1
72-
else:
73-
definition["concurrency_level"] = {
74-
"type": "ConcurrencyLevel",
75-
"default_concurrency": 1,
76-
}
7768

7869
should_normalize = should_normalize_manifest(manifest)
7970
if should_normalize:

airbyte_cdk/manifest_server/openapi.yaml

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ info:
88
contact:
99
name: Airbyte
1010
url: https://airbyte.com/
11-
version: 0.1.0
11+
version: 0.2.0
1212
paths:
1313
/health/:
1414
get:
@@ -62,6 +62,12 @@ paths:
6262
application/json:
6363
schema:
6464
$ref: '#/components/schemas/StreamReadResponse'
65+
'400':
66+
description: Bad Request - Error processing request
67+
content:
68+
application/json:
69+
schema:
70+
$ref: '#/components/schemas/ErrorResponse'
6571
'422':
6672
description: Validation Error
6773
content:
@@ -90,6 +96,12 @@ paths:
9096
application/json:
9197
schema:
9298
$ref: '#/components/schemas/CheckResponse'
99+
'400':
100+
description: Bad Request - Error processing request
101+
content:
102+
application/json:
103+
schema:
104+
$ref: '#/components/schemas/ErrorResponse'
93105
'422':
94106
description: Validation Error
95107
content:
@@ -118,6 +130,12 @@ paths:
118130
application/json:
119131
schema:
120132
$ref: '#/components/schemas/DiscoverResponse'
133+
'400':
134+
description: Bad Request - Error processing request
135+
content:
136+
application/json:
137+
schema:
138+
$ref: '#/components/schemas/ErrorResponse'
121139
'422':
122140
description: Validation Error
123141
content:
@@ -146,6 +164,12 @@ paths:
146164
application/json:
147165
schema:
148166
$ref: '#/components/schemas/ManifestResponse'
167+
'400':
168+
description: Bad Request - Error processing request
169+
content:
170+
application/json:
171+
schema:
172+
$ref: '#/components/schemas/ErrorResponse'
149173
'422':
150174
description: Validation Error
151175
content:
@@ -180,6 +204,12 @@ paths:
180204
application/json:
181205
schema:
182206
$ref: '#/components/schemas/ManifestResponse'
207+
'400':
208+
description: Bad Request - Error processing request
209+
content:
210+
application/json:
211+
schema:
212+
$ref: '#/components/schemas/ErrorResponse'
183213
'422':
184214
description: Validation Error
185215
content:
@@ -353,6 +383,16 @@ components:
353383
- catalog
354384
title: DiscoverResponse
355385
description: Response to discover a manifest.
386+
ErrorResponse:
387+
properties:
388+
detail:
389+
type: string
390+
title: Detail
391+
type: object
392+
required:
393+
- detail
394+
title: ErrorResponse
395+
description: Error response for API requests.
356396
FullResolveRequest:
357397
properties:
358398
manifest:

airbyte_cdk/manifest_server/routers/manifest.py

Lines changed: 114 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@
1414
INJECTED_COMPONENTS_PY,
1515
INJECTED_COMPONENTS_PY_CHECKSUMS,
1616
)
17+
from airbyte_cdk.utils.airbyte_secrets_utils import filter_secrets
1718

1819
from ..api_models import (
1920
CheckRequest,
2021
CheckResponse,
2122
DiscoverRequest,
2223
DiscoverResponse,
24+
ErrorResponse,
2325
FullResolveRequest,
2426
Manifest,
2527
ManifestResponse,
@@ -64,7 +66,13 @@ def safe_build_source(
6466
)
6567

6668

67-
@router.post("/test_read", operation_id="testRead")
69+
@router.post(
70+
"/test_read",
71+
operation_id="testRead",
72+
responses={
73+
400: {"description": "Bad Request - Error processing request", "model": ErrorResponse}
74+
},
75+
)
6876
def test_read(request: StreamTestReadRequest) -> StreamReadResponse:
6977
"""
7078
Test reading from a specific stream in the manifest.
@@ -87,8 +95,19 @@ def test_read(request: StreamTestReadRequest) -> StreamReadResponse:
8795
"md5": hashlib.md5(request.custom_components_code.encode()).hexdigest()
8896
}
8997

98+
# We enforce a concurrency level of 1 so that the stream is processed on a single thread
99+
# to retain ordering for the grouping of the builder message responses.
100+
manifest = request.manifest.model_dump()
101+
if "concurrency_level" in manifest:
102+
manifest["concurrency_level"]["default_concurrency"] = 1
103+
else:
104+
manifest["concurrency_level"] = {
105+
"type": "ConcurrencyLevel",
106+
"default_concurrency": 1,
107+
}
108+
90109
source = safe_build_source(
91-
request.manifest.model_dump(),
110+
manifest,
92111
config_dict,
93112
catalog,
94113
converted_state,
@@ -98,18 +117,29 @@ def test_read(request: StreamTestReadRequest) -> StreamReadResponse:
98117
)
99118

100119
runner = ManifestCommandProcessor(source)
101-
cdk_result = runner.test_read(
102-
config_dict,
103-
catalog,
104-
converted_state,
105-
request.record_limit,
106-
request.page_limit,
107-
request.slice_limit,
108-
)
109-
return StreamReadResponse.model_validate(asdict(cdk_result))
120+
try:
121+
cdk_result = runner.test_read(
122+
config_dict,
123+
catalog,
124+
converted_state,
125+
request.record_limit,
126+
request.page_limit,
127+
request.slice_limit,
128+
)
129+
return StreamReadResponse.model_validate(asdict(cdk_result))
130+
except Exception as exc:
131+
# Filter secrets from error message before returning to client
132+
sanitized_message = filter_secrets(f"Error reading stream: {str(exc)}")
133+
raise HTTPException(status_code=400, detail=sanitized_message)
110134

111135

112-
@router.post("/check", operation_id="check")
136+
@router.post(
137+
"/check",
138+
operation_id="check",
139+
responses={
140+
400: {"description": "Bad Request - Error processing request", "model": ErrorResponse}
141+
},
142+
)
113143
def check(request: CheckRequest) -> CheckResponse:
114144
"""Check configuration against a manifest"""
115145
# Apply trace tags from context if provided
@@ -119,13 +149,24 @@ def check(request: CheckRequest) -> CheckResponse:
119149
project_id=request.context.project_id,
120150
)
121151

122-
source = safe_build_source(request.manifest.model_dump(), request.config.model_dump())
123-
runner = ManifestCommandProcessor(source)
124-
success, message = runner.check_connection(request.config.model_dump())
125-
return CheckResponse(success=success, message=message)
152+
try:
153+
source = safe_build_source(request.manifest.model_dump(), request.config.model_dump())
154+
runner = ManifestCommandProcessor(source)
155+
success, message = runner.check_connection(request.config.model_dump())
156+
return CheckResponse(success=success, message=message)
157+
except Exception as exc:
158+
# Filter secrets from error message before returning to client
159+
sanitized_message = filter_secrets(f"Error checking connection: {str(exc)}")
160+
raise HTTPException(status_code=400, detail=sanitized_message)
126161

127162

128-
@router.post("/discover", operation_id="discover")
163+
@router.post(
164+
"/discover",
165+
operation_id="discover",
166+
responses={
167+
400: {"description": "Bad Request - Error processing request", "model": ErrorResponse}
168+
},
169+
)
129170
def discover(request: DiscoverRequest) -> DiscoverResponse:
130171
"""Discover streams from a manifest"""
131172
# Apply trace tags from context if provided
@@ -135,15 +176,31 @@ def discover(request: DiscoverRequest) -> DiscoverResponse:
135176
project_id=request.context.project_id,
136177
)
137178

138-
source = safe_build_source(request.manifest.model_dump(), request.config.model_dump())
139-
runner = ManifestCommandProcessor(source)
140-
catalog = runner.discover(request.config.model_dump())
141-
if catalog is None:
142-
raise HTTPException(status_code=422, detail="Connector did not return a discovered catalog")
143-
return DiscoverResponse(catalog=catalog)
179+
try:
180+
source = safe_build_source(request.manifest.model_dump(), request.config.model_dump())
181+
runner = ManifestCommandProcessor(source)
182+
catalog = runner.discover(request.config.model_dump())
183+
if catalog is None:
184+
raise HTTPException(
185+
status_code=422, detail="Connector did not return a discovered catalog"
186+
)
187+
return DiscoverResponse(catalog=catalog)
188+
except HTTPException:
189+
# Re-raise HTTPExceptions as-is (like the catalog None check above)
190+
raise
191+
except Exception as exc:
192+
# Filter secrets from error message before returning to client
193+
sanitized_message = filter_secrets(f"Error discovering streams: {str(exc)}")
194+
raise HTTPException(status_code=400, detail=sanitized_message)
144195

145196

146-
@router.post("/resolve", operation_id="resolve")
197+
@router.post(
198+
"/resolve",
199+
operation_id="resolve",
200+
responses={
201+
400: {"description": "Bad Request - Error processing request", "model": ErrorResponse}
202+
},
203+
)
147204
def resolve(request: ResolveRequest) -> ManifestResponse:
148205
"""Resolve a manifest to its final configuration."""
149206
# Apply trace tags from context if provided
@@ -153,11 +210,22 @@ def resolve(request: ResolveRequest) -> ManifestResponse:
153210
project_id=request.context.project_id,
154211
)
155212

156-
source = safe_build_source(request.manifest.model_dump(), {})
157-
return ManifestResponse(manifest=Manifest(**source.resolved_manifest))
213+
try:
214+
source = safe_build_source(request.manifest.model_dump(), {})
215+
return ManifestResponse(manifest=Manifest(**source.resolved_manifest))
216+
except Exception as exc:
217+
# Filter secrets from error message before returning to client
218+
sanitized_message = filter_secrets(f"Error resolving manifest: {str(exc)}")
219+
raise HTTPException(status_code=400, detail=sanitized_message)
158220

159221

160-
@router.post("/full_resolve", operation_id="fullResolve")
222+
@router.post(
223+
"/full_resolve",
224+
operation_id="fullResolve",
225+
responses={
226+
400: {"description": "Bad Request - Error processing request", "model": ErrorResponse}
227+
},
228+
)
161229
def full_resolve(request: FullResolveRequest) -> ManifestResponse:
162230
"""
163231
Fully resolve a manifest, including dynamic streams.
@@ -171,21 +239,26 @@ def full_resolve(request: FullResolveRequest) -> ManifestResponse:
171239
project_id=request.context.project_id,
172240
)
173241

174-
source = safe_build_source(request.manifest.model_dump(), request.config.model_dump())
175-
manifest = {**source.resolved_manifest}
176-
streams = manifest.get("streams", [])
177-
for stream in streams:
178-
stream["dynamic_stream_name"] = None
242+
try:
243+
source = safe_build_source(request.manifest.model_dump(), request.config.model_dump())
244+
manifest = {**source.resolved_manifest}
245+
streams = manifest.get("streams", [])
246+
for stream in streams:
247+
stream["dynamic_stream_name"] = None
179248

180-
mapped_streams: Dict[str, List[Dict[str, Any]]] = {}
181-
for stream in source.dynamic_streams:
182-
generated_streams = mapped_streams.setdefault(stream["dynamic_stream_name"], [])
249+
mapped_streams: Dict[str, List[Dict[str, Any]]] = {}
250+
for stream in source.dynamic_streams:
251+
generated_streams = mapped_streams.setdefault(stream["dynamic_stream_name"], [])
183252

184-
if len(generated_streams) < request.stream_limit:
185-
generated_streams += [stream]
253+
if len(generated_streams) < request.stream_limit:
254+
generated_streams += [stream]
186255

187-
for generated_streams_list in mapped_streams.values():
188-
streams.extend(generated_streams_list)
256+
for generated_streams_list in mapped_streams.values():
257+
streams.extend(generated_streams_list)
189258

190-
manifest["streams"] = streams
191-
return ManifestResponse(manifest=Manifest(**manifest))
259+
manifest["streams"] = streams
260+
return ManifestResponse(manifest=Manifest(**manifest))
261+
except Exception as exc:
262+
# Filter secrets from error message before returning to client
263+
sanitized_message = filter_secrets(f"Error full resolving manifest: {str(exc)}")
264+
raise HTTPException(status_code=400, detail=sanitized_message)

airbyte_cdk/sources/declarative/extractors/response_to_file_extractor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
import zlib
88
from contextlib import closing
99
from dataclasses import InitVar, dataclass
10+
from math import nan
1011
from typing import Any, Dict, Iterable, Mapping, Optional, Tuple
1112

1213
import pandas as pd
1314
import requests
14-
from numpy import nan
1515

1616
from airbyte_cdk.sources.declarative.extractors.record_extractor import RecordExtractor
1717

airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3923,7 +3923,9 @@ def _instantiate_parent_stream_state_manager(
39233923

39243924
if not parent_state and not isinstance(parent_state, dict):
39253925
cursor_values = child_state.values()
3926-
if cursor_values:
3926+
if cursor_values and len(cursor_values) == 1:
3927+
# We assume the child state is a pair `{<cursor_field>: <cursor_value>}` and we will use the
3928+
# cursor value as a parent state.
39273929
incremental_sync_model: Union[
39283930
DatetimeBasedCursorModel,
39293931
IncrementingCountCursorModel,

0 commit comments

Comments
 (0)