diff --git a/AGENTS.md b/AGENTS.md index 6c037b99..7ee5276c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,7 +15,7 @@ Do not guess or iterate blindly. Download the artifact first. ## Files You Can Safely Edit - `overlays/python_speakeasy.yaml` — Speakeasy overlay -- `scripts/post_generate.py` — Post-generation patch script +- `scripts/post_generate.uv` — Post-generation patch script (standalone uv script) - `poe_tasks.toml` — Build task definitions - `.github/workflows/` — CI workflows - `.github/dependabot.yml` — Dependabot configuration diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 845e7b08..ae59b4d0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,7 +29,7 @@ The Python SDK is generated through a multi-step pipeline: ▼ ┌──────────────────────────────────────────┐ │ 4. Post-Generation Patches │ -│ (scripts/post_generate.py) │ +│ (scripts/post_generate.uv) │ └──────────────────┬───────────────────────┘ │ ▼ @@ -49,15 +49,15 @@ The Python SDK is generated through a multi-step pipeline: 3. **Speakeasy Code Generation** — Speakeasy consumes the spec (+ overlay if enabled) and generates the Python SDK in `src/airbyte_api/`. These files should never be edited by hand. -4. **Post-Generation Patches** — A Python script applies any SDK-specific patches after generation (currently a no-op placeholder): - [`scripts/post_generate.py`](https://github.com/airbytehq/airbyte-api-python-sdk/blob/main/scripts/post_generate.py) +4. **Post-Generation Patches** — A standalone uv script applies SDK-specific patches after generation (e.g. replacing the hardcoded `__version__` with `importlib.metadata`): + [`scripts/post_generate.uv`](https://github.com/airbytehq/airbyte-api-python-sdk/blob/main/scripts/post_generate.uv) 5. **Package Build & Publish** — The generated SDK is built with `uv build` and published to PyPI via OIDC trusted publishing. > **Tip:** If you need to change SDK behavior, determine which layer is appropriate: > - **API changes** → submit to the [upstream OpenAPI spec](https://github.com/airbytehq/airbyte-platform/blob/main/airbyte-api/server-api/src/main/openapi/api_sdk.yaml) > - **Python SDK-specific schema tweaks** → modify the [overlay](https://github.com/airbytehq/airbyte-api-python-sdk/blob/main/overlays/python_speakeasy.yaml) -> - **Post-generation fixes** → modify the [post-generate script](https://github.com/airbytehq/airbyte-api-python-sdk/blob/main/scripts/post_generate.py) +> - **Post-generation fixes** → modify the [post-generate script](https://github.com/airbytehq/airbyte-api-python-sdk/blob/main/scripts/post_generate.uv) > - Then trigger regeneration (see below) ## For Maintainers diff --git a/poe_tasks.toml b/poe_tasks.toml index b0520fe2..0def9830 100644 --- a/poe_tasks.toml +++ b/poe_tasks.toml @@ -13,7 +13,7 @@ speakeasy run $ARGS [tasks._post-generate] help = "Run post-generation patches." shell = """ -uv run python scripts/post_generate.py +uv run scripts/post_generate.uv """ [tasks._generate-readme] diff --git a/scripts/post_generate.py b/scripts/post_generate.py deleted file mode 100644 index 47788292..00000000 --- a/scripts/post_generate.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Post-generation patch pipeline (no-op placeholder). - -This script runs after Speakeasy code generation to apply any -Python SDK-specific patches. Currently no patches are needed. - -Add patch functions here when durable fixes to generated code are required. -See the terraform-provider-airbyte repo for examples of post-generation patches. -""" - - -def main() -> None: - print("post_generate: no patches to apply (no-op)") - - -if __name__ == "__main__": - main() diff --git a/scripts/post_generate.uv b/scripts/post_generate.uv new file mode 100755 index 00000000..9b1dbbf9 --- /dev/null +++ b/scripts/post_generate.uv @@ -0,0 +1,70 @@ +#!/usr/bin/env -S uv run --python 3.10 --script +# /// script +# requires-python = ">=3.10,<3.14" +# dependencies = [] +# /// +"""Post-generation patch pipeline. + +Runs after Speakeasy code generation to apply durable fixes to generated code. +See the terraform-provider-airbyte repo for more examples of post-generation patches. +""" + +import re +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parent.parent +VERSION_FILE = REPO_ROOT / "src" / "airbyte_api" / "_version.py" + + +def patch_version_file() -> None: + """Replace the hardcoded `__version__` in `_version.py` with a dynamic lookup. + + Speakeasy generates a `_version.py` with a hardcoded `__version__` string + and a static `__user_agent__`. This patch rewrites both so that the + installed package version (set at build time by `uv-dynamic-versioning` + from the git tag) is used instead. Generation metadata + (`__openapi_doc_version__`, `__gen_version__`) is left intact. + """ + text = VERSION_FILE.read_text() + original = text + + # 1. Replace the hardcoded __version__ assignment with importlib.metadata lookup. + # Matches: __version__: str = "1.0.0" (any semver-ish string) + text = re.sub( + r'^__version__: str = ".*"$', + "__version__: str = importlib.metadata.version(__title__)", + text, + count=1, + flags=re.MULTILINE, + ) + + # 2. Replace the static __user_agent__ string with an f-string using the + # dynamic __version__. + # Matches: __user_agent__: str = "speakeasy-sdk/python 1.0.0 2.911.0 1.0.0 airbyte-api" + text = re.sub( + r'^__user_agent__: str = "speakeasy-sdk/python .+"$', + ( + "__user_agent__: str = (\n" + ' f"speakeasy-sdk/python {__version__} {__gen_version__}"\n' + ' f" {__openapi_doc_version__} {__title__}"\n' + ")" + ), + text, + count=1, + flags=re.MULTILINE, + ) + + if text == original: + print("post_generate: _version.py already patched (no changes)") + return + + VERSION_FILE.write_text(text) + print("post_generate: patched _version.py (hardcoded __version__ → importlib.metadata)") + + +def main() -> None: + patch_version_file() + + +if __name__ == "__main__": + main() diff --git a/src/airbyte_api/_version.py b/src/airbyte_api/_version.py index f8a8ca6e..2742580e 100644 --- a/src/airbyte_api/_version.py +++ b/src/airbyte_api/_version.py @@ -3,10 +3,13 @@ import importlib.metadata __title__: str = "airbyte-api" -__version__: str = "1.0.0" +__version__: str = importlib.metadata.version(__title__) __openapi_doc_version__: str = "1.0.0" __gen_version__: str = "2.911.0" -__user_agent__: str = "speakeasy-sdk/python 1.0.0 2.911.0 1.0.0 airbyte-api" +__user_agent__: str = ( + f"speakeasy-sdk/python {__version__} {__gen_version__}" + f" {__openapi_doc_version__} {__title__}" +) try: if __package__ is not None: