Skip to content

Commit a0f8ed5

Browse files
committed
Merge remote-tracking branch 'origin/main' into aj/feat/mini-cat-test-suites
2 parents 6248994 + 5c32297 commit a0f8ed5

File tree

136 files changed

+9077
-1248
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

136 files changed

+9077
-1248
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: Docker Build Check
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
8+
jobs:
9+
docker-build-check:
10+
name: SDM Docker Image Build # Renamed job to be more descriptive
11+
runs-on: ubuntu-24.04
12+
steps:
13+
- name: Checkout code
14+
uses: actions/checkout@v4
15+
with:
16+
fetch-depth: 0
17+
18+
# Build the Python package to create the wheel files needed for the Docker build
19+
- name: Build Python Package
20+
uses: hynek/build-and-inspect-python-package@v2
21+
env:
22+
POETRY_DYNAMIC_VERSIONING_BYPASS: "0.0.0dev0"
23+
24+
# Copy the wheel files to the dist directory
25+
- name: Copy wheel files to dist directory
26+
run: |
27+
mkdir -p dist
28+
cp /tmp/baipp/dist/*.whl dist/
29+
30+
- name: Set up QEMU for multi-platform builds
31+
uses: docker/setup-qemu-action@v3
32+
33+
- name: Set up Docker Buildx
34+
uses: docker/setup-buildx-action@v3
35+
36+
- name: Build Docker image for multiple platforms
37+
id: docker-build
38+
uses: docker/build-push-action@v5
39+
with:
40+
context: .
41+
platforms: linux/amd64,linux/arm64
42+
push: false
43+
tags: airbyte/source-declarative-manifest:pr-${{ github.event.pull_request.number }}
44+
outputs: type=image,name=target,annotation-index.org.opencontainers.image.description=SDM Docker image for PR ${{ github.event.pull_request.number }}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Dependency Analysis
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
paths:
8+
- "airbyte_cdk/**"
9+
- "poetry.lock"
10+
- "pyproject.toml"
11+
pull_request:
12+
paths:
13+
- "airbyte_cdk/**"
14+
- "poetry.lock"
15+
- "pyproject.toml"
16+
17+
jobs:
18+
dependency-analysis:
19+
name: Dependency Analysis with Deptry
20+
runs-on: ubuntu-24.04
21+
steps:
22+
- name: Checkout code
23+
uses: actions/checkout@v4
24+
- name: Set up Python
25+
uses: actions/setup-python@v5
26+
with:
27+
python-version: '3.10'
28+
- name: Set up Poetry
29+
uses: Gr1N/setup-poetry@v9
30+
with:
31+
poetry-version: "2.0.1"
32+
- name: Install dependencies
33+
run: poetry install --all-extras
34+
35+
# Job-specific step(s):
36+
- name: Run Deptry
37+
run: |
38+
poetry run deptry .

.github/workflows/semantic_pr_check.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,13 @@ jobs:
3939
Build
4040
tests
4141
Tests
42+
43+
- name: Check for "do not merge" in PR title
44+
if: ${{ github.event.pull_request.draft == false }}
45+
uses: actions/github-script@v6
46+
with:
47+
script: |
48+
const title = context.payload.pull_request.title.toLowerCase();
49+
if (title.includes('do not merge') || title.includes('do-not-merge')) {
50+
core.setFailed('PR title contains "do not merge" or "do-not-merge". Please remove this before merging.');
51+
}

Dockerfile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# A new version of source-declarative-manifest is built for every new Airbyte CDK release, and their versions are kept in sync.
66
#
77

8-
FROM docker.io/airbyte/python-connector-base:3.0.0@sha256:1a0845ff2b30eafa793c6eee4e8f4283c2e52e1bbd44eed6cb9e9abd5d34d844
8+
FROM docker.io/airbyte/python-connector-base:4.0.0@sha256:d9894b6895923b379f3006fa251147806919c62b7d9021b5cd125bb67d7bbe22
99

1010
WORKDIR /airbyte/integration_code
1111

@@ -24,8 +24,8 @@ RUN pip install dist/*.whl
2424
RUN mkdir -p source_declarative_manifest \
2525
&& echo 'from source_declarative_manifest.run import run\n\nif __name__ == "__main__":\n run()' > main.py \
2626
&& touch source_declarative_manifest/__init__.py \
27-
&& cp /usr/local/lib/python3.10/site-packages/airbyte_cdk/cli/source_declarative_manifest/_run.py source_declarative_manifest/run.py \
28-
&& cp /usr/local/lib/python3.10/site-packages/airbyte_cdk/cli/source_declarative_manifest/spec.json source_declarative_manifest/
27+
&& cp /usr/local/lib/python3.11/site-packages/airbyte_cdk/cli/source_declarative_manifest/_run.py source_declarative_manifest/run.py \
28+
&& cp /usr/local/lib/python3.11/site-packages/airbyte_cdk/cli/source_declarative_manifest/spec.json source_declarative_manifest/
2929

3030
# Remove unnecessary build files
3131
RUN rm -rf dist/ pyproject.toml poetry.lock README.md

airbyte_cdk/connector_builder/connector_builder_handler.py

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55

66
from dataclasses import asdict, dataclass, field
7-
from typing import Any, List, Mapping
7+
from typing import Any, Dict, List, Mapping
88

99
from airbyte_cdk.connector_builder.test_reader import TestReader
1010
from airbyte_cdk.models import (
@@ -27,30 +27,34 @@
2727
DEFAULT_MAXIMUM_NUMBER_OF_PAGES_PER_SLICE = 5
2828
DEFAULT_MAXIMUM_NUMBER_OF_SLICES = 5
2929
DEFAULT_MAXIMUM_RECORDS = 100
30+
DEFAULT_MAXIMUM_STREAMS = 100
3031

3132
MAX_PAGES_PER_SLICE_KEY = "max_pages_per_slice"
3233
MAX_SLICES_KEY = "max_slices"
3334
MAX_RECORDS_KEY = "max_records"
35+
MAX_STREAMS_KEY = "max_streams"
3436

3537

3638
@dataclass
37-
class TestReadLimits:
39+
class TestLimits:
3840
max_records: int = field(default=DEFAULT_MAXIMUM_RECORDS)
3941
max_pages_per_slice: int = field(default=DEFAULT_MAXIMUM_NUMBER_OF_PAGES_PER_SLICE)
4042
max_slices: int = field(default=DEFAULT_MAXIMUM_NUMBER_OF_SLICES)
43+
max_streams: int = field(default=DEFAULT_MAXIMUM_STREAMS)
4144

4245

43-
def get_limits(config: Mapping[str, Any]) -> TestReadLimits:
46+
def get_limits(config: Mapping[str, Any]) -> TestLimits:
4447
command_config = config.get("__test_read_config", {})
4548
max_pages_per_slice = (
4649
command_config.get(MAX_PAGES_PER_SLICE_KEY) or DEFAULT_MAXIMUM_NUMBER_OF_PAGES_PER_SLICE
4750
)
4851
max_slices = command_config.get(MAX_SLICES_KEY) or DEFAULT_MAXIMUM_NUMBER_OF_SLICES
4952
max_records = command_config.get(MAX_RECORDS_KEY) or DEFAULT_MAXIMUM_RECORDS
50-
return TestReadLimits(max_records, max_pages_per_slice, max_slices)
53+
max_streams = command_config.get(MAX_STREAMS_KEY) or DEFAULT_MAXIMUM_STREAMS
54+
return TestLimits(max_records, max_pages_per_slice, max_slices, max_streams)
5155

5256

53-
def create_source(config: Mapping[str, Any], limits: TestReadLimits) -> ManifestDeclarativeSource:
57+
def create_source(config: Mapping[str, Any], limits: TestLimits) -> ManifestDeclarativeSource:
5458
manifest = config["__injected_declarative_manifest"]
5559
return ManifestDeclarativeSource(
5660
config=config,
@@ -71,7 +75,7 @@ def read_stream(
7175
config: Mapping[str, Any],
7276
configured_catalog: ConfiguredAirbyteCatalog,
7377
state: List[AirbyteStateMessage],
74-
limits: TestReadLimits,
78+
limits: TestLimits,
7579
) -> AirbyteMessage:
7680
try:
7781
test_read_handler = TestReader(
@@ -117,5 +121,40 @@ def resolve_manifest(source: ManifestDeclarativeSource) -> AirbyteMessage:
117121
return error.as_airbyte_message()
118122

119123

124+
def full_resolve_manifest(source: ManifestDeclarativeSource, limits: TestLimits) -> AirbyteMessage:
125+
try:
126+
manifest = {**source.resolved_manifest}
127+
streams = manifest.get("streams", [])
128+
for stream in streams:
129+
stream["dynamic_stream_name"] = None
130+
131+
mapped_streams: Dict[str, List[Dict[str, Any]]] = {}
132+
for stream in source.dynamic_streams:
133+
generated_streams = mapped_streams.setdefault(stream["dynamic_stream_name"], [])
134+
135+
if len(generated_streams) < limits.max_streams:
136+
generated_streams += [stream]
137+
138+
for generated_streams_list in mapped_streams.values():
139+
streams.extend(generated_streams_list)
140+
141+
manifest["streams"] = streams
142+
return AirbyteMessage(
143+
type=Type.RECORD,
144+
record=AirbyteRecordMessage(
145+
data={"manifest": manifest},
146+
emitted_at=_emitted_at(),
147+
stream="full_resolve_manifest",
148+
),
149+
)
150+
except AirbyteTracedException as exc:
151+
return exc.as_airbyte_message()
152+
except Exception as exc:
153+
error = AirbyteTracedException.from_exception(
154+
exc, message=f"Error full resolving manifest: {str(exc)}"
155+
)
156+
return error.as_airbyte_message()
157+
158+
120159
def _emitted_at() -> int:
121160
return ab_datetime_now().to_epoch_millis()

airbyte_cdk/connector_builder/main.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010

1111
from airbyte_cdk.connector import BaseConnector
1212
from airbyte_cdk.connector_builder.connector_builder_handler import (
13-
TestReadLimits,
13+
TestLimits,
1414
create_source,
15+
full_resolve_manifest,
1516
get_limits,
1617
read_stream,
1718
resolve_manifest,
@@ -72,7 +73,7 @@ def handle_connector_builder_request(
7273
config: Mapping[str, Any],
7374
catalog: Optional[ConfiguredAirbyteCatalog],
7475
state: List[AirbyteStateMessage],
75-
limits: TestReadLimits,
76+
limits: TestLimits,
7677
) -> AirbyteMessage:
7778
if command == "resolve_manifest":
7879
return resolve_manifest(source)
@@ -81,6 +82,8 @@ def handle_connector_builder_request(
8182
catalog is not None
8283
), "`test_read` requires a valid `ConfiguredAirbyteCatalog`, got None."
8384
return read_stream(source, config, catalog, state, limits)
85+
elif command == "full_resolve_manifest":
86+
return full_resolve_manifest(source, limits)
8487
else:
8588
raise ValueError(f"Unrecognized command {command}.")
8689

airbyte_cdk/connector_builder/models.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,6 @@ class HttpRequest:
2121
body: Optional[str] = None
2222

2323

24-
@dataclass
25-
class StreamReadPages:
26-
records: List[object]
27-
request: Optional[HttpRequest] = None
28-
response: Optional[HttpResponse] = None
29-
30-
31-
@dataclass
32-
class StreamReadSlices:
33-
pages: List[StreamReadPages]
34-
slice_descriptor: Optional[Dict[str, Any]]
35-
state: Optional[List[Dict[str, Any]]] = None
36-
37-
3824
@dataclass
3925
class LogMessage:
4026
message: str
@@ -46,11 +32,27 @@ class LogMessage:
4632
@dataclass
4733
class AuxiliaryRequest:
4834
title: str
35+
type: str
4936
description: str
5037
request: HttpRequest
5138
response: HttpResponse
5239

5340

41+
@dataclass
42+
class StreamReadPages:
43+
records: List[object]
44+
request: Optional[HttpRequest] = None
45+
response: Optional[HttpResponse] = None
46+
47+
48+
@dataclass
49+
class StreamReadSlices:
50+
pages: List[StreamReadPages]
51+
slice_descriptor: Optional[Dict[str, Any]]
52+
state: Optional[List[Dict[str, Any]]] = None
53+
auxiliary_requests: Optional[List[AuxiliaryRequest]] = None
54+
55+
5456
@dataclass
5557
class StreamRead(object):
5658
logs: List[LogMessage]

0 commit comments

Comments
 (0)