Skip to content

Commit 4c29c91

Browse files
committed
merge from main
2 parents b122c0a + bf998bd commit 4c29c91

File tree

95 files changed

+5472
-395
lines changed

Some content is hidden

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

95 files changed

+5472
-395
lines changed

.github/workflows/connector-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ jobs:
9696
name: "Check: '${{matrix.connector}}' (skip=${{needs.cdk_changes.outputs['src'] == 'false' || needs.cdk_changes.outputs[matrix.cdk_extra] == 'false'}})"
9797
permissions:
9898
checks: write
99-
contents: write # Required for creating commit statuses
99+
contents: write # Required for creating commit statuses
100100
pull-requests: read
101101
steps:
102102
- name: Abort if extra not changed (${{matrix.cdk_extra}})

.github/workflows/docker-build-check.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77

88
jobs:
99
docker-build-check:
10-
name: SDM Docker Image Build # Renamed job to be more descriptive
10+
name: SDM Docker Image Build # Renamed job to be more descriptive
1111
runs-on: ubuntu-24.04
1212
steps:
1313
- name: Checkout code

.github/workflows/pypi_publish.yml

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# Note: We may want to rename this file at some point. However, if we rename the workflow file name,
66
# we have to also update the Trusted Publisher settings on PyPI.
77

8-
name: Packaging and Publishing
8+
name: CDK Publish
99

1010
on:
1111
push:
@@ -14,7 +14,12 @@ on:
1414
workflow_dispatch:
1515
inputs:
1616
version:
17-
description: "Note that this workflow is intended for prereleases. For public-facing stable releases, please use the GitHub Releases workflow instead: https://github.com/airbytehq/airbyte-python-cdk/blob/main/docs/RELEASES.md. If running this workflow from main or from a dev branch, please enter the desired version number here, for instance 1.2.3dev0 or 1.2.3rc1."
17+
description: >
18+
Note that this workflow is intended for prereleases. For public-facing stable releases,
19+
please use the GitHub Releases workflow instead:
20+
https://github.com/airbytehq/airbyte-python-cdk/blob/main/docs/RELEASES.md.
21+
For prereleases, please leave the version blank to use the detected version. Alternatively,
22+
you can override the dynamic versioning for special use cases.
1823
required: false
1924
publish_to_pypi:
2025
description: "Publish to PyPI. If true, the workflow will publish to PyPI."
@@ -37,17 +42,29 @@ jobs:
3742
name: Build Python Package
3843
runs-on: ubuntu-24.04
3944
steps:
40-
- name: Detect Release Tag Version
45+
- name: Checkout CDK Repo
46+
uses: actions/checkout@v4
47+
with:
48+
fetch-depth: 0
49+
50+
- name: Detect Prerelease Version using Dunamai
51+
uses: mtkennerly/dunamai-action@v1
52+
with:
53+
args: --format "{base}.post{distance}.dev${{ github.run_id }}"
54+
env-var: DETECTED_VERSION
55+
56+
- name: Detect Release Tag Version from git ref ('${{ github.ref_name }}')
4157
if: startsWith(github.ref, 'refs/tags/v')
4258
run: |
59+
echo "Overriding Dunamai detected version: '${{ env.DETECTED_VERSION || 'none' }}'"
60+
# Extract the version from the git ref
4361
DETECTED_VERSION=${{ github.ref_name }}
44-
echo "Version ref set to '${DETECTED_VERSION}'"
4562
# Remove the 'v' prefix if it exists
4663
DETECTED_VERSION="${DETECTED_VERSION#v}"
47-
echo "Setting version to '$DETECTED_VERSION'"
64+
echo "Setting detected version to '$DETECTED_VERSION'"
4865
echo "DETECTED_VERSION=${DETECTED_VERSION}" >> $GITHUB_ENV
4966
50-
- name: Validate and set VERSION from git ref ('${{ github.ref_name }}') and input (${{ github.event.inputs.version || 'none' }})
67+
- name: Validate and set VERSION (detected='${{ env.DETECTED_VERSION }}', input='${{ github.event.inputs.version || 'none' }}')
5168
id: set_version
5269
run: |
5370
INPUT_VERSION=${{ github.event.inputs.version }}
@@ -62,8 +79,8 @@ jobs:
6279
INPUT_VERSION="${INPUT_VERSION#v}"
6380
# Fail if detected version is non-empty and different from the input version
6481
if [ -n "${DETECTED_VERSION:-}" ] && [ -n "${INPUT_VERSION:-}" ] && [ "${DETECTED_VERSION}" != "${INPUT_VERSION}" ]; then
65-
echo "Error: Version input '${INPUT_VERSION}' does not match detected version '${DETECTED_VERSION}'."
66-
exit 1
82+
echo "Warning: Version input '${INPUT_VERSION}' does not match detected version '${DETECTED_VERSION}'."
83+
echo "Using input version '${INPUT_VERSION}' instead."
6784
fi
6885
# Set the version to the input version if non-empty, otherwise the detected version
6986
VERSION="${INPUT_VERSION:-$DETECTED_VERSION}"
@@ -84,10 +101,6 @@ jobs:
84101
echo "IS_PRERELEASE=true" >> $GITHUB_OUTPUT
85102
fi
86103
87-
- uses: actions/checkout@v4
88-
with:
89-
fetch-depth: 0
90-
91104
- uses: hynek/build-and-inspect-python-package@v2
92105
env:
93106
# Pass in the evaluated version from the previous step

.github/workflows/python_dependency_analysis.yml

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,20 @@ jobs:
1919
name: Dependency Analysis with Deptry
2020
runs-on: ubuntu-24.04
2121
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
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
3434

35-
# Job-specific step(s):
36-
- name: Run Deptry
37-
run: |
38-
poetry run deptry .
35+
# Job-specific step(s):
36+
- name: Run Deptry
37+
run: |
38+
poetry run deptry .

.github/workflows/semantic_pr_check.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
Build
4040
tests
4141
Tests
42-
42+
4343
- name: Check for "do not merge" in PR title
4444
if: ${{ github.event.pull_request.draft == false }}
4545
uses: actions/github-script@v6

.github/workflows/slash_command_dispatch.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ jobs:
5151
- \`/test\` - Runs the test suite
5252
- \`/poetry-lock\` - Re-locks dependencies and updates the poetry.lock file
5353
- \`/help\` - Shows this help message"
54-
54+
5555
if [[ "${{ github.event.comment.body }}" == "/help" ]]; then
5656
echo "body=$HELP_TEXT" >> $GITHUB_OUTPUT
5757
else

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ repos:
3636
- id: check-toml
3737

3838
- repo: https://github.com/astral-sh/ruff-pre-commit
39-
rev: v0.8.3
39+
rev: v0.11.5
4040
hooks:
4141
# Run the linter with repo-defined settings
4242
- id: ruff

airbyte_cdk/connector_builder/connector_builder_handler.py

Lines changed: 47 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, ClassVar, Dict, List, Mapping
88

99
from airbyte_cdk.connector_builder.test_reader import TestReader
1010
from airbyte_cdk.models import (
@@ -27,30 +27,36 @@
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:
40+
__test__: ClassVar[bool] = False # Tell Pytest this is not a Pytest class, despite its name
41+
3842
max_records: int = field(default=DEFAULT_MAXIMUM_RECORDS)
3943
max_pages_per_slice: int = field(default=DEFAULT_MAXIMUM_NUMBER_OF_PAGES_PER_SLICE)
4044
max_slices: int = field(default=DEFAULT_MAXIMUM_NUMBER_OF_SLICES)
45+
max_streams: int = field(default=DEFAULT_MAXIMUM_STREAMS)
4146

4247

43-
def get_limits(config: Mapping[str, Any]) -> TestReadLimits:
48+
def get_limits(config: Mapping[str, Any]) -> TestLimits:
4449
command_config = config.get("__test_read_config", {})
4550
max_pages_per_slice = (
4651
command_config.get(MAX_PAGES_PER_SLICE_KEY) or DEFAULT_MAXIMUM_NUMBER_OF_PAGES_PER_SLICE
4752
)
4853
max_slices = command_config.get(MAX_SLICES_KEY) or DEFAULT_MAXIMUM_NUMBER_OF_SLICES
4954
max_records = command_config.get(MAX_RECORDS_KEY) or DEFAULT_MAXIMUM_RECORDS
50-
return TestReadLimits(max_records, max_pages_per_slice, max_slices)
55+
max_streams = command_config.get(MAX_STREAMS_KEY) or DEFAULT_MAXIMUM_STREAMS
56+
return TestLimits(max_records, max_pages_per_slice, max_slices, max_streams)
5157

5258

53-
def create_source(config: Mapping[str, Any], limits: TestReadLimits) -> ManifestDeclarativeSource:
59+
def create_source(config: Mapping[str, Any], limits: TestLimits) -> ManifestDeclarativeSource:
5460
manifest = config["__injected_declarative_manifest"]
5561
return ManifestDeclarativeSource(
5662
config=config,
@@ -71,7 +77,7 @@ def read_stream(
7177
config: Mapping[str, Any],
7278
configured_catalog: ConfiguredAirbyteCatalog,
7379
state: List[AirbyteStateMessage],
74-
limits: TestReadLimits,
80+
limits: TestLimits,
7581
) -> AirbyteMessage:
7682
try:
7783
test_read_handler = TestReader(
@@ -117,5 +123,40 @@ def resolve_manifest(source: ManifestDeclarativeSource) -> AirbyteMessage:
117123
return error.as_airbyte_message()
118124

119125

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

airbyte_cdk/connector_builder/main.py

Lines changed: 8 additions & 5 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,15 +73,17 @@ 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)
7980
elif command == "test_read":
80-
assert (
81-
catalog is not None
82-
), "`test_read` requires a valid `ConfiguredAirbyteCatalog`, got None."
81+
assert catalog is not None, (
82+
"`test_read` requires a valid `ConfiguredAirbyteCatalog`, got None."
83+
)
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/test_reader/reader.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55

66
import logging
7-
from typing import Any, Dict, Iterator, List, Mapping, Optional, Union
7+
from typing import Any, ClassVar, Dict, Iterator, List, Mapping, Optional, Union
88

99
from airbyte_cdk.connector_builder.models import (
1010
AuxiliaryRequest,
@@ -66,6 +66,8 @@ class TestReader:
6666
6767
"""
6868

69+
__test__: ClassVar[bool] = False # Tell Pytest this is not a Pytest class, despite its name
70+
6971
logger = logging.getLogger("airbyte.connector-builder")
7072

7173
def __init__(

0 commit comments

Comments
 (0)