Typed Codegen
How optixlog generate derives types from project schemas and makes passing the wrong node type to a project a static type error.
The typed codegen system is the load-bearing feature of the OptixLog SDK. It turns your organization's live schema into a Python module where passing a node type to a project that does not include it is a static type error — caught by pyright or mypy before your code runs.
The core guarantee
Given a schema where SimulationNode is in project GRATING_COUPLER_LAB but LayoutNode is not:
from optixlog_gen import OptixClient, Projects, SimulationNode, LayoutNode
client = OptixClient()
# Valid — SimulationNode is in GRATING_COUPLER_LAB's schema:
client.project(Projects.GRATING_COUPLER_LAB).ingest(
data=SimulationNode(config={}, solver="fdtd"),
prompt="FDTD run.",
)
# Static type error — LayoutNode is NOT in GRATING_COUPLER_LAB's schema:
client.project(Projects.GRATING_COUPLER_LAB).ingest(
data=LayoutNode(gds_path="x.gds"), # pyright: reportArgumentType / mypy: [call-overload]
prompt="wrong project",
)This error is caught at type-check time (in your editor, CI, or pre-commit), not at runtime.
How it works
1. Overloads select the project wrapper
OptixClient.project() has one @overload per project in your schema, each keyed on that project's Literal["<id>"]:
class OptixClient:
@overload
def project(self, id: Literal["proj_grating_7f3a"]) -> _Project_proj_grating_7f3a: ...
@overload
def project(self, id: Literal["proj_modulator_22b1"]) -> _Project_proj_modulator_22b1: ...Projects constants are Literal-typed, so passing Projects.GRATING_COUPLER_LAB resolves to the overload for proj_grating_7f3a and returns _Project_proj_grating_7f3a.
2. Per-project union aliases restrict node types
Each project wrapper's ingest() method accepts only a union of that project's node types:
_GratingCouplerLabNodes = SimulationNode | MeasurementNode
_ModulatorProgramNodes = SimulationNode | LayoutNode
class _Project_proj_grating_7f3a:
@overload
def ingest(self, *, data: _GratingCouplerLabNodes, prompt: str) -> IngestResult: ...
@overload
def ingest(self, *, data: _GratingCouplerLabNodes,
origin_nodes: Sequence[str], forward_nodes: Sequence[str]) -> IngestResult: ...LayoutNode is not in _GratingCouplerLabNodes, so passing it fails assignability — pyright reports reportArgumentType, mypy reports [call-overload].
3. Shared node types are emitted once
A node type that appears in multiple projects is a single dataclass that participates in each project's union alias. It is not copied per project. This means isinstance checks and object identity behave correctly at runtime.
# SimulationNode is the same class in both unions:
_GratingCouplerLabNodes = SimulationNode | MeasurementNode
_ModulatorProgramNodes = SimulationNode | LayoutNodeThe Projects namespace
Projects is a plain class of Literal-typed constants:
class Projects:
GRATING_COUPLER_LAB: Literal["proj_grating_7f3a"] = "proj_grating_7f3a"
MODULATOR_PROGRAM: Literal["proj_modulator_22b1"] = "proj_modulator_22b1"The explicit Literal[...] annotation (not a bare string assignment) is what allows the constant to select the right project() overload. Editors offer autocomplete on Projects. because the class is a standard namespace.
Running optixlog generate
optixlog generateThis walks up from your current directory to find optixlog.toml, fetches schemas from the server (or fixture), and writes optixlog_gen.py. The output is byte-deterministic — identical schema always produces identical bytes.
To check whether committed bindings are stale without writing new ones:
optixlog generate --checkExit code is 1 if the output would change, 0 if it is up to date. Use this in CI.
To preview the output without writing:
optixlog generate --dry-runUsing pyright or mypy
Run pyright on your code to get the full benefit of typed codegen:
pyright your_ingest_script.pyOr mypy:
mypy your_ingest_script.pyErrors will appear at the ingest(data=...) call site, not at runtime.
The generated file header
Every optixlog_gen.py starts with:
# AUTOGENERATED by `optixlog generate` — DO NOT EDIT BY HAND.
# Regenerate after a schema change with: optixlog generate
# ruff: noqaDo not edit this file by hand. Regenerate it whenever your schema changes.
Node Types and Schemas
Per-project node schemas, typed fields, the many-to-many node-to-project mapping, and the universal fields every node has.
Credentials vs Config
The difference between ~/.optixlog/credentials.toml (secret, per-user) and optixlog.toml (committed, per-project) — what goes where and resolution precedence.