airbyte-cdk 6.60.15__py3-none-any.whl → 6.60.16.post40.dev17219503797__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.
Files changed (47) hide show
  1. airbyte_cdk/connector_builder/connector_builder_handler.py +32 -36
  2. airbyte_cdk/connector_builder/main.py +3 -3
  3. airbyte_cdk/connector_builder/test_reader/helpers.py +24 -2
  4. airbyte_cdk/connector_builder/test_reader/message_grouper.py +1 -1
  5. airbyte_cdk/manifest_server/Dockerfile +45 -0
  6. airbyte_cdk/manifest_server/README.md +142 -0
  7. airbyte_cdk/manifest_server/__init__.py +3 -0
  8. airbyte_cdk/manifest_server/api_models/__init__.py +41 -0
  9. airbyte_cdk/manifest_server/api_models/capabilities.py +7 -0
  10. airbyte_cdk/manifest_server/api_models/dicts.py +17 -0
  11. airbyte_cdk/manifest_server/api_models/manifest.py +73 -0
  12. airbyte_cdk/manifest_server/api_models/stream.py +76 -0
  13. airbyte_cdk/manifest_server/app.py +17 -0
  14. airbyte_cdk/manifest_server/auth.py +43 -0
  15. airbyte_cdk/manifest_server/cli/__init__.py +5 -0
  16. airbyte_cdk/manifest_server/cli/_common.py +28 -0
  17. airbyte_cdk/manifest_server/cli/_info.py +30 -0
  18. airbyte_cdk/manifest_server/cli/_openapi.py +43 -0
  19. airbyte_cdk/manifest_server/cli/_start.py +38 -0
  20. airbyte_cdk/manifest_server/cli/run.py +59 -0
  21. airbyte_cdk/manifest_server/command_processor/__init__.py +0 -0
  22. airbyte_cdk/manifest_server/command_processor/processor.py +151 -0
  23. airbyte_cdk/manifest_server/command_processor/utils.py +76 -0
  24. airbyte_cdk/manifest_server/main.py +24 -0
  25. airbyte_cdk/manifest_server/openapi.yaml +641 -0
  26. airbyte_cdk/manifest_server/routers/__init__.py +0 -0
  27. airbyte_cdk/manifest_server/routers/capabilities.py +25 -0
  28. airbyte_cdk/manifest_server/routers/health.py +13 -0
  29. airbyte_cdk/manifest_server/routers/manifest.py +137 -0
  30. airbyte_cdk/sources/concurrent_source/concurrent_read_processor.py +15 -22
  31. airbyte_cdk/sources/concurrent_source/concurrent_source.py +30 -18
  32. airbyte_cdk/sources/declarative/concurrent_declarative_source.py +73 -3
  33. airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +66 -39
  34. airbyte_cdk/sources/declarative/stream_slicers/declarative_partition_generator.py +42 -4
  35. airbyte_cdk/sources/declarative/stream_slicers/stream_slicer_test_read_decorator.py +2 -2
  36. airbyte_cdk/sources/message/concurrent_repository.py +47 -0
  37. airbyte_cdk/sources/streams/concurrent/cursor.py +23 -7
  38. airbyte_cdk/sources/streams/concurrent/partition_reader.py +46 -5
  39. airbyte_cdk/sources/streams/concurrent/partitions/types.py +7 -1
  40. airbyte_cdk/sources/streams/http/http_client.py +4 -1
  41. airbyte_cdk/sources/utils/slice_logger.py +4 -0
  42. {airbyte_cdk-6.60.15.dist-info → airbyte_cdk-6.60.16.post40.dev17219503797.dist-info}/METADATA +4 -1
  43. {airbyte_cdk-6.60.15.dist-info → airbyte_cdk-6.60.16.post40.dev17219503797.dist-info}/RECORD +47 -21
  44. {airbyte_cdk-6.60.15.dist-info → airbyte_cdk-6.60.16.post40.dev17219503797.dist-info}/entry_points.txt +1 -0
  45. {airbyte_cdk-6.60.15.dist-info → airbyte_cdk-6.60.16.post40.dev17219503797.dist-info}/LICENSE.txt +0 -0
  46. {airbyte_cdk-6.60.15.dist-info → airbyte_cdk-6.60.16.post40.dev17219503797.dist-info}/LICENSE_SHORT +0 -0
  47. {airbyte_cdk-6.60.15.dist-info → airbyte_cdk-6.60.16.post40.dev17219503797.dist-info}/WHEEL +0 -0
@@ -0,0 +1,28 @@
1
+ # Copyright (c) 2025 Airbyte, Inc., all rights reserved.
2
+ """Common utilities for manifest server CLI commands."""
3
+
4
+ import sys
5
+
6
+ import rich_click as click
7
+
8
+ # Import server dependencies with graceful fallback
9
+ try:
10
+ import fastapi # noqa: F401
11
+ import uvicorn # noqa: F401
12
+
13
+ FASTAPI_AVAILABLE = True
14
+ except ImportError:
15
+ FASTAPI_AVAILABLE = False
16
+
17
+
18
+ def check_manifest_server_dependencies() -> None:
19
+ """Check if manifest-server dependencies are installed."""
20
+ if not FASTAPI_AVAILABLE:
21
+ click.echo(
22
+ "❌ Manifest runner dependencies not found. Please install with:\n\n"
23
+ " pip install airbyte-cdk[manifest-server]\n"
24
+ " # or\n"
25
+ " poetry install --extras manifest-server\n",
26
+ err=True,
27
+ )
28
+ sys.exit(1)
@@ -0,0 +1,30 @@
1
+ # Copyright (c) 2025 Airbyte, Inc., all rights reserved.
2
+ """Info command for the manifest server CLI."""
3
+
4
+ from typing import Any, Optional
5
+
6
+ import rich_click as click
7
+
8
+ # Import server dependencies with graceful fallback
9
+ fastapi: Optional[Any] = None
10
+ uvicorn: Optional[Any] = None
11
+
12
+ try:
13
+ import fastapi # type: ignore[no-redef]
14
+ import uvicorn # type: ignore[no-redef]
15
+
16
+ FASTAPI_AVAILABLE = True
17
+ except ImportError:
18
+ FASTAPI_AVAILABLE = False
19
+
20
+
21
+ @click.command()
22
+ def info() -> None:
23
+ """Show manifest server information and status."""
24
+ if FASTAPI_AVAILABLE and fastapi is not None and uvicorn is not None:
25
+ click.echo("✅ Manifest runner dependencies are installed")
26
+ click.echo(f" FastAPI version: {fastapi.__version__}")
27
+ click.echo(f" Uvicorn version: {uvicorn.__version__}")
28
+ else:
29
+ click.echo("❌ Manifest runner dependencies not installed")
30
+ click.echo(" Install with: pip install airbyte-cdk[manifest-server]")
@@ -0,0 +1,43 @@
1
+ # Copyright (c) 2025 Airbyte, Inc., all rights reserved.
2
+ """Generate OpenAPI command for the manifest server CLI."""
3
+
4
+ from pathlib import Path
5
+
6
+ import rich_click as click
7
+ from yaml import dump
8
+
9
+ from ._common import check_manifest_server_dependencies
10
+
11
+
12
+ @click.command("generate-openapi")
13
+ @click.option(
14
+ "--output",
15
+ "-o",
16
+ default="airbyte_cdk/manifest_server/openapi.yaml",
17
+ help="Output path for the OpenAPI YAML file",
18
+ show_default=True,
19
+ )
20
+ def generate_openapi(output: str) -> None:
21
+ """Generate OpenAPI YAML specification for the manifest server."""
22
+ check_manifest_server_dependencies()
23
+
24
+ # Import the FastAPI app
25
+ from airbyte_cdk.manifest_server.app import app
26
+
27
+ # Get OpenAPI schema
28
+ openapi_schema = app.openapi()
29
+
30
+ # Ensure output directory exists
31
+ output_path = Path(output)
32
+ output_path.parent.mkdir(parents=True, exist_ok=True)
33
+
34
+ # Write YAML file with header comment
35
+ with open(output_path, "w") as f:
36
+ f.write("# This file is auto-generated. Do not edit manually.\n")
37
+ f.write("# To regenerate, run: manifest-server generate-openapi\n")
38
+ f.write("\n")
39
+ dump(openapi_schema, f, default_flow_style=False, sort_keys=False)
40
+
41
+ click.echo(f"✅ OpenAPI YAML generated at: {output_path}")
42
+ click.echo(f" Title: {openapi_schema.get('info', {}).get('title', 'N/A')}")
43
+ click.echo(f" Version: {openapi_schema.get('info', {}).get('version', 'N/A')}")
@@ -0,0 +1,38 @@
1
+ # Copyright (c) 2025 Airbyte, Inc., all rights reserved.
2
+ """Start command for the manifest server CLI."""
3
+
4
+ import rich_click as click
5
+
6
+ from ._common import check_manifest_server_dependencies
7
+
8
+
9
+ @click.command()
10
+ @click.option(
11
+ "--host",
12
+ default="127.0.0.1",
13
+ help="Host to bind the server to",
14
+ show_default=True,
15
+ )
16
+ @click.option(
17
+ "--port",
18
+ default=8000,
19
+ help="Port to bind the server to",
20
+ show_default=True,
21
+ )
22
+ @click.option(
23
+ "--reload",
24
+ is_flag=True,
25
+ help="Enable auto-reload for development",
26
+ )
27
+ def start(host: str, port: int, reload: bool) -> None:
28
+ """Start the FastAPI manifest server server."""
29
+ check_manifest_server_dependencies()
30
+
31
+ # Import and use the main server function
32
+ from airbyte_cdk.manifest_server.main import run_server
33
+
34
+ run_server(
35
+ host=host,
36
+ port=port,
37
+ reload=reload,
38
+ )
@@ -0,0 +1,59 @@
1
+ # Copyright (c) 2025 Airbyte, Inc., all rights reserved.
2
+ """Standalone CLI for the Airbyte CDK Manifest Server.
3
+
4
+ This CLI provides commands for running and managing the manifest server server.
5
+
6
+ **Installation:**
7
+
8
+ To use the manifest-server functionality, install the CDK with the manifest-server extra:
9
+
10
+ ```bash
11
+ pip install airbyte-cdk[manifest-server]
12
+ # or
13
+ poetry install --extras manifest-server
14
+ ```
15
+
16
+ **Usage:**
17
+
18
+ ```bash
19
+ manifest-server start --port 8000
20
+ manifest-server info
21
+ manifest-server --help
22
+ ```
23
+ """
24
+
25
+ import rich_click as click
26
+
27
+ from ._info import info
28
+ from ._openapi import generate_openapi
29
+ from ._start import start
30
+
31
+
32
+ @click.group(
33
+ help=__doc__.replace("\n", "\n\n"), # Render docstring as help text (markdown)
34
+ invoke_without_command=True,
35
+ )
36
+ @click.pass_context
37
+ def cli(
38
+ ctx: click.Context,
39
+ ) -> None:
40
+ """Airbyte Manifest Server CLI."""
41
+
42
+ if ctx.invoked_subcommand is None:
43
+ # If no subcommand is provided, show the help message.
44
+ click.echo(ctx.get_help())
45
+ ctx.exit()
46
+
47
+
48
+ cli.add_command(start)
49
+ cli.add_command(info)
50
+ cli.add_command(generate_openapi)
51
+
52
+
53
+ def run() -> None:
54
+ """Entry point for the manifest-server CLI."""
55
+ cli()
56
+
57
+
58
+ if __name__ == "__main__":
59
+ run()
@@ -0,0 +1,151 @@
1
+ import logging
2
+ from typing import Any, Dict, Iterable, List, Mapping, Optional, Tuple
3
+
4
+ from airbyte_protocol_dataclasses.models import (
5
+ AirbyteCatalog,
6
+ AirbyteConnectionStatus,
7
+ AirbyteMessage,
8
+ Status,
9
+ TraceType,
10
+ )
11
+ from airbyte_protocol_dataclasses.models import Type as AirbyteMessageType
12
+ from fastapi import HTTPException
13
+
14
+ from airbyte_cdk.connector_builder.models import StreamRead
15
+ from airbyte_cdk.connector_builder.test_reader import TestReader
16
+ from airbyte_cdk.entrypoint import AirbyteEntrypoint
17
+ from airbyte_cdk.models import (
18
+ AirbyteStateMessage,
19
+ ConfiguredAirbyteCatalog,
20
+ )
21
+ from airbyte_cdk.sources.declarative.manifest_declarative_source import (
22
+ ManifestDeclarativeSource,
23
+ )
24
+
25
+
26
+ class ManifestCommandProcessor:
27
+ _source: ManifestDeclarativeSource
28
+ _logger = logging.getLogger("airbyte.manifest-server")
29
+
30
+ def __init__(self, source: ManifestDeclarativeSource) -> None:
31
+ self._source = source
32
+
33
+ def test_read(
34
+ self,
35
+ config: Mapping[str, Any],
36
+ catalog: ConfiguredAirbyteCatalog,
37
+ state: List[AirbyteStateMessage],
38
+ record_limit: int,
39
+ page_limit: int,
40
+ slice_limit: int,
41
+ ) -> StreamRead:
42
+ """
43
+ Test the read method of the source.
44
+ """
45
+
46
+ test_read_handler = TestReader(
47
+ max_pages_per_slice=page_limit,
48
+ max_slices=slice_limit,
49
+ max_record_limit=record_limit,
50
+ )
51
+
52
+ stream_read = test_read_handler.run_test_read(
53
+ source=self._source,
54
+ config=config,
55
+ configured_catalog=catalog,
56
+ state=state,
57
+ stream_name=catalog.streams[0].stream.name,
58
+ record_limit=record_limit,
59
+ )
60
+
61
+ return stream_read
62
+
63
+ def check_connection(
64
+ self,
65
+ config: Mapping[str, Any],
66
+ ) -> Tuple[bool, str]:
67
+ """
68
+ Check the connection to the source.
69
+ """
70
+
71
+ spec = self._source.spec(self._logger)
72
+ messages = AirbyteEntrypoint(source=self._source).check(spec, config)
73
+ messages_by_type = self._get_messages_by_type(messages)
74
+ self._raise_on_trace_message(messages_by_type)
75
+ connection_status = self._get_connection_status(messages_by_type)
76
+
77
+ if connection_status:
78
+ return connection_status.status == Status.SUCCEEDED, connection_status.message
79
+ return False, "Connection check failed"
80
+
81
+ def discover(
82
+ self,
83
+ config: Mapping[str, Any],
84
+ ) -> Optional[AirbyteCatalog]:
85
+ """
86
+ Discover the catalog from the source.
87
+ """
88
+ spec = self._source.spec(self._logger)
89
+ messages = AirbyteEntrypoint(source=self._source).discover(spec, config)
90
+ messages_by_type = self._get_messages_by_type(messages)
91
+ self._raise_on_trace_message(messages_by_type)
92
+ return self._get_catalog(messages_by_type)
93
+
94
+ def _get_messages_by_type(
95
+ self,
96
+ messages: Iterable[AirbyteMessage],
97
+ ) -> Dict[str, List[AirbyteMessage]]:
98
+ """
99
+ Group messages by type.
100
+ """
101
+ grouped: Dict[str, List[AirbyteMessage]] = {}
102
+ for message in messages:
103
+ msg_type = message.type
104
+ if msg_type not in grouped:
105
+ grouped[msg_type] = []
106
+ grouped[msg_type].append(message)
107
+ return grouped
108
+
109
+ def _get_connection_status(
110
+ self,
111
+ messages_by_type: Mapping[str, List[AirbyteMessage]],
112
+ ) -> Optional[AirbyteConnectionStatus]:
113
+ """
114
+ Get the connection status from the messages.
115
+ """
116
+ messages = messages_by_type.get(AirbyteMessageType.CONNECTION_STATUS)
117
+ return messages[-1].connectionStatus if messages else None
118
+
119
+ def _get_catalog(
120
+ self,
121
+ messages_by_type: Mapping[str, List[AirbyteMessage]],
122
+ ) -> Optional[AirbyteCatalog]:
123
+ """
124
+ Get the catalog from the messages.
125
+ """
126
+ messages = messages_by_type.get(AirbyteMessageType.CATALOG)
127
+ return messages[-1].catalog if messages else None
128
+
129
+ def _raise_on_trace_message(
130
+ self,
131
+ messages_by_type: Mapping[str, List[AirbyteMessage]],
132
+ ) -> None:
133
+ """
134
+ Raise an exception if a trace message is found.
135
+ """
136
+ messages = [
137
+ message
138
+ for message in messages_by_type.get(AirbyteMessageType.TRACE, [])
139
+ if message.trace.type == TraceType.ERROR
140
+ ]
141
+ if messages:
142
+ error_message = messages[-1].trace.error.message
143
+ self._logger.warning(
144
+ "Error response from CDK: %s\n%s",
145
+ error_message,
146
+ messages[-1].trace.error.stack_trace,
147
+ )
148
+ raise HTTPException(
149
+ status_code=422,
150
+ detail=f"AirbyteTraceMessage response from CDK: {error_message}",
151
+ )
@@ -0,0 +1,76 @@
1
+ from typing import Any, Mapping, Optional
2
+
3
+ from airbyte_cdk.models import (
4
+ AirbyteStream,
5
+ ConfiguredAirbyteCatalog,
6
+ ConfiguredAirbyteStream,
7
+ DestinationSyncMode,
8
+ SyncMode,
9
+ )
10
+ from airbyte_cdk.sources.declarative.manifest_declarative_source import (
11
+ ManifestDeclarativeSource,
12
+ )
13
+ from airbyte_cdk.sources.declarative.parsers.model_to_component_factory import (
14
+ ModelToComponentFactory,
15
+ )
16
+
17
+ SHOULD_NORMALIZE_KEY = "__should_normalize"
18
+ SHOULD_MIGRATE_KEY = "__should_migrate"
19
+
20
+
21
+ def build_catalog(stream_name: str) -> ConfiguredAirbyteCatalog:
22
+ return ConfiguredAirbyteCatalog(
23
+ streams=[
24
+ ConfiguredAirbyteStream(
25
+ stream=AirbyteStream(
26
+ name=stream_name,
27
+ json_schema={},
28
+ supported_sync_modes=[SyncMode.full_refresh, SyncMode.incremental],
29
+ ),
30
+ sync_mode=SyncMode.incremental,
31
+ destination_sync_mode=DestinationSyncMode.overwrite,
32
+ )
33
+ ]
34
+ )
35
+
36
+
37
+ def should_migrate_manifest(manifest: Mapping[str, Any]) -> bool:
38
+ """
39
+ Determines whether the manifest should be migrated,
40
+ based on the presence of the "__should_migrate" key.
41
+
42
+ This flag is set by the UI.
43
+ """
44
+ return manifest.get(SHOULD_MIGRATE_KEY, False)
45
+
46
+
47
+ def should_normalize_manifest(manifest: Mapping[str, Any]) -> bool:
48
+ """
49
+ Determines whether the manifest should be normalized,
50
+ based on the presence of the "__should_normalize" key.
51
+
52
+ This flag is set by the UI.
53
+ """
54
+ return manifest.get(SHOULD_NORMALIZE_KEY, False)
55
+
56
+
57
+ def build_source(
58
+ manifest: Mapping[str, Any],
59
+ config: Mapping[str, Any],
60
+ page_limit: Optional[int] = None,
61
+ slice_limit: Optional[int] = None,
62
+ ) -> ManifestDeclarativeSource:
63
+ return ManifestDeclarativeSource(
64
+ source_config=manifest,
65
+ config=config,
66
+ normalize_manifest=should_normalize_manifest(manifest),
67
+ migrate_manifest=should_migrate_manifest(manifest),
68
+ emit_connector_builder_messages=True,
69
+ component_factory=ModelToComponentFactory(
70
+ emit_connector_builder_messages=True,
71
+ limit_pages_fetched_per_slice=page_limit,
72
+ limit_slices_fetched=slice_limit,
73
+ disable_retries=True,
74
+ disable_cache=True,
75
+ ),
76
+ )
@@ -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()