Files
mizan/protocol/mizan-codegen/scripts/run_pydantic_to_rust.py
2026-07-30 21:12:29 -04:00

92 lines
2.4 KiB
Python

#!/usr/bin/env python3
"""Report a Pydantic module's declarations as JSON on stdout.
Takes one argv JSON object of `module` and `derives`; returns `enums` as
name plus variant identifiers, and `structs` as rendered Rust source.
"""
import importlib
import inspect
import json
import sys
from enum import Enum
from pathlib import Path
from pydantic import BaseModel
from pydantic_to_rust import (
emit_rust_struct,
to_rust_variant_ident,
walk_pydantic_model,
)
def _declared_in(module, obj) -> bool:
return getattr(obj, "__module__", None) == module.__name__
def discover_models(module) -> list[type[BaseModel]]:
"""BaseModel subclasses declared here. An imported one is another
module's to emit."""
return [
obj
for _, obj in inspect.getmembers(module, inspect.isclass)
if issubclass(obj, BaseModel)
and obj is not BaseModel
and _declared_in(module, obj)
]
def discover_enums(module) -> list[type[Enum]]:
"""Enum subclasses declared here. Only variant identifiers are reported;
the caller renders the enum body."""
return [
obj
for _, obj in inspect.getmembers(module, inspect.isclass)
if issubclass(obj, Enum) and obj is not Enum and _declared_in(module, obj)
]
def main() -> int:
if len(sys.argv) < 2:
sys.stderr.write("run_pydantic_to_rust.py: missing JSON payload argument\n")
return 2
payload = json.loads(sys.argv[1])
module_name: str = payload["module"]
derives = tuple(payload["derives"])
sys.path.insert(0, str(Path.cwd()))
module = importlib.import_module(module_name)
enums = discover_enums(module)
models = discover_models(module)
if not enums and not models:
sys.stderr.write(
f"run_pydantic_to_rust.py: no Enum or BaseModel subclasses "
f"declared in {module_name!r}\n"
)
return 3
json.dump(
{
"enums": [
{
"name": enum_class.__name__,
"variants": [to_rust_variant_ident(m.name) for m in enum_class],
}
for enum_class in enums
],
"structs": [
emit_rust_struct(walk_pydantic_model(model), derives=derives)
for model in models
],
},
sys.stdout,
)
return 0
if __name__ == "__main__":
sys.exit(main())