OptixLog Docs
Concepts

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 | LayoutNode

The 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 generate

This 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 --check

Exit 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-run

Using pyright or mypy

Run pyright on your code to get the full benefit of typed codegen:

pyright your_ingest_script.py

Or mypy:

mypy your_ingest_script.py

Errors 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: noqa

Do not edit this file by hand. Regenerate it whenever your schema changes.

On this page