airbyte-cdk 6.61.2__py3-none-any.whl → 6.61.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- airbyte_cdk/manifest_server/Dockerfile +45 -0
- airbyte_cdk/manifest_server/README.md +142 -0
- airbyte_cdk/manifest_server/__init__.py +3 -0
- airbyte_cdk/manifest_server/api_models/__init__.py +49 -0
- airbyte_cdk/manifest_server/api_models/capabilities.py +7 -0
- airbyte_cdk/manifest_server/api_models/dicts.py +17 -0
- airbyte_cdk/manifest_server/api_models/manifest.py +73 -0
- airbyte_cdk/manifest_server/api_models/stream.py +76 -0
- airbyte_cdk/manifest_server/app.py +17 -0
- airbyte_cdk/manifest_server/auth.py +43 -0
- airbyte_cdk/manifest_server/cli/__init__.py +5 -0
- airbyte_cdk/manifest_server/cli/_common.py +28 -0
- airbyte_cdk/manifest_server/cli/_info.py +30 -0
- airbyte_cdk/manifest_server/cli/_openapi.py +43 -0
- airbyte_cdk/manifest_server/cli/_start.py +38 -0
- airbyte_cdk/manifest_server/cli/run.py +59 -0
- airbyte_cdk/manifest_server/command_processor/__init__.py +0 -0
- airbyte_cdk/manifest_server/command_processor/processor.py +122 -0
- airbyte_cdk/manifest_server/command_processor/utils.py +99 -0
- airbyte_cdk/manifest_server/main.py +24 -0
- airbyte_cdk/manifest_server/openapi.yaml +641 -0
- airbyte_cdk/manifest_server/routers/__init__.py +0 -0
- airbyte_cdk/manifest_server/routers/capabilities.py +25 -0
- airbyte_cdk/manifest_server/routers/health.py +13 -0
- airbyte_cdk/manifest_server/routers/manifest.py +155 -0
- {airbyte_cdk-6.61.2.dist-info → airbyte_cdk-6.61.3.dist-info}/METADATA +4 -1
- {airbyte_cdk-6.61.2.dist-info → airbyte_cdk-6.61.3.dist-info}/RECORD +31 -6
- {airbyte_cdk-6.61.2.dist-info → airbyte_cdk-6.61.3.dist-info}/entry_points.txt +1 -0
- {airbyte_cdk-6.61.2.dist-info → airbyte_cdk-6.61.3.dist-info}/LICENSE.txt +0 -0
- {airbyte_cdk-6.61.2.dist-info → airbyte_cdk-6.61.3.dist-info}/LICENSE_SHORT +0 -0
- {airbyte_cdk-6.61.2.dist-info → airbyte_cdk-6.61.3.dist-info}/WHEEL +0 -0
@@ -0,0 +1,122 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import Any, List, Mapping, Optional, Tuple
|
3
|
+
|
4
|
+
from airbyte_protocol_dataclasses.models import (
|
5
|
+
AirbyteCatalog,
|
6
|
+
Status,
|
7
|
+
)
|
8
|
+
from fastapi import HTTPException
|
9
|
+
|
10
|
+
from airbyte_cdk.connector_builder.models import StreamRead
|
11
|
+
from airbyte_cdk.connector_builder.test_reader import TestReader
|
12
|
+
from airbyte_cdk.entrypoint import AirbyteEntrypoint
|
13
|
+
from airbyte_cdk.models import (
|
14
|
+
AirbyteStateMessage,
|
15
|
+
ConfiguredAirbyteCatalog,
|
16
|
+
)
|
17
|
+
from airbyte_cdk.sources.declarative.concurrent_declarative_source import (
|
18
|
+
ConcurrentDeclarativeSource,
|
19
|
+
)
|
20
|
+
from airbyte_cdk.test.entrypoint_wrapper import AirbyteEntrypointException, EntrypointOutput
|
21
|
+
|
22
|
+
|
23
|
+
class ManifestCommandProcessor:
|
24
|
+
_source: ConcurrentDeclarativeSource[Optional[List[AirbyteStateMessage]]]
|
25
|
+
_logger = logging.getLogger("airbyte.manifest-server")
|
26
|
+
|
27
|
+
def __init__(
|
28
|
+
self, source: ConcurrentDeclarativeSource[Optional[List[AirbyteStateMessage]]]
|
29
|
+
) -> None:
|
30
|
+
self._source = source
|
31
|
+
|
32
|
+
def test_read(
|
33
|
+
self,
|
34
|
+
config: Mapping[str, Any],
|
35
|
+
catalog: ConfiguredAirbyteCatalog,
|
36
|
+
state: List[AirbyteStateMessage],
|
37
|
+
record_limit: int,
|
38
|
+
page_limit: int,
|
39
|
+
slice_limit: int,
|
40
|
+
) -> StreamRead:
|
41
|
+
"""
|
42
|
+
Test the read method of the source.
|
43
|
+
"""
|
44
|
+
|
45
|
+
test_read_handler = TestReader(
|
46
|
+
max_pages_per_slice=page_limit,
|
47
|
+
max_slices=slice_limit,
|
48
|
+
max_record_limit=record_limit,
|
49
|
+
)
|
50
|
+
|
51
|
+
stream_read = test_read_handler.run_test_read(
|
52
|
+
source=self._source,
|
53
|
+
config=config,
|
54
|
+
configured_catalog=catalog,
|
55
|
+
state=state,
|
56
|
+
stream_name=catalog.streams[0].stream.name,
|
57
|
+
record_limit=record_limit,
|
58
|
+
)
|
59
|
+
|
60
|
+
return stream_read
|
61
|
+
|
62
|
+
def check_connection(
|
63
|
+
self,
|
64
|
+
config: Mapping[str, Any],
|
65
|
+
) -> Tuple[bool, Optional[str]]:
|
66
|
+
"""
|
67
|
+
Check the connection to the source.
|
68
|
+
"""
|
69
|
+
|
70
|
+
spec = self._source.spec(self._logger)
|
71
|
+
entrypoint = AirbyteEntrypoint(source=self._source)
|
72
|
+
messages = entrypoint.check(spec, config)
|
73
|
+
output = EntrypointOutput(
|
74
|
+
messages=[AirbyteEntrypoint.airbyte_message_to_string(m) for m in messages],
|
75
|
+
command=["check"],
|
76
|
+
)
|
77
|
+
self._raise_on_trace_message(output)
|
78
|
+
|
79
|
+
status_messages = output.connection_status_messages
|
80
|
+
if not status_messages or status_messages[-1].connectionStatus is None:
|
81
|
+
return False, "Connection check did not return a status message"
|
82
|
+
|
83
|
+
connection_status = status_messages[-1].connectionStatus
|
84
|
+
return (
|
85
|
+
connection_status.status == Status.SUCCEEDED,
|
86
|
+
connection_status.message,
|
87
|
+
)
|
88
|
+
|
89
|
+
def discover(
|
90
|
+
self,
|
91
|
+
config: Mapping[str, Any],
|
92
|
+
) -> Optional[AirbyteCatalog]:
|
93
|
+
"""
|
94
|
+
Discover the catalog from the source.
|
95
|
+
"""
|
96
|
+
spec = self._source.spec(self._logger)
|
97
|
+
entrypoint = AirbyteEntrypoint(source=self._source)
|
98
|
+
messages = entrypoint.discover(spec, config)
|
99
|
+
output = EntrypointOutput(
|
100
|
+
messages=[AirbyteEntrypoint.airbyte_message_to_string(m) for m in messages],
|
101
|
+
command=["discover"],
|
102
|
+
)
|
103
|
+
self._raise_on_trace_message(output)
|
104
|
+
|
105
|
+
try:
|
106
|
+
catalog_message = output.catalog
|
107
|
+
return catalog_message.catalog
|
108
|
+
except ValueError:
|
109
|
+
# No catalog message found
|
110
|
+
return None
|
111
|
+
|
112
|
+
def _raise_on_trace_message(
|
113
|
+
self,
|
114
|
+
output: EntrypointOutput,
|
115
|
+
) -> None:
|
116
|
+
"""
|
117
|
+
Raise an exception if a trace message is found.
|
118
|
+
"""
|
119
|
+
try:
|
120
|
+
output.raise_if_errors()
|
121
|
+
except AirbyteEntrypointException as e:
|
122
|
+
raise HTTPException(status_code=422, detail=e.message)
|
@@ -0,0 +1,99 @@
|
|
1
|
+
import copy
|
2
|
+
from typing import Any, Dict, List, Mapping, Optional
|
3
|
+
|
4
|
+
from airbyte_protocol_dataclasses.models import AirbyteStateMessage
|
5
|
+
|
6
|
+
from airbyte_cdk.models import (
|
7
|
+
AirbyteStream,
|
8
|
+
ConfiguredAirbyteCatalog,
|
9
|
+
ConfiguredAirbyteStream,
|
10
|
+
DestinationSyncMode,
|
11
|
+
SyncMode,
|
12
|
+
)
|
13
|
+
from airbyte_cdk.sources.declarative.concurrent_declarative_source import (
|
14
|
+
ConcurrentDeclarativeSource,
|
15
|
+
TestLimits,
|
16
|
+
)
|
17
|
+
|
18
|
+
SHOULD_NORMALIZE_KEY = "__should_normalize"
|
19
|
+
SHOULD_MIGRATE_KEY = "__should_migrate"
|
20
|
+
|
21
|
+
|
22
|
+
def build_catalog(stream_name: str) -> ConfiguredAirbyteCatalog:
|
23
|
+
return ConfiguredAirbyteCatalog(
|
24
|
+
streams=[
|
25
|
+
ConfiguredAirbyteStream(
|
26
|
+
stream=AirbyteStream(
|
27
|
+
name=stream_name,
|
28
|
+
json_schema={},
|
29
|
+
supported_sync_modes=[SyncMode.full_refresh, SyncMode.incremental],
|
30
|
+
),
|
31
|
+
sync_mode=SyncMode.incremental,
|
32
|
+
destination_sync_mode=DestinationSyncMode.overwrite,
|
33
|
+
)
|
34
|
+
]
|
35
|
+
)
|
36
|
+
|
37
|
+
|
38
|
+
def should_migrate_manifest(manifest: Mapping[str, Any]) -> bool:
|
39
|
+
"""
|
40
|
+
Determines whether the manifest should be migrated,
|
41
|
+
based on the presence of the "__should_migrate" key.
|
42
|
+
|
43
|
+
This flag is set by the UI.
|
44
|
+
"""
|
45
|
+
return manifest.get(SHOULD_MIGRATE_KEY, False)
|
46
|
+
|
47
|
+
|
48
|
+
def should_normalize_manifest(manifest: Mapping[str, Any]) -> bool:
|
49
|
+
"""
|
50
|
+
Determines whether the manifest should be normalized,
|
51
|
+
based on the presence of the "__should_normalize" key.
|
52
|
+
|
53
|
+
This flag is set by the UI.
|
54
|
+
"""
|
55
|
+
return manifest.get(SHOULD_NORMALIZE_KEY, False)
|
56
|
+
|
57
|
+
|
58
|
+
def build_source(
|
59
|
+
manifest: Dict[str, Any],
|
60
|
+
catalog: Optional[ConfiguredAirbyteCatalog],
|
61
|
+
config: Mapping[str, Any],
|
62
|
+
state: Optional[List[AirbyteStateMessage]],
|
63
|
+
record_limit: Optional[int] = None,
|
64
|
+
page_limit: Optional[int] = None,
|
65
|
+
slice_limit: Optional[int] = None,
|
66
|
+
) -> ConcurrentDeclarativeSource[Optional[List[AirbyteStateMessage]]]:
|
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.
|
69
|
+
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
|
+
}
|
77
|
+
|
78
|
+
should_normalize = should_normalize_manifest(manifest)
|
79
|
+
if should_normalize:
|
80
|
+
del definition[SHOULD_NORMALIZE_KEY]
|
81
|
+
|
82
|
+
should_migrate = should_migrate_manifest(manifest)
|
83
|
+
if should_migrate:
|
84
|
+
del definition[SHOULD_MIGRATE_KEY]
|
85
|
+
|
86
|
+
return ConcurrentDeclarativeSource(
|
87
|
+
catalog=catalog,
|
88
|
+
state=state,
|
89
|
+
source_config=definition,
|
90
|
+
config=config,
|
91
|
+
normalize_manifest=should_normalize,
|
92
|
+
migrate_manifest=should_migrate,
|
93
|
+
emit_connector_builder_messages=True,
|
94
|
+
limits=TestLimits(
|
95
|
+
max_pages_per_slice=page_limit or TestLimits.DEFAULT_MAX_PAGES_PER_SLICE,
|
96
|
+
max_slices=slice_limit or TestLimits.DEFAULT_MAX_SLICES,
|
97
|
+
max_records=record_limit or TestLimits.DEFAULT_MAX_RECORDS,
|
98
|
+
),
|
99
|
+
)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# Copyright (c) 2025 Airbyte, Inc., all rights reserved.
|
2
|
+
"""Main entry point for the Airbyte Manifest Server server."""
|
3
|
+
|
4
|
+
import uvicorn
|
5
|
+
|
6
|
+
|
7
|
+
def run_server(
|
8
|
+
host: str = "127.0.0.1", port: int = 8000, reload: bool = False, log_level: str = "info"
|
9
|
+
) -> None:
|
10
|
+
"""Run the FastAPI server."""
|
11
|
+
|
12
|
+
print(f"🚀 Starting Airbyte CDK Manifest Server on {host}:{port}")
|
13
|
+
|
14
|
+
uvicorn.run(
|
15
|
+
"airbyte_cdk.manifest_server.app:app",
|
16
|
+
host=host,
|
17
|
+
port=port,
|
18
|
+
reload=reload,
|
19
|
+
log_level=log_level,
|
20
|
+
)
|
21
|
+
|
22
|
+
|
23
|
+
if __name__ == "__main__":
|
24
|
+
run_server()
|