diff --git a/.github/.lambda-python-runtimes b/.github/.lambda-python-runtimes new file mode 100644 index 0000000..c6a9edf --- /dev/null +++ b/.github/.lambda-python-runtimes @@ -0,0 +1,6 @@ +3.9 +3.10 +3.11 +3.12 +3.13 +3.14 diff --git a/.github/workflows/bump-duckdb.yml b/.github/workflows/bump-duckdb.yml index 50362fd..1d24990 100644 --- a/.github/workflows/bump-duckdb.yml +++ b/.github/workflows/bump-duckdb.yml @@ -62,6 +62,16 @@ jobs: if: steps.diff.outputs.changed == 'true' run: echo "${{ steps.pypi.outputs.version }}" > .github/.duckdb-version + - name: Set up Python + if: steps.diff.outputs.changed == 'true' + uses: actions/setup-python@v6 + with: + python-version: "3.x" + + - name: Update PYTHON_VERSIONS + if: steps.diff.outputs.changed == 'true' + run: python scripts/update_python_versions.py + - name: Create pull request if: steps.diff.outputs.changed == 'true' uses: peter-evans/create-pull-request@v8 @@ -75,8 +85,8 @@ jobs: Release notes: https://github.com/duckdb/duckdb/releases/tag/v${{ steps.pypi.outputs.version }} - CI will validate the build matrix (Python × arch). Merge to publish layers. + CI will validate the build matrix (Python x arch). Merge to publish layers. > [!NOTE] - > If `PYTHON_VERSIONS` needs updating (new Lambda runtime or DuckDB dropped/added support), edit `.github/workflows/build-layer.yml` directly on this branch before merging. + > `PYTHON_VERSIONS` has been updated automatically to match DuckDB wheels and Lambda runtimes. Review the diff if Python versions changed. labels: dependencies diff --git a/scripts/update_python_versions.py b/scripts/update_python_versions.py new file mode 100644 index 0000000..cdd66cc --- /dev/null +++ b/scripts/update_python_versions.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +import json +import re +import sys +import urllib.error +import urllib.request +from pathlib import Path + +repo_root = Path(__file__).parent.parent + + +def fetch_duckdb_pythons(version: str) -> set[str]: + url = f"https://pypi.org/pypi/duckdb/{version}/json" + try: + with urllib.request.urlopen(url) as response: + data = json.load(response) + except urllib.error.HTTPError as e: + if e.code == 404: + print(f"Error: DuckDB {version} not found on PyPI.", file=sys.stderr) + else: + print( + f"Error: PyPI returned HTTP {e.code} for DuckDB {version}.", + file=sys.stderr, + ) + sys.exit(1) + pythons = set() + for file in data["urls"]: + filename = file["filename"] + if "manylinux" not in filename: + continue + match = re.search(r"cp3(\d+)", filename) + if match: + pythons.add(f"3.{match.group(1)}") + return pythons + + +def read_lambda_runtimes() -> set[str]: + path = repo_root / ".github" / ".lambda-python-runtimes" + return {line.strip() for line in path.read_text().splitlines() if line.strip()} + + +def read_python_versions() -> set[str]: + path = repo_root / ".github" / "workflows" / "build-layer.yml" + match = re.search(r'PYTHON_VERSIONS:\s*"([^"]+)"', path.read_text()) + if not match: + print("Error: PYTHON_VERSIONS not found in build-layer.yml", file=sys.stderr) + sys.exit(1) + return {v.strip() for v in match.group(1).split(",")} + + +def write_python_versions(versions: set[str]) -> None: + def ver_key(v: str) -> tuple: + return tuple(int(x) for x in v.split(".")) + + formatted = ",".join(sorted(versions, key=ver_key)) + path = repo_root / ".github" / "workflows" / "build-layer.yml" + content = path.read_text() + updated = re.sub(r'(PYTHON_VERSIONS:\s*")[^"]+(")', rf"\g<1>{formatted}\2", content) + path.write_text(updated) + + +def main(): + duckdb_version = (repo_root / ".github" / ".duckdb-version").read_text().strip() + + duckdb_pythons = fetch_duckdb_pythons(duckdb_version) + lambda_runtimes = read_lambda_runtimes() + current = read_python_versions() + expected = duckdb_pythons & lambda_runtimes + + if expected == current: + print(f"PYTHON_VERSIONS already up to date for DuckDB {duckdb_version}.") + return + + def ver_key(v: str) -> tuple: + return tuple(int(x) for x in v.split(".")) + + write_python_versions(expected) + print(f"PYTHON_VERSIONS updated for DuckDB {duckdb_version}:") + for v in sorted(expected - current, key=ver_key): + print(f" + {v}") + for v in sorted(current - expected, key=ver_key): + print(f" - {v}") + + +if __name__ == "__main__": + main()