mirror of
https://github.com/openai/codex.git
synced 2026-04-28 00:25:56 +00:00
2026-03-07 Stabilize experimental python SDK foundation
Description: - pin datamodel-code-generator to an exact version and invoke it through the active interpreter - make generate_types own the maintained generated surfaces and regenerate committed artifacts - make sdk/python tests hermetic and regeneration checks idempotent
This commit is contained in:
10
sdk/python/tests/conftest.py
Normal file
10
sdk/python/tests/conftest.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SRC = ROOT / "src"
|
||||
|
||||
if str(SRC) not in sys.path:
|
||||
sys.path.insert(0, str(SRC))
|
||||
118
sdk/python/tests/test_artifact_workflow_and_binaries.py
Normal file
118
sdk/python/tests/test_artifact_workflow_and_binaries.py
Normal file
@@ -0,0 +1,118 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import importlib.util
|
||||
import platform
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def _load_update_script_module():
|
||||
script_path = ROOT / "scripts" / "update_sdk_artifacts.py"
|
||||
spec = importlib.util.spec_from_file_location("update_sdk_artifacts", script_path)
|
||||
if spec is None or spec.loader is None:
|
||||
raise AssertionError(f"Failed to load script module: {script_path}")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[spec.name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def test_generation_has_single_maintenance_entrypoint_script() -> None:
|
||||
scripts = sorted(p.name for p in (ROOT / "scripts").glob("*.py"))
|
||||
assert scripts == ["update_sdk_artifacts.py"]
|
||||
|
||||
|
||||
def test_generate_types_wires_all_generation_steps() -> None:
|
||||
source = (ROOT / "scripts" / "update_sdk_artifacts.py").read_text()
|
||||
tree = ast.parse(source)
|
||||
|
||||
generate_types_fn = next(
|
||||
(node for node in tree.body if isinstance(node, ast.FunctionDef) and node.name == "generate_types"),
|
||||
None,
|
||||
)
|
||||
assert generate_types_fn is not None
|
||||
|
||||
calls: list[str] = []
|
||||
for node in generate_types_fn.body:
|
||||
if isinstance(node, ast.Expr) and isinstance(node.value, ast.Call):
|
||||
fn = node.value.func
|
||||
if isinstance(fn, ast.Name):
|
||||
calls.append(fn.id)
|
||||
|
||||
assert calls == [
|
||||
"generate_v2_all",
|
||||
"generate_protocol_types",
|
||||
"generate_schema_types",
|
||||
"generate_notification_registry",
|
||||
"generate_codex_event_types",
|
||||
"generate_public_api_flat_methods",
|
||||
]
|
||||
|
||||
|
||||
def test_bundled_binaries_exist_for_all_supported_platforms() -> None:
|
||||
script = _load_update_script_module()
|
||||
for platform_key in script.PLATFORMS:
|
||||
bin_path = script.bundled_platform_bin_path(platform_key)
|
||||
assert bin_path.is_file(), f"Missing bundled binary: {bin_path}"
|
||||
|
||||
|
||||
def test_default_runtime_uses_current_platform_bundled_binary() -> None:
|
||||
client_source = (ROOT / "src" / "codex_app_server" / "client.py").read_text()
|
||||
client_tree = ast.parse(client_source)
|
||||
|
||||
# Keep this assertion source-level so it works in both PR2 (types foundation)
|
||||
# and PR3 (full SDK), regardless of runtime module wiring.
|
||||
app_server_config = next(
|
||||
(
|
||||
node
|
||||
for node in client_tree.body
|
||||
if isinstance(node, ast.ClassDef) and node.name == "AppServerConfig"
|
||||
),
|
||||
None,
|
||||
)
|
||||
assert app_server_config is not None
|
||||
|
||||
codex_bin_field = next(
|
||||
(
|
||||
node
|
||||
for node in app_server_config.body
|
||||
if isinstance(node, ast.AnnAssign)
|
||||
and isinstance(node.target, ast.Name)
|
||||
and node.target.id == "codex_bin"
|
||||
),
|
||||
None,
|
||||
)
|
||||
assert codex_bin_field is not None
|
||||
assert isinstance(codex_bin_field.value, ast.Call)
|
||||
assert isinstance(codex_bin_field.value.func, ast.Name)
|
||||
assert codex_bin_field.value.func.id == "str"
|
||||
assert len(codex_bin_field.value.args) == 1
|
||||
bundled_call = codex_bin_field.value.args[0]
|
||||
assert isinstance(bundled_call, ast.Call)
|
||||
assert isinstance(bundled_call.func, ast.Name)
|
||||
assert bundled_call.func.id == "_bundled_codex_path"
|
||||
|
||||
bin_root = (ROOT / "src" / "codex_app_server" / "bin").resolve()
|
||||
|
||||
sys_name = platform.system().lower()
|
||||
machine = platform.machine().lower()
|
||||
is_arm = machine in {"arm64", "aarch64"}
|
||||
|
||||
if sys_name.startswith("darwin"):
|
||||
platform_dir = "darwin-arm64" if is_arm else "darwin-x64"
|
||||
exe = "codex"
|
||||
elif sys_name.startswith("linux"):
|
||||
platform_dir = "linux-arm64" if is_arm else "linux-x64"
|
||||
exe = "codex"
|
||||
elif sys_name.startswith("windows"):
|
||||
platform_dir = "windows-arm64" if is_arm else "windows-x64"
|
||||
exe = "codex.exe"
|
||||
else:
|
||||
raise AssertionError(f"Unsupported platform in test: {sys_name}/{machine}")
|
||||
|
||||
expected = (bin_root / platform_dir / exe).resolve()
|
||||
assert expected.is_file()
|
||||
55
sdk/python/tests/test_contract_generation.py
Normal file
55
sdk/python/tests/test_contract_generation.py
Normal file
@@ -0,0 +1,55 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
GENERATED_TARGETS = [
|
||||
Path("src/codex_app_server/generated/schema_types.py"),
|
||||
Path("src/codex_app_server/generated/protocol_types.py"),
|
||||
Path("src/codex_app_server/generated/codex_event_types.py"),
|
||||
Path("src/codex_app_server/generated/notification_registry.py"),
|
||||
Path("src/codex_app_server/generated/v2_all"),
|
||||
Path("src/codex_app_server/public_api.py"),
|
||||
]
|
||||
|
||||
|
||||
def _snapshot_target(root: Path, rel_path: Path) -> dict[str, bytes] | bytes | None:
|
||||
target = root / rel_path
|
||||
if not target.exists():
|
||||
return None
|
||||
if target.is_file():
|
||||
return target.read_bytes()
|
||||
|
||||
snapshot: dict[str, bytes] = {}
|
||||
for path in sorted(target.rglob("*")):
|
||||
if path.is_file() and "__pycache__" not in path.parts:
|
||||
snapshot[str(path.relative_to(target))] = path.read_bytes()
|
||||
return snapshot
|
||||
|
||||
|
||||
def _snapshot_targets(root: Path) -> dict[str, dict[str, bytes] | bytes | None]:
|
||||
return {
|
||||
str(rel_path): _snapshot_target(root, rel_path) for rel_path in GENERATED_TARGETS
|
||||
}
|
||||
|
||||
|
||||
def test_generated_files_are_up_to_date():
|
||||
before = _snapshot_targets(ROOT)
|
||||
|
||||
# Regenerate contract artifacts via single maintenance entrypoint.
|
||||
env = os.environ.copy()
|
||||
python_bin = str(Path(sys.executable).parent)
|
||||
env["PATH"] = f"{python_bin}{os.pathsep}{env.get('PATH', '')}"
|
||||
|
||||
subprocess.run(
|
||||
[sys.executable, "scripts/update_sdk_artifacts.py", "--types-only"],
|
||||
cwd=ROOT,
|
||||
check=True,
|
||||
env=env,
|
||||
)
|
||||
|
||||
after = _snapshot_targets(ROOT)
|
||||
assert before == after, "Generated files drifted after regeneration"
|
||||
Reference in New Issue
Block a user