Skip to content

Commit 4607bdd

Browse files
feat!: migrate to Speakeasy Python v2 generator and modernize packaging
- Upgrade gen.yaml to v2 generator (templateVersion: v2, version: 1.0.0) - Update .speakeasy/workflow.yaml to use pinned CLI version - Add .github/speakeasy/dummy-compose.yml for Speakeasy CLI version pinning - Add overlays/python_speakeasy.yaml (no-op placeholder for future SDK-specific tweaks) - Add scripts/post_generate.py (no-op placeholder for future post-gen patches) - Add poe_tasks.toml with generation, lint, test, and typecheck tasks - Add generate-command.yml workflow (replaces opaque Speakeasy reusable workflow) - Add slash-command-dispatch.yml for /generate support - Remove legacy files: setup.py, RELEASES.md, pylintrc, scripts/publish.sh, .genignore - Remove legacy workflows: speakeasy_sdk_generation.yml, speakeasy_sdk_publish.yml - Remove stale airbyte-api.openapi.yaml (60k line spec copy) - Update .gitignore for modern Python project structure BREAKING CHANGE: SDK is now generated with Speakeasy Python v2 generator. The v2 generator produces pyproject.toml-based packaging, uses httpx instead of requests, and Pydantic models instead of dataclasses-json. This is a major version bump to 1.0.0. Co-Authored-By: AJ Steers <aj@airbyte.io>
1 parent 9f75c79 commit 4607bdd

17 files changed

Lines changed: 408 additions & 62498 deletions

.genignore

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Speakeasy CLI version pin. Bumped by Dependabot's `docker-compose`
2+
# ecosystem; never invoked via `docker compose` (hence `dummy-`).
3+
# Consumed by `.github/workflows/generate-command.yml`, which pulls the
4+
# image and copies `/usr/local/bin/speakeasy` onto the runner.
5+
6+
services:
7+
speakeasy:
8+
image: ghcr.io/speakeasy-api/speakeasy:v1.784.0
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
# Speakeasy SDK Generation Workflow
2+
#
3+
# This workflow regenerates the Python SDK code using Speakeasy.
4+
# It can create a new PR, update an existing PR branch, or run in dry-run mode for validation.
5+
#
6+
# Triggers:
7+
# - On push to main: Auto-generates after every merge to ensure SDK stays up-to-date (auto-merge enabled)
8+
# - Daily schedule (6 AM UTC): Catches upstream API spec changes (auto-merge enabled)
9+
# - Manual workflow_dispatch: For on-demand generation
10+
# - Slash command (/generate): Regenerates and pushes results back to the PR branch
11+
# - workflow_call: For validation from other workflows (e.g., PR checks)
12+
#
13+
# Generation Process:
14+
# 1. Install Speakeasy CLI from pinned Docker image
15+
# 2. Run Speakeasy to generate the Python SDK code
16+
# 3. Run post-generation patches (currently no-op)
17+
# 4. (If PR context) Commit and push regenerated code back to the PR branch
18+
# 5. (If no PR context and not dry_run) Create a new PR with the regenerated code
19+
# 6. (If dry_run) Verify the generated code is valid
20+
#
21+
# How to use:
22+
# - From a PR: Comment `/generate` to regenerate and push to the PR branch
23+
# - From Actions: Go to Actions > Generate > Run workflow (creates a new PR)
24+
# - Optionally check "Dry run" to validate generation without committing
25+
26+
name: Generate SDK
27+
28+
"on":
29+
push:
30+
branches:
31+
- main
32+
schedule:
33+
- cron: '0 6 * * *'
34+
workflow_dispatch:
35+
inputs:
36+
dry_run:
37+
description: Validate generation without creating a PR
38+
type: boolean
39+
default: false
40+
pr:
41+
description: 'PR number (if set, pushes results to the PR branch instead of creating a new PR)'
42+
type: string
43+
required: false
44+
comment-id:
45+
description: 'Comment ID (for slash command triggers)'
46+
type: string
47+
required: false
48+
workflow_call:
49+
inputs:
50+
dry_run:
51+
description: Validate generation without creating a PR
52+
type: boolean
53+
default: false
54+
55+
concurrency:
56+
group: ${{ (github.event_name == 'push' || github.event_name == 'schedule') && 'generate-new-pr' || format('generate-{0}', github.run_id) }}
57+
cancel-in-progress: true
58+
59+
jobs:
60+
generate:
61+
name: Generate SDK
62+
runs-on: ubuntu-latest
63+
timeout-minutes: 30
64+
permissions:
65+
contents: write
66+
pull-requests: write
67+
steps:
68+
- name: Authenticate as GitHub App
69+
if: ${{ !inputs.dry_run && github.event.inputs.pr != '' }}
70+
uses: actions/create-github-app-token@v3
71+
id: get-app-token
72+
with:
73+
app-id: ${{ secrets.OCTAVIA_BOT_APP_ID }}
74+
private-key: ${{ secrets.OCTAVIA_BOT_PRIVATE_KEY }}
75+
76+
- name: Post or append starting comment
77+
if: ${{ !inputs.dry_run && github.event.inputs.pr != '' }}
78+
id: start-comment
79+
uses: peter-evans/create-or-update-comment@v5
80+
with:
81+
token: ${{ steps.get-app-token.outputs.token }}
82+
issue-number: ${{ github.event.inputs.pr }}
83+
comment-id: ${{ github.event.inputs.comment-id || '' }}
84+
body: |
85+
> **Generate SDK Job Info**
86+
>
87+
> Running Speakeasy SDK generation.
88+
89+
> Job started... [Check job output.](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
90+
91+
- name: Resolve PR head branch
92+
if: ${{ !inputs.dry_run && github.event.inputs.pr != '' }}
93+
id: pr-branch
94+
env:
95+
GH_TOKEN: ${{ steps.get-app-token.outputs.token }}
96+
PR_NUMBER: ${{ github.event.inputs.pr }}
97+
run: |
98+
PR_JSON=$(gh api repos/${{ github.repository }}/pulls/${PR_NUMBER})
99+
HEAD_REF=$(echo "$PR_JSON" | jq -r '.head.ref')
100+
IS_FORK=$(echo "$PR_JSON" | jq -r '.head.repo.fork')
101+
if [ "$IS_FORK" = "true" ]; then
102+
echo "::error::Cannot run /generate on fork PRs. Please regenerate locally."
103+
exit 1
104+
fi
105+
echo "head_ref=${HEAD_REF}" >> $GITHUB_OUTPUT
106+
107+
- name: Checkout repository
108+
uses: actions/checkout@v4
109+
with:
110+
fetch-depth: 0
111+
ref: ${{ steps.pr-branch.outputs.head_ref || '' }}
112+
token: ${{ steps.get-app-token.outputs.token || github.token }}
113+
114+
- name: Install uv
115+
uses: astral-sh/setup-uv@v5
116+
117+
- name: Set up Python
118+
uses: actions/setup-python@v5
119+
with:
120+
python-version: '3.12'
121+
122+
- name: Get next version from release drafter
123+
id: get-version
124+
uses: aaronsteers/semantic-pr-release-drafter@v1.1.0
125+
with:
126+
dry-run: true
127+
env:
128+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
129+
130+
- name: Install Speakeasy CLI
131+
run: |
132+
SPEAKEASY_IMAGE=$(yq '.services.speakeasy.image' .github/speakeasy/dummy-compose.yml)
133+
echo "Pinned Speakeasy image: $SPEAKEASY_IMAGE"
134+
docker pull "$SPEAKEASY_IMAGE"
135+
CONTAINER_ID=$(docker create "$SPEAKEASY_IMAGE")
136+
sudo docker cp "$CONTAINER_ID:/usr/local/bin/speakeasy" /usr/local/bin/speakeasy
137+
docker rm "$CONTAINER_ID" >/dev/null
138+
speakeasy --version
139+
140+
- name: Generate SDK with Speakeasy
141+
env:
142+
SPEAKEASY_API_KEY: ${{ secrets.SPEAKEASY_API_KEY }}
143+
VERSION: ${{ steps.get-version.outputs.resolved-version }}
144+
run: |
145+
if [ -z "$VERSION" ]; then
146+
echo "::error::Version resolution returned empty. Cannot proceed without an explicit version."
147+
exit 1
148+
fi
149+
echo "Using version from release drafter: $VERSION"
150+
uvx --from=poethepoet poe generate-code
151+
152+
- name: Post-generation patching
153+
run: python3 scripts/post_generate.py
154+
155+
- name: Verify generated code
156+
run: |
157+
if [ -f "pyproject.toml" ]; then
158+
echo "pyproject.toml found (v2 generator confirmed)"
159+
uv sync --no-install-project 2>/dev/null || true
160+
uv run python -c "import airbyte_api; print(f'SDK import OK: {airbyte_api.__name__}')" || echo "::warning::SDK import check failed"
161+
else
162+
echo "::warning::No pyproject.toml found. Generation may not have produced v2 output."
163+
fi
164+
165+
- name: Upload generated SDK as artifact
166+
if: ${{ inputs.dry_run }}
167+
uses: actions/upload-artifact@v4
168+
with:
169+
name: generated_sdk_code
170+
path: |
171+
src/
172+
pyproject.toml
173+
py.typed
174+
retention-days: 7
175+
176+
- name: Generation Summary
177+
run: |
178+
echo "=== Generation Summary ==="
179+
echo "Source files: $(find src/ -name '*.py' 2>/dev/null | wc -l)"
180+
echo "Model files: $(find src/ -path '*/models/*' -name '*.py' 2>/dev/null | wc -l)"
181+
if [ -f "pyproject.toml" ]; then
182+
echo "Package version: $(grep 'version' pyproject.toml | head -1)"
183+
fi
184+
185+
- name: Check for changes
186+
if: ${{ !inputs.dry_run }}
187+
id: changes
188+
run: |
189+
if [ -n "$(git status --porcelain)" ]; then
190+
echo "has_changes=true" >> $GITHUB_OUTPUT
191+
else
192+
echo "has_changes=false" >> $GITHUB_OUTPUT
193+
fi
194+
195+
# --- PR branch mode: commit and push to the existing PR branch ---
196+
- name: Push regenerated code to PR branch
197+
if: ${{ !inputs.dry_run && github.event.inputs.pr != '' && steps.changes.outputs.has_changes == 'true' }}
198+
run: |
199+
git config user.name "octavia-bot[bot]"
200+
git config user.email "octavia-bot[bot]@users.noreply.github.com"
201+
git add -A
202+
git commit -m "chore: regenerate SDK with Speakeasy"
203+
git push
204+
205+
# --- New PR mode: create a PR to main ---
206+
- name: Authenticate as GitHub App for PR creation
207+
if: ${{ !inputs.dry_run && steps.changes.outputs.has_changes == 'true' && github.event.inputs.pr == '' }}
208+
uses: actions/create-github-app-token@v3
209+
id: get-pr-token
210+
with:
211+
app-id: ${{ secrets.OCTAVIA_BOT_APP_ID }}
212+
private-key: ${{ secrets.OCTAVIA_BOT_PRIVATE_KEY }}
213+
214+
- name: Create Pull Request
215+
if: ${{ !inputs.dry_run && steps.changes.outputs.has_changes == 'true' && github.event.inputs.pr == '' }}
216+
id: create-pr
217+
uses: peter-evans/create-pull-request@v6
218+
with:
219+
token: ${{ steps.get-pr-token.outputs.token }}
220+
commit-message: "chore: regenerate SDK with Speakeasy"
221+
title: "chore: regenerate SDK with Speakeasy"
222+
body: |
223+
This PR was automatically generated by the Speakeasy SDK generation workflow.
224+
225+
Please review the changes and merge if they look correct.
226+
branch: speakeasy-sdk-regen
227+
base: main
228+
delete-branch: true
229+
230+
- name: Enable auto-merge (new PR only)
231+
if: |
232+
(github.event_name == 'push'
233+
|| github.event_name == 'schedule'
234+
) && steps.create-pr.outputs.pull-request-operation == 'created'
235+
env:
236+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
237+
run: gh pr merge ${{ steps.create-pr.outputs.pull-request-number }} --auto --squash
238+
239+
- name: Append success comment
240+
if: ${{ success() && !inputs.dry_run && github.event.inputs.pr != '' }}
241+
uses: peter-evans/create-or-update-comment@v5
242+
with:
243+
token: ${{ steps.get-app-token.outputs.token }}
244+
comment-id: ${{ steps.start-comment.outputs.comment-id }}
245+
reactions: hooray
246+
body: |
247+
> SDK generation completed successfully.
248+
249+
- name: Append failure comment
250+
if: ${{ failure() && !inputs.dry_run && github.event.inputs.pr != '' }}
251+
uses: peter-evans/create-or-update-comment@v5
252+
with:
253+
token: ${{ steps.get-app-token.outputs.token }}
254+
comment-id: ${{ steps.start-comment.outputs.comment-id }}
255+
reactions: confused
256+
body: |
257+
> SDK generation failed. Check the [job output](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: Slash Command Dispatch
2+
3+
on:
4+
issue_comment:
5+
types: [created]
6+
7+
permissions:
8+
contents: read
9+
issues: write
10+
pull-requests: write
11+
actions: write
12+
13+
jobs:
14+
slash-command-dispatch:
15+
name: Slash Command Dispatch
16+
# Only allow slash commands on pull requests (not on issues)
17+
if: ${{ github.event.issue.pull_request }}
18+
runs-on: ubuntu-latest
19+
steps:
20+
- name: Authenticate as GitHub App
21+
uses: actions/create-github-app-token@v3
22+
id: get-app-token
23+
with:
24+
app-id: ${{ secrets.OCTAVIA_BOT_APP_ID }}
25+
private-key: ${{ secrets.OCTAVIA_BOT_PRIVATE_KEY }}
26+
27+
- name: Slash Command Dispatch
28+
id: dispatch
29+
uses: peter-evans/slash-command-dispatch@9bdcd7914ec1b75590b790b844aa3b8eee7c683a # v5.0.2
30+
with:
31+
repository: ${{ github.repository }}
32+
token: ${{ steps.get-app-token.outputs.token }}
33+
dispatch-type: workflow
34+
issue-type: pull-request
35+
commands: |
36+
generate
37+
static-args: |
38+
pr=${{ github.event.issue.number }}
39+
comment-id=${{ github.event.comment.id }}
40+
# Only run for users with 'write' permission on the main repository
41+
permission: write
42+
43+
- name: Edit comment with error message
44+
if: steps.dispatch.outputs.error-message
45+
uses: peter-evans/create-or-update-comment@v5
46+
with:
47+
comment-id: ${{ github.event.comment.id }}
48+
body: |
49+
> Error: ${{ steps.dispatch.outputs.error-message }}

.github/workflows/speakeasy_sdk_generation.yml

Lines changed: 0 additions & 26 deletions
This file was deleted.

.github/workflows/speakeasy_sdk_publish.yml

Lines changed: 0 additions & 14 deletions
This file was deleted.

.gitignore

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
.python-version
22
.DS_Store
33
venv/
4+
.venv/
45
src/*.egg-info/
56
__pycache__/
67
.pytest_cache/
7-
.python-version`
88
.env
9+
dist/
10+
build/
11+
*.egg-info/
12+
13+
# Generated OpenAPI spec (fetched fresh each generation run)
14+
*.openapi.yaml

0 commit comments

Comments
 (0)