diff --git a/.gitignore b/.gitignore index 9e274a9..b4c5921 100644 --- a/.gitignore +++ b/.gitignore @@ -65,5 +65,5 @@ target/ # Ipython Notebook .ipynb_checkpoints -# Build artifacts +# OpenAPI bundle output (generated by make bundle-specs) api/bundled/ diff --git a/Makefile b/Makefile index 13b6269..706acd7 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,39 @@ -VERSION = 1.2.1 +# Python package version. Override on generate: +# make generate-client VERSION=1.3.0 +VERSION ?= 1.2.1 GENERATOR_VERSION = 7.17.0 -COMMON_PROPS = packageName=flightctl,useOneOfDiscriminatorLookup=true,packageVersion=$(VERSION) +COMMON_PROPS = packageName=flightctl,useOneOfDiscriminatorLookup=true,packageVersion=$(VERSION),removeEnumValuePrefix=false GIT_USER = flightctl GIT_REPO = python-client +# Required: flightctl/flightctl release tag. +# Example: make fetch-specs FLIGHTCTL_VERSION=v1.3.0 +FLIGHTCTL_REPO = https://raw.githubusercontent.com/flightctl/flightctl +FLIGHTCTL_VERSION ?= +SPEC_FILES = \ + api/core/v1beta1/openapi.yaml \ + api/core/v1alpha1/openapi.yaml \ + api/imagebuilder/v1alpha1/openapi.yaml -.PHONY: generate-client generate-core generate-v1alpha1 generate-imagebuilder bundle-specs +.PHONY: generate-client generate-core generate-v1alpha1 generate-imagebuilder bundle-specs fetch-specs fix-enum-number-prefix + +# Fetch OpenAPI specs from a specific flightctl/flightctl version +fetch-specs: + @if [ -z "$(FLIGHTCTL_VERSION)" ]; then \ + echo "FLIGHTCTL_VERSION is required (flightctl/flightctl release tag)."; \ + echo "Example: make fetch-specs FLIGHTCTL_VERSION=v1.3.0"; \ + exit 1; \ + fi + @for f in $(SPEC_FILES); do \ + echo "Fetching $$f from flightctl/flightctl@$(FLIGHTCTL_VERSION)"; \ + mkdir -p $$(dirname $$f); \ + curl -fsSL "$(FLIGHTCTL_REPO)/$(FLIGHTCTL_VERSION)/$$f" -o $$f; \ + done + @echo "Fetched specs from flightctl/flightctl@$(FLIGHTCTL_VERSION)" + +# Rewrite Enum.NUMBER_PascalCase -> Enum.PascalCase in generated Python. +# Spec defaults stay intact so wire values match the Go server. +fix-enum-number-prefix: + python3 scripts/fix-enum-number-prefix.py # Bundle specs (resolve $refs into self-contained files) bundle-specs: @@ -24,6 +53,7 @@ generate-core: bundle-specs --additional-properties=$(COMMON_PROPS) \ --git-user-id $(GIT_USER) \ --git-repo-id $(GIT_REPO) + $(MAKE) fix-enum-number-prefix # Core v1alpha1 generate-v1alpha1: bundle-specs @@ -33,9 +63,10 @@ generate-v1alpha1: bundle-specs -i api/bundled/core-v1alpha1.yaml \ -o . \ --skip-validate-spec \ - --additional-properties=packageName=flightctl.v1alpha1,useOneOfDiscriminatorLookup=true,generateSourceCodeOnly=true,packageVersion=$(VERSION) \ + --additional-properties=packageName=flightctl.v1alpha1,useOneOfDiscriminatorLookup=true,generateSourceCodeOnly=true,packageVersion=$(VERSION),removeEnumValuePrefix=false \ --git-user-id $(GIT_USER) \ --git-repo-id $(GIT_REPO) + $(MAKE) fix-enum-number-prefix # ImageBuilder v1alpha1 generate-imagebuilder: bundle-specs @@ -45,8 +76,11 @@ generate-imagebuilder: bundle-specs -i api/bundled/imagebuilder-v1alpha1.yaml \ -o . \ --skip-validate-spec \ - --additional-properties=packageName=flightctl.imagebuilder,useOneOfDiscriminatorLookup=true,generateSourceCodeOnly=true,packageVersion=$(VERSION) \ + --additional-properties=packageName=flightctl.imagebuilder,useOneOfDiscriminatorLookup=true,generateSourceCodeOnly=true,packageVersion=$(VERSION),removeEnumValuePrefix=false \ --git-user-id $(GIT_USER) \ --git-repo-id $(GIT_REPO) + $(MAKE) fix-enum-number-prefix -generate-client: generate-core generate-v1alpha1 generate-imagebuilder \ No newline at end of file +generate-client: + @echo "Generating Python client package Version=$(VERSION)" + $(MAKE) VERSION=$(VERSION) generate-core generate-v1alpha1 generate-imagebuilder diff --git a/scripts/fix-enum-number-prefix.py b/scripts/fix-enum-number-prefix.py new file mode 100644 index 0000000..9476545 --- /dev/null +++ b/scripts/fix-enum-number-prefix.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Rewrite broken OpenAPI Generator Python enum default references. + +The Python generator honors x-enum-varnames on the Enum class +(PullIfNotPresent = 'IfNotPresent') but emits Field defaults as +ImagePullPolicy.NUMBER_PullIfNotPresent. That crashes at import time. + +This keeps the OpenAPI spec (and its defaults) intact so the client +serializes the same wire values the Go server expects, and only patches +the generated Python after codegen. + +Skips integer-style names (NUMBER_1, NUMBER_MINUS_1) by requiring a +PascalCase identifier with at least one lowercase letter. +""" + +from __future__ import annotations + +import re +import sys +from pathlib import Path + +ROOTS = ("flightctl", "test") +PATTERN = re.compile( + r"\.(NUMBER_)([A-Z][A-Za-z0-9]*[a-z][A-Za-z0-9]*)" +) + + +def fix_text(text: str) -> tuple[str, int]: + count = 0 + + def repl(match: re.Match[str]) -> str: + nonlocal count + count += 1 + return f".{match.group(2)}" + + return PATTERN.subn(repl, text)[0], count + + +def main() -> int: + repo = Path(__file__).resolve().parent.parent + files_changed = 0 + replacements = 0 + for root_name in ROOTS: + root = repo / root_name + if not root.is_dir(): + continue + for path in root.rglob("*.py"): + original = path.read_text(encoding="utf-8") + updated, count = fix_text(original) + if count: + path.write_text(updated, encoding="utf-8") + files_changed += 1 + replacements += count + print( + f"Rewrote {replacements} NUMBER_ enum reference(s) in {files_changed} file(s)" + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main())