from __future__ import annotations import argparse import shutil import subprocess from dataclasses import dataclass from pathlib import Path ROOT = Path(__file__).resolve().parent.parent GENERATED_ROOT = ROOT / "ebay_client" / "generated" CODEGEN_PYTHON = ROOT / ".venv_codegen" / "Scripts" / "python.exe" @dataclass(frozen=True) class ApiSpec: name: str spec_path: Path output_path: Path API_SPECS = { "media": ApiSpec( name="media", spec_path=ROOT / "commerce_media_v1_beta_oas3.yaml", output_path=GENERATED_ROOT / "media", ), "notification": ApiSpec( name="notification", spec_path=ROOT / "commerce_notification_v1_oas3.yaml", output_path=GENERATED_ROOT / "notification", ), "inventory": ApiSpec( name="inventory", spec_path=ROOT / "sell_inventory_v1_oas3.yaml", output_path=GENERATED_ROOT / "inventory", ), "fulfillment": ApiSpec( name="fulfillment", spec_path=ROOT / "sell_fulfillment_v1_oas3.yaml", output_path=GENERATED_ROOT / "fulfillment", ), "account": ApiSpec( name="account", spec_path=ROOT / "sell_account_v1_oas3.yaml", output_path=GENERATED_ROOT / "account", ), "feed": ApiSpec( name="feed", spec_path=ROOT / "sell_feed_v1_oas3.yaml", output_path=GENERATED_ROOT / "feed", ), } def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="Generate isolated low-level clients from the eBay OpenAPI contracts.") parser.add_argument("--api", choices=sorted(API_SPECS), help="Generate only one API package.") parser.add_argument("--fail-on-warning", action="store_true", help="Fail if the generator emits warnings.") return parser.parse_args() def run_generation(spec: ApiSpec, *, fail_on_warning: bool) -> None: if not spec.spec_path.exists(): raise FileNotFoundError(f"OpenAPI spec not found: {spec.spec_path}") if not CODEGEN_PYTHON.exists(): raise FileNotFoundError( "Code generation interpreter not found. Create .venv_codegen with a supported Python version first." ) if spec.output_path.exists(): shutil.rmtree(spec.output_path) spec.output_path.mkdir(parents=True, exist_ok=True) command = [ str(CODEGEN_PYTHON), "-m", "datamodel_code_generator", "--input", str(spec.spec_path), "--input-file-type", "openapi", "--output", str(spec.output_path / "models.py"), "--output-model-type", "pydantic_v2.BaseModel", "--target-python-version", "3.11", "--reuse-model", "--use-schema-description", "--field-constraints", "--use-double-quotes", ] if fail_on_warning: command.append("--disable-warnings") subprocess.run(command, check=True, cwd=str(ROOT)) normalize_generated_module(spec.output_path / "models.py") (spec.output_path / "__init__.py").write_text( '"""Generated Pydantic models from the OpenAPI contract."""\n\nfrom .models import *\n', encoding="utf-8", ) def normalize_generated_module(file_path: Path) -> None: raw_bytes = file_path.read_bytes() try: content = raw_bytes.decode("utf-8") except UnicodeDecodeError: content = raw_bytes.decode("cp1252") content = content.replace("\u00a0", " ") file_path.write_text(content, encoding="utf-8") def main() -> int: args = parse_args() specs = [API_SPECS[args.api]] if args.api else [API_SPECS[name] for name in sorted(API_SPECS)] for spec in specs: print(f"Generating {spec.name} from {spec.spec_path.name} -> {spec.output_path}") run_generation(spec, fail_on_warning=args.fail_on_warning) return 0 if __name__ == "__main__": raise SystemExit(main())