😏
This commit is contained in:
Executable
+613
@@ -0,0 +1,613 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate the bundled route/model catalog and upstream inventory.
|
||||
|
||||
The inventories are intentionally explicit and reviewable. `codex-mmo catalog
|
||||
verify --remote` compares dynamic provider listings against this baseline; it
|
||||
does not silently add models whose protocol and capabilities are unknown.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / "libexec"))
|
||||
from mmo_inventory_snapshot import load_inventory_snapshots # noqa: E402
|
||||
from mmo_profiles import validate_catalog_data # noqa: E402
|
||||
from mmo_util import atomic_write_json, atomic_write_text, toml_dumps # noqa: E402
|
||||
from mmo_version import MMO_SCHEMA_VERSION # noqa: E402
|
||||
|
||||
INVENTORY_SNAPSHOT_ROOT = ROOT / "config" / "inventory-snapshots"
|
||||
|
||||
|
||||
def route(**values: Any) -> dict[str, Any]:
|
||||
return values
|
||||
|
||||
|
||||
def model(
|
||||
route_key: str,
|
||||
upstream_id: str,
|
||||
maker: str,
|
||||
display_name: str,
|
||||
*,
|
||||
description: str,
|
||||
context: int,
|
||||
output: int | None = None,
|
||||
reasoning: list[str] | None = None,
|
||||
default_reasoning: str | None = None,
|
||||
modalities: list[str] | None = None,
|
||||
output_modalities: list[str] | None = None,
|
||||
tool_calling: bool = True,
|
||||
parallel_tool_calls: bool = True,
|
||||
summaries: bool = False,
|
||||
structured: bool = True,
|
||||
resource: str | None = None,
|
||||
kind: str = "chat",
|
||||
agent_compatible: bool = True,
|
||||
availability: str = "current",
|
||||
confidence: str = "documented",
|
||||
source: str,
|
||||
inventory: str | None = None,
|
||||
input_cost: float | None = None,
|
||||
output_cost: float | None = None,
|
||||
cached_input_cost: float | None = None,
|
||||
cache_write_input_cost: float | None = None,
|
||||
unit_cost: float | None = None,
|
||||
) -> dict[str, Any]:
|
||||
values: dict[str, Any] = {
|
||||
"route": route_key,
|
||||
"upstream_id": upstream_id,
|
||||
"maker": maker,
|
||||
"display_name": display_name,
|
||||
"description": description,
|
||||
"kind": kind,
|
||||
"agent_compatible": agent_compatible,
|
||||
"context_window": context,
|
||||
"reasoning_levels": reasoning or (["none"] if not agent_compatible else ["high"]),
|
||||
"default_reasoning": default_reasoning
|
||||
or ("none" if not agent_compatible else (reasoning or ["high"])[-1]),
|
||||
"modalities": modalities or ["text"],
|
||||
"output_modalities": output_modalities or ["text"],
|
||||
"tool_calling": tool_calling,
|
||||
"parallel_tool_calls": parallel_tool_calls,
|
||||
"supports_reasoning_summaries": summaries,
|
||||
"structured_output": structured,
|
||||
"availability": availability,
|
||||
"capability_confidence": confidence,
|
||||
"source": source,
|
||||
}
|
||||
if output is not None:
|
||||
values["max_output_tokens"] = output
|
||||
if resource:
|
||||
values["resource_group"] = resource
|
||||
if inventory:
|
||||
values["inventory"] = inventory
|
||||
if input_cost is not None:
|
||||
values["input_cost_per_million"] = input_cost
|
||||
if output_cost is not None:
|
||||
values["output_cost_per_million"] = output_cost
|
||||
if cached_input_cost is not None:
|
||||
values["cached_input_cost_per_million"] = cached_input_cost
|
||||
if cache_write_input_cost is not None:
|
||||
values["cache_write_input_cost_per_million"] = cache_write_input_cost
|
||||
if unit_cost is not None:
|
||||
values["unit_cost_usd"] = unit_cost
|
||||
return values
|
||||
|
||||
|
||||
routes: dict[str, dict[str, Any]] = {
|
||||
"zai_coding_responses": route(
|
||||
driver="switchyard",
|
||||
name="Z.AI Coding Plan Responses",
|
||||
api_operator="zai",
|
||||
access_product="zai_coding_plan",
|
||||
wire_protocol="openai_responses",
|
||||
billing_mode="subscription",
|
||||
base_url="https://api.z.ai/api/v1",
|
||||
credential_envs=["ZAI_CODING_API_KEY"],
|
||||
max_retries=1,
|
||||
transport_modalities=["text"],
|
||||
tool_calling=True,
|
||||
parallel_tool_calls=True,
|
||||
resource_group="zai_coding_plan",
|
||||
inventory="zai-coding-plan",
|
||||
),
|
||||
"zai_coding_openai_chat": route(
|
||||
driver="switchyard",
|
||||
name="Z.AI Coding Plan OpenAI-compatible",
|
||||
api_operator="zai",
|
||||
access_product="zai_coding_plan",
|
||||
wire_protocol="openai_chat",
|
||||
billing_mode="subscription",
|
||||
base_url="https://api.z.ai/api/coding/paas/v4",
|
||||
credential_envs=["ZAI_CODING_API_KEY"],
|
||||
max_retries=1,
|
||||
transport_modalities=["text"],
|
||||
tool_calling=True,
|
||||
parallel_tool_calls=True,
|
||||
resource_group="zai_coding_plan",
|
||||
inventory="zai-coding-plan",
|
||||
),
|
||||
"zai_coding_anthropic_messages": route(
|
||||
driver="catalog_only",
|
||||
name="Z.AI Coding Plan Anthropic-compatible (catalog only)",
|
||||
api_operator="zai",
|
||||
access_product="zai_coding_plan",
|
||||
wire_protocol="anthropic_messages",
|
||||
billing_mode="subscription",
|
||||
base_url="https://api.z.ai/api/anthropic",
|
||||
credential_envs=["ZAI_CODING_API_KEY"],
|
||||
auth="bearer",
|
||||
transport_modalities=["text"],
|
||||
transport_output_modalities=["text"],
|
||||
tool_calling=False,
|
||||
parallel_tool_calls=False,
|
||||
resource_group="zai_coding_plan",
|
||||
inventory="zai-coding-plan",
|
||||
),
|
||||
"zai_general_openai_chat": route(
|
||||
driver="switchyard",
|
||||
name="Z.AI General API",
|
||||
api_operator="zai",
|
||||
access_product="zai_general_api",
|
||||
wire_protocol="openai_chat",
|
||||
billing_mode="api",
|
||||
base_url="https://api.z.ai/api/paas/v4",
|
||||
credential_envs=["ZAI_API_KEY"],
|
||||
max_retries=1,
|
||||
transport_modalities=["text", "image", "video", "file"],
|
||||
tool_calling=True,
|
||||
parallel_tool_calls=True,
|
||||
resource_group="zai_api",
|
||||
inventory="zai-api",
|
||||
),
|
||||
"zai_general_catalog": route(
|
||||
driver="catalog_only",
|
||||
name="Z.AI media and specialist API catalog",
|
||||
api_operator="zai",
|
||||
access_product="zai_general_api",
|
||||
wire_protocol="catalog_only",
|
||||
billing_mode="catalog_only",
|
||||
transport_modalities=["text", "image", "video", "audio", "file"],
|
||||
tool_calling=False,
|
||||
parallel_tool_calls=False,
|
||||
resource_group="zai_api",
|
||||
inventory="zai-api",
|
||||
),
|
||||
"opencode_zen_openai_chat": route(
|
||||
driver="switchyard",
|
||||
name="OpenCode Zen Chat Completions",
|
||||
api_operator="opencode",
|
||||
access_product="opencode_zen",
|
||||
wire_protocol="openai_chat",
|
||||
billing_mode="api",
|
||||
base_url="https://opencode.ai/zen/v1",
|
||||
credential_envs=["OPENCODE_API_KEY"],
|
||||
max_retries=1,
|
||||
transport_modalities=["text"],
|
||||
tool_calling=True,
|
||||
parallel_tool_calls=False,
|
||||
resource_group="opencode_zen",
|
||||
inventory="opencode-zen",
|
||||
),
|
||||
"opencode_zen_responses": route(
|
||||
driver="switchyard",
|
||||
name="OpenCode Zen Responses",
|
||||
api_operator="opencode",
|
||||
access_product="opencode_zen",
|
||||
wire_protocol="openai_responses",
|
||||
billing_mode="api",
|
||||
base_url="https://opencode.ai/zen/v1",
|
||||
credential_envs=["OPENCODE_API_KEY"],
|
||||
max_retries=1,
|
||||
transport_modalities=["text"],
|
||||
tool_calling=True,
|
||||
parallel_tool_calls=False,
|
||||
resource_group="opencode_zen",
|
||||
inventory="opencode-zen",
|
||||
),
|
||||
"opencode_zen_anthropic_messages": route(
|
||||
driver="switchyard",
|
||||
name="OpenCode Zen Anthropic Messages",
|
||||
api_operator="opencode",
|
||||
access_product="opencode_zen",
|
||||
wire_protocol="anthropic_messages",
|
||||
billing_mode="api",
|
||||
base_url="https://opencode.ai/zen/v1",
|
||||
credential_envs=["OPENCODE_API_KEY"],
|
||||
max_retries=1,
|
||||
transport_modalities=["text"],
|
||||
tool_calling=True,
|
||||
parallel_tool_calls=False,
|
||||
resource_group="opencode_zen",
|
||||
inventory="opencode-zen",
|
||||
),
|
||||
"opencode_zen_google_catalog": route(
|
||||
driver="catalog_only",
|
||||
name="OpenCode Zen Google-native models (catalog only)",
|
||||
api_operator="opencode",
|
||||
access_product="opencode_zen",
|
||||
wire_protocol="catalog_only",
|
||||
billing_mode="catalog_only",
|
||||
transport_modalities=["text", "image", "video", "audio", "file"],
|
||||
transport_output_modalities=["text"],
|
||||
tool_calling=True,
|
||||
parallel_tool_calls=False,
|
||||
resource_group="opencode_zen",
|
||||
inventory="opencode-zen",
|
||||
),
|
||||
# OpenCode Go uses different endpoints for different model families.
|
||||
"opencode_go_openai_chat": route(
|
||||
driver="switchyard",
|
||||
name="OpenCode Go Chat Completions",
|
||||
api_operator="opencode",
|
||||
access_product="opencode_go",
|
||||
wire_protocol="openai_chat",
|
||||
billing_mode="subscription",
|
||||
base_url="https://opencode.ai/zen/go/v1",
|
||||
credential_envs=["OPENCODE_API_KEY"],
|
||||
max_retries=1,
|
||||
transport_modalities=["text"],
|
||||
tool_calling=True,
|
||||
parallel_tool_calls=True,
|
||||
resource_group="opencode_go",
|
||||
inventory="opencode-go",
|
||||
),
|
||||
"opencode_go_responses": route(
|
||||
driver="switchyard",
|
||||
name="OpenCode Go Responses",
|
||||
api_operator="opencode",
|
||||
access_product="opencode_go",
|
||||
wire_protocol="openai_responses",
|
||||
billing_mode="subscription",
|
||||
base_url="https://opencode.ai/zen/go/v1",
|
||||
credential_envs=["OPENCODE_API_KEY"],
|
||||
max_retries=1,
|
||||
transport_modalities=["text"],
|
||||
tool_calling=True,
|
||||
parallel_tool_calls=True,
|
||||
resource_group="opencode_go",
|
||||
inventory="opencode-go",
|
||||
),
|
||||
"opencode_go_anthropic_messages": route(
|
||||
driver="switchyard",
|
||||
name="OpenCode Go Anthropic Messages",
|
||||
api_operator="opencode",
|
||||
access_product="opencode_go",
|
||||
wire_protocol="anthropic_messages",
|
||||
billing_mode="subscription",
|
||||
base_url="https://opencode.ai/zen/go/v1",
|
||||
credential_envs=["OPENCODE_API_KEY"],
|
||||
max_retries=1,
|
||||
transport_modalities=["text"],
|
||||
tool_calling=True,
|
||||
parallel_tool_calls=True,
|
||||
resource_group="opencode_go",
|
||||
inventory="opencode-go",
|
||||
),
|
||||
"openrouter_openai_chat": route(
|
||||
driver="switchyard",
|
||||
name="OpenRouter Chat Completions",
|
||||
api_operator="openrouter",
|
||||
access_product="openrouter_api",
|
||||
wire_protocol="openai_chat",
|
||||
billing_mode="api",
|
||||
base_url="https://openrouter.ai/api/v1",
|
||||
credential_envs=["OPENROUTER_API_KEY"],
|
||||
extra_headers={"X-OpenRouter-Metadata": "enabled"},
|
||||
max_retries=1,
|
||||
transport_modalities=["text"],
|
||||
tool_calling=True,
|
||||
parallel_tool_calls=True,
|
||||
resource_group="openrouter",
|
||||
inventory="openrouter",
|
||||
),
|
||||
"llama_cpp_local_openai_chat": route(
|
||||
driver="switchyard",
|
||||
name="Local llama.cpp",
|
||||
api_operator="local",
|
||||
access_product="llama_cpp",
|
||||
wire_protocol="openai_chat",
|
||||
billing_mode="local",
|
||||
base_url="http://127.0.0.1:8001/v1",
|
||||
max_retries=0,
|
||||
transport_modalities=["text"],
|
||||
tool_calling=True,
|
||||
parallel_tool_calls=False,
|
||||
resource_group="local_gpu_0",
|
||||
),
|
||||
"codex_chatgpt_builtin": route(
|
||||
driver="codex_builtin",
|
||||
name="Built-in Codex with ChatGPT authentication",
|
||||
api_operator="openai",
|
||||
access_product="chatgpt_codex",
|
||||
wire_protocol="codex_builtin",
|
||||
billing_mode="chatgpt_subscription",
|
||||
provider_id="openai",
|
||||
auth="chatgpt",
|
||||
transport_modalities=["text", "image"],
|
||||
tool_calling=True,
|
||||
parallel_tool_calls=True,
|
||||
resource_group="chatgpt_subscription",
|
||||
inventory="openai-codex",
|
||||
),
|
||||
"openai_api_responses": route(
|
||||
driver="switchyard",
|
||||
name="OpenAI API through Switchyard",
|
||||
api_operator="openai",
|
||||
access_product="openai_api",
|
||||
wire_protocol="openai_responses",
|
||||
billing_mode="api",
|
||||
base_url="https://api.openai.com/v1",
|
||||
credential_envs=["OPENAI_API_KEY"],
|
||||
max_retries=1,
|
||||
transport_modalities=["text", "image"],
|
||||
tool_calling=True,
|
||||
parallel_tool_calls=True,
|
||||
resource_group="openai_api",
|
||||
),
|
||||
"anthropic_api_messages": route(
|
||||
driver="switchyard",
|
||||
name="Anthropic API through Switchyard",
|
||||
api_operator="anthropic",
|
||||
access_product="anthropic_api",
|
||||
wire_protocol="anthropic_messages",
|
||||
billing_mode="api",
|
||||
base_url="https://api.anthropic.com/v1",
|
||||
credential_envs=["ANTHROPIC_API_KEY"],
|
||||
max_retries=1,
|
||||
transport_modalities=["text"],
|
||||
tool_calling=True,
|
||||
parallel_tool_calls=True,
|
||||
resource_group="anthropic_api",
|
||||
),
|
||||
"ollama_local_openai_chat": route(
|
||||
driver="switchyard",
|
||||
name="Local Ollama through Switchyard",
|
||||
api_operator="local",
|
||||
access_product="ollama",
|
||||
wire_protocol="openai_chat",
|
||||
billing_mode="local",
|
||||
base_url="http://127.0.0.1:11434/v1",
|
||||
max_retries=0,
|
||||
transport_modalities=["text"],
|
||||
tool_calling=True,
|
||||
parallel_tool_calls=False,
|
||||
resource_group="local_gpu_0",
|
||||
),
|
||||
"lmstudio_local_openai_chat": route(
|
||||
driver="switchyard",
|
||||
name="Local LM Studio through Switchyard",
|
||||
api_operator="local",
|
||||
access_product="lmstudio",
|
||||
wire_protocol="openai_chat",
|
||||
billing_mode="local",
|
||||
base_url="http://127.0.0.1:1234/v1",
|
||||
max_retries=0,
|
||||
transport_modalities=["text"],
|
||||
tool_calling=True,
|
||||
parallel_tool_calls=False,
|
||||
resource_group="local_gpu_0",
|
||||
),
|
||||
"ollama_codex_oss": route(
|
||||
driver="codex_oss",
|
||||
name="Codex native Ollama OSS mode",
|
||||
api_operator="local",
|
||||
access_product="ollama",
|
||||
wire_protocol="codex_oss",
|
||||
billing_mode="local",
|
||||
provider_id="ollama",
|
||||
transport_modalities=["text"],
|
||||
tool_calling=True,
|
||||
parallel_tool_calls=False,
|
||||
resource_group="local_gpu_0",
|
||||
),
|
||||
"lmstudio_codex_oss": route(
|
||||
driver="codex_oss",
|
||||
name="Codex native LM Studio OSS mode",
|
||||
api_operator="local",
|
||||
access_product="lmstudio",
|
||||
wire_protocol="codex_oss",
|
||||
billing_mode="local",
|
||||
provider_id="lmstudio",
|
||||
transport_modalities=["text"],
|
||||
tool_calling=True,
|
||||
parallel_tool_calls=False,
|
||||
resource_group="local_gpu_0",
|
||||
),
|
||||
}
|
||||
|
||||
resources: dict[str, dict[str, Any]] = {
|
||||
"opencode_zen": {
|
||||
"description": "OpenCode Zen request capacity",
|
||||
"lock_key": "provider:opencode-zen",
|
||||
"max_active": 8,
|
||||
},
|
||||
"zai_coding_plan": {
|
||||
"description": "Z.AI Coding Plan request capacity",
|
||||
"lock_key": "provider:zai-coding-plan",
|
||||
"max_active": 4,
|
||||
},
|
||||
"zai_api": {
|
||||
"description": "Z.AI general API request capacity",
|
||||
"lock_key": "provider:zai-api",
|
||||
"max_active": 8,
|
||||
},
|
||||
"opencode_go": {
|
||||
"description": "OpenCode Go request capacity",
|
||||
"lock_key": "provider:opencode-go",
|
||||
"max_active": 6,
|
||||
},
|
||||
"openrouter": {
|
||||
"description": "OpenRouter request capacity",
|
||||
"lock_key": "provider:openrouter",
|
||||
"max_active": 8,
|
||||
},
|
||||
"local_gpu_0": {
|
||||
"description": "One local GPU/model-server slot",
|
||||
"lock_key": "local-gpu:0",
|
||||
"max_active": 1,
|
||||
},
|
||||
"chatgpt_subscription": {
|
||||
"description": "Built-in ChatGPT/Codex account concurrency",
|
||||
"lock_key": "provider:chatgpt",
|
||||
"max_active": 4,
|
||||
},
|
||||
"openai_api": {
|
||||
"description": "OpenAI API concurrency",
|
||||
"lock_key": "provider:openai-api",
|
||||
"max_active": 8,
|
||||
},
|
||||
"anthropic_api": {
|
||||
"description": "Anthropic API concurrency",
|
||||
"lock_key": "provider:anthropic-api",
|
||||
"max_active": 8,
|
||||
},
|
||||
}
|
||||
|
||||
models: dict[str, dict[str, Any]] = {}
|
||||
binding_keys: dict[tuple[str, str], str] = {}
|
||||
inventory_snapshots = load_inventory_snapshots(INVENTORY_SNAPSHOT_ROOT)
|
||||
inventory_as_of = max(str(snapshot["as_of"]) for snapshot in inventory_snapshots)
|
||||
for snapshot in inventory_snapshots:
|
||||
inventory_id = str(snapshot["inventory"])
|
||||
for key, record in snapshot["models"].items():
|
||||
if key in models:
|
||||
raise ValueError(f"duplicate catalog model key across inventory snapshots: {key}")
|
||||
catalog_record = record["catalog"]
|
||||
if catalog_record.get("inventory") != inventory_id:
|
||||
raise ValueError(f"snapshot {inventory_id} model {key} has mismatched inventory")
|
||||
binding = (str(catalog_record["route"]), str(catalog_record["upstream_id"]))
|
||||
if binding in binding_keys:
|
||||
raise ValueError(
|
||||
f"duplicate route/upstream binding across inventory snapshots: "
|
||||
f"{binding_keys[binding]!r} and {key!r} both select {binding!r}"
|
||||
)
|
||||
binding_keys[binding] = key
|
||||
models[key] = dict(catalog_record)
|
||||
|
||||
# Project-local deployments are curated configuration, not upstream inventory.
|
||||
models["llama_cpp_local_openai_chat__qwen3_5_9b"] = model(
|
||||
"llama_cpp_local_openai_chat",
|
||||
"qwen3.5-9b",
|
||||
"qwen",
|
||||
"Qwen3.5-9B local",
|
||||
description="Project-capped text-only 32K/8K local evidence deployment; the upstream model is natively 262K and multimodal",
|
||||
context=32_768,
|
||||
output=8_192,
|
||||
reasoning=["none"],
|
||||
default_reasoning="none",
|
||||
parallel_tool_calls=False,
|
||||
summaries=False,
|
||||
structured=False,
|
||||
resource="local_gpu_0",
|
||||
source="qwen35-model-card",
|
||||
confidence="project-capped-deployment",
|
||||
)
|
||||
|
||||
catalog = {
|
||||
"schema_version": MMO_SCHEMA_VERSION,
|
||||
"routes": routes,
|
||||
"models": models,
|
||||
"resources": resources,
|
||||
}
|
||||
validate_catalog_data(catalog, label="generated catalog")
|
||||
|
||||
inventory: dict[str, Any] = {
|
||||
"schema_version": MMO_SCHEMA_VERSION,
|
||||
"as_of": inventory_as_of,
|
||||
"sources": {
|
||||
"openai-api-models": "https://developers.openai.com/api/docs/models",
|
||||
"qwen35-model-card": "https://huggingface.co/Qwen/Qwen3.5-9B",
|
||||
"llama-cpp-server": "https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md",
|
||||
},
|
||||
"inventories": {},
|
||||
}
|
||||
for snapshot in inventory_snapshots:
|
||||
inventory_id = str(snapshot["inventory"])
|
||||
for source_id, source_url in snapshot["sources"].items():
|
||||
existing = inventory["sources"].get(source_id)
|
||||
if existing is not None and existing != source_url:
|
||||
raise ValueError(f"conflicting URL for inventory source {source_id}")
|
||||
inventory["sources"][source_id] = source_url
|
||||
catalog_keys = sorted(snapshot["models"])
|
||||
model_ids = sorted(
|
||||
{str(snapshot["models"][key]["catalog"]["upstream_id"]) for key in catalog_keys}
|
||||
)
|
||||
entry: dict[str, Any] = {
|
||||
"as_of": snapshot["as_of"],
|
||||
"snapshot": f"config/inventory-snapshots/{inventory_id}.json",
|
||||
"adapter": snapshot["adapter"],
|
||||
"fingerprint_fields": snapshot["fingerprint_fields"],
|
||||
"models_sha256": snapshot["models_sha256"],
|
||||
"dynamic": snapshot["dynamic"],
|
||||
"expected_count": len(model_ids),
|
||||
"models": model_ids,
|
||||
"catalog_keys": catalog_keys,
|
||||
}
|
||||
entry.update(snapshot["discovery"])
|
||||
if snapshot["captures"]:
|
||||
entry["captures"] = snapshot["captures"]
|
||||
inventory["inventories"][inventory_id] = entry
|
||||
|
||||
HEADER = f"""# Generated by scripts/generate_catalog.py. Do not edit this file directly.\n# Add local routes/models under ~/.config/codex-mmo/catalog.d/*.toml.\n# Inventory baseline: config/upstream-inventory.json (as of {inventory_as_of}).\n\n"""
|
||||
|
||||
|
||||
def _rendered_outputs() -> dict[Path, str]:
|
||||
return {
|
||||
ROOT / "config" / "catalog.toml": HEADER + toml_dumps(catalog),
|
||||
ROOT / "config" / "upstream-inventory.json": json.dumps(
|
||||
inventory, ensure_ascii=False, indent=2, sort_keys=True, allow_nan=False
|
||||
)
|
||||
+ "\n",
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Generate the bundled model catalog")
|
||||
parser.add_argument(
|
||||
"--check",
|
||||
action="store_true",
|
||||
help="fail if generated files differ without modifying the source tree",
|
||||
)
|
||||
args = parser.parse_args(argv)
|
||||
outputs = _rendered_outputs()
|
||||
changed = [
|
||||
path.relative_to(ROOT).as_posix()
|
||||
for path, expected in outputs.items()
|
||||
if not path.is_file() or path.read_text(encoding="utf-8") != expected
|
||||
]
|
||||
if not args.check:
|
||||
atomic_write_text(
|
||||
ROOT / "config" / "catalog.toml",
|
||||
outputs[ROOT / "config" / "catalog.toml"],
|
||||
0o644,
|
||||
)
|
||||
atomic_write_json(ROOT / "config" / "upstream-inventory.json", inventory, 0o644)
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"routes": len(routes),
|
||||
"models": len(models),
|
||||
"resources": len(resources),
|
||||
"inventory_models": {
|
||||
key: len(value["models"])
|
||||
for key, value in sorted(inventory["inventories"].items())
|
||||
},
|
||||
"changed": changed,
|
||||
"passed": not changed if args.check else True,
|
||||
},
|
||||
indent=2,
|
||||
sort_keys=True,
|
||||
allow_nan=False,
|
||||
)
|
||||
)
|
||||
return 1 if args.check and changed else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user