126 lines
3.8 KiB
Python
126 lines
3.8 KiB
Python
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())
|