Files
openai--openai-agents-python/.github/scripts/update_released_api_contract.py
T
2026-08-23 03:39:56 +00:00

137 lines
4.5 KiB
Python

from __future__ import annotations
import argparse
import json
import re
import subprocess
import sys
from pathlib import Path
if sys.version_info >= (3, 11):
import tomllib
else:
import tomli as tomllib
ROOT = Path(__file__).resolve().parents[2]
CONTRACT = ROOT / "tests" / "fixtures" / "released_api_contract.json"
POLICY = ROOT / "tests" / "fixtures" / "released_api_contract_policy.json"
sys.path.insert(0, str(ROOT))
from integration_tests._contract_support import ( # noqa: E402
build_released_api_contract,
load_api_contract,
load_submodule_export_policy,
)
def _parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Update the rolling released public API contract from the local checkout."
)
parser.add_argument("--version", required=True, help="Release version without a leading v.")
parser.add_argument(
"--check",
action="store_true",
help="Fail instead of writing when the committed contract is out of date.",
)
parser.add_argument(
"--output",
type=Path,
help="Write a prospective contract to this path instead of changing the released fixture.",
)
return parser.parse_args()
def _project_version() -> str:
data = tomllib.loads((ROOT / "pyproject.toml").read_text(encoding="utf-8"))
version = data.get("project", {}).get("version")
if not isinstance(version, str):
raise RuntimeError("pyproject.toml is missing project.version")
return version
def _head_commit() -> str:
return subprocess.check_output(
["git", "rev-parse", "HEAD"],
cwd=ROOT,
text=True,
).strip()
def _render(contract: dict[str, object]) -> str:
return json.dumps(contract, indent=2, sort_keys=True) + "\n"
def main() -> int:
args = _parse_args()
if args.check and args.output is not None:
raise SystemExit("--check and --output cannot be used together")
version = args.version
if (
version.startswith("v")
or re.fullmatch(r"\d+\.\d+(?:\.\d+)*(?:[A-Za-z0-9.-]+)?", version) is None
):
raise SystemExit("--version must be a semver-like value without a leading v")
project_version = _project_version()
if project_version != version:
raise SystemExit(
f"--version {version!r} does not match pyproject.toml version {project_version!r}"
)
output = args.output.resolve() if args.output is not None else None
protected_outputs = {CONTRACT.resolve(), POLICY.resolve()}
if output in protected_outputs:
raise SystemExit(
"--output must not overwrite released API contract inputs: "
"tests/fixtures/released_api_contract.json or "
"tests/fixtures/released_api_contract_policy.json"
)
current = load_api_contract(CONTRACT)
policy = load_submodule_export_policy(POLICY)
try:
updated = build_released_api_contract(
current,
baseline=f"v{version}",
baseline_commit=_head_commit(),
release_policy=policy,
)
except ValueError as error:
raise SystemExit(str(error)) from None
rendered = _render(updated)
if output is not None:
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(rendered, encoding="utf-8")
print(f"Wrote prospective released API contract to {output}.")
return 0
existing = CONTRACT.read_text(encoding="utf-8")
if rendered == existing:
print(f"Released API contract is current for v{version}.")
return 0
if args.check:
print(
f"Released API contract is out of date for v{version}; "
f"run `make update-released-api-contract VERSION={version}`.",
file=sys.stderr,
)
return 1
previous_exports = set(current["required_top_level_exports"])
current_exports = set(updated["required_top_level_exports"])
CONTRACT.write_text(rendered, encoding="utf-8")
print(f"Updated released API contract for v{version}.")
print(f"Added exports: {sorted(current_exports - previous_exports)!r}")
print(f"Removed exports: {sorted(previous_exports - current_exports)!r}")
print(
"Review shipped example imports and update released_api_contract_policy.json when "
"the release adds canonical imports, public properties, public type aliases, public "
"TypedDict fields, or public modules."
)
return 0
if __name__ == "__main__":
raise SystemExit(main())