OptixLog Docs
Reference

Generated Module Anatomy

A section-by-section walk-through of optixlog_gen.py — what each block contains, what is generated vs hand-written, and how the static typing mechanism enforces per-project node constraints.

optixlog generate emits a single Python file (default optixlog_gen.py, or a package directory when module_style = "package"). Every byte of this file is generated — you should never edit it by hand. The header says so:

# AUTOGENERATED by `optixlog generate` — DO NOT EDIT BY HAND.
# Regenerate after a schema change with: optixlog generate
# ruff: noqa

The # ruff: noqa comment suppresses lint on the generated output. The file is byte-deterministic: identical input always yields identical bytes, so it is safe to commit and diff.

This page walks through python-sdk-spec/examples/optixlog_gen.py section by section.

1. Header block

The file opens with the autogenerated banner and all necessary imports:

from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Literal, Mapping, Sequence, overload

from optixlog import JSONValue, SDKClient, resolve_credential
from optixlog.pipeline import IngestResult, Pipeline, PipelineProject
  • from __future__ import annotations — defers annotation evaluation (PEP 563), needed for the union aliases used as type hints before the classes are fully defined.
  • dataclasses.dataclass is always imported. field is added only when a node has a mutable default (a dict or list literal), which requires field(default_factory=...).
  • typing imports are sorted and trimmed to what the schema actually requires. Mapping appears only when at least one node uses a json/object field.
  • resolve_credential is a runtime helper that reads ~/.optixlog/credentials.toml and merges environment variables so OptixClient() works without explicit base_url/api_key arguments.

2. Node dataclasses

One @dataclass(frozen=True, kw_only=True) class is generated per node type. Shared node types (those referenced by more than one project) are emitted once — they are not duplicated. The single class participates in multiple project union aliases.

optixlog_gen.py — node dataclasses
@dataclass(frozen=True, kw_only=True)
class SimulationNode:
    config: Mapping[str, JSONValue]
    solver: Literal["fdtd", "eme", "varfdtd"]
    wavelength_nm: float = 1550.0
    other_param: str | None = None

    def to_payload(self) -> dict[str, JSONValue]:
        return {
            "config": dict(self.config),
            "solver": self.solver,
            "wavelength_nm": self.wavelength_nm,
            "other_param": self.other_param,
        }


@dataclass(frozen=True, kw_only=True)
class MeasurementNode:
    instrument: str
    samples: Sequence[float]
    notes: str | None = None

    def to_payload(self) -> dict[str, JSONValue]:
        return {
            "instrument": self.instrument,
            "samples": list(self.samples),
            "notes": self.notes,
        }


@dataclass(frozen=True, kw_only=True)
class LayoutNode:
    gds_path: str
    layer_map: Mapping[str, JSONValue] | None = None

    def to_payload(self) -> dict[str, JSONValue]:
        return {
            "gds_path": self.gds_path,
            "layer_map": dict(self.layer_map) if self.layer_map is not None else None,
        }

For the full type mapping rules, see Type Mapping.

3. Projects namespace

A plain class whose attributes are Literal-annotated string constants, one per project:

optixlog_gen.py — Projects
class Projects:
    GRATING_COUPLER_LAB: Literal["proj_grating_7f3a"] = "proj_grating_7f3a"
    MODULATOR_PROGRAM: Literal["proj_modulator_22b1"] = "proj_modulator_22b1"

The explicit Literal["proj_grating_7f3a"] annotation — not a bare string assignment — is what allows the type checker to resolve which OptixClient.project() overload is called. Without it, passing Projects.GRATING_COUPLER_LAB would widen to str and the overload selection would fail.

Constant names are derived from the project name field using UPPER_SNAKE_CASE. Non-identifier characters collapse to _. Names that would start with a digit are prefixed P_. Collisions get a numeric suffix (_2, _3, ...) in project order.

4. Per-project union aliases

One type alias per project, formed as the union of that project's node dataclasses:

optixlog_gen.py — union aliases
_GratingCouplerLabNodes = SimulationNode | MeasurementNode
_ModulatorProgramNodes = SimulationNode | LayoutNode

These aliases are private (prefixed _). Their sole purpose is to constrain the data parameter of each project wrapper's ingest method. SimulationNode appears in both unions because the sample schema assigns it to both projects — the class is shared, not copied.

Alias names are derived from the Projects constant: GRATING_COUPLER_LAB → _GratingCouplerLabNodes.

5. Per-project wrapper classes

One _Project_<sanitized_id> class per project. Each wraps a PipelineProject instance and exposes ingest as a pair of @overloads — one for the prompt shape and one for the explicit edge shape — with the data parameter typed as that project's union alias:

optixlog_gen.py — _Project_proj_grating_7f3a
class _Project_proj_grating_7f3a:
    def __init__(self, inner: PipelineProject) -> None:
        self._inner = inner

    @overload
    def ingest(self, *, data: _GratingCouplerLabNodes, prompt: str) -> IngestResult: ...
    @overload
    def ingest(
        self,
        *,
        data: _GratingCouplerLabNodes,
        origin_nodes: Sequence[str],
        forward_nodes: Sequence[str],
    ) -> IngestResult: ...
    def ingest(
        self,
        *,
        data: _GratingCouplerLabNodes,
        prompt: str | None = None,
        origin_nodes: Sequence[str] | None = None,
        forward_nodes: Sequence[str] | None = None,
    ) -> IngestResult:
        if prompt is not None:
            return self._inner.ingest(data=data, prompt=prompt)
        assert origin_nodes is not None and forward_nodes is not None
        return self._inner.ingest(
            data=data, origin_nodes=origin_nodes, forward_nodes=forward_nodes
        )

The wrapper class name is _Project_ followed by the sanitized project id (non-identifier characters replaced with _; a leading digit is prefixed _).

6. OptixClient

The generated OptixClient is the entry point you import. It wraps an SDKClient and a Pipeline and provides one @overload per project id — each keyed on a Literal["<id>"] type and returning that project's wrapper:

optixlog_gen.py — OptixClient
class OptixClient:
    def __init__(self, *, base_url: str | None = None, api_key: str | None = None, **kw: Any) -> None:
        cred = resolve_credential(base_url=base_url, api_key=api_key)
        self._sdk = SDKClient(base_url=cred.api_base_url, api_key=cred.api_key, **kw)
        self._pipeline = Pipeline(self._sdk)

    @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: ...
    def project(self, id: str) -> Any:
        return _PROJECT_WRAPPERS[id](self._pipeline.project(id))

    @property
    def sdk(self) -> SDKClient:
        return self._sdk

    def close(self) -> None:
        self._sdk.close()

Key points:

  • base_url and api_key are both optional. When omitted, resolve_credential fills them from OPTIXLOG_API_KEY/OPTIXLOG_BASE_URL environment variables or from ~/.optixlog/credentials.toml.
  • The sdk property exposes the underlying SDKClient for use with Management and Pipeline directly.
  • close() closes the underlying HTTP connection pool. Call it when you are done or use OptixClient as a context manager.
  • The project() overload count is linear in the number of projects, not quadratic.

7. _PROJECT_WRAPPERS

A module-level dictionary that maps each project id to its wrapper class. The runtime body of project() uses this to avoid duplicating dispatch logic:

optixlog_gen.py — _PROJECT_WRAPPERS
_PROJECT_WRAPPERS: dict[str, type[_Project_proj_grating_7f3a] | type[_Project_proj_modulator_22b1]] = {
    "proj_grating_7f3a": _Project_proj_grating_7f3a,
    "proj_modulator_22b1": _Project_proj_modulator_22b1,
}

This dictionary is also private. It is the bridge between the typed overloads (which the type checker resolves statically) and the runtime dispatch (which Python resolves dynamically).

How the typing mechanism works

The goal is to make passing a node type that a project does not support a static type error, not a runtime error.

  1. Each project gets a union alias — the union of the node types in its schema.
  2. Each project wrapper's ingest accepts data: <ProjectUnion>. Passing a node type that is a member of the union is assignable and typechecks. Passing one that is not a member of the union matches no overload.
  3. OptixClient.project() has one @overload per Literal["<id>"]. The Projects constants are Literal-typed, so Projects.GRATING_COUPLER_LAB resolves to the _Project_proj_grating_7f3a overload and thence to _GratingCouplerLabNodes.

Positive case — typechecks because SimulationNode is in _GratingCouplerLabNodes:

client.project(Projects.GRATING_COUPLER_LAB).ingest(
    data=SimulationNode(config={}, solver="fdtd"),
    prompt="Simulated a grating coupler.",
)

Negative case — static type error because LayoutNode is not in _GratingCouplerLabNodes:

client.project(Projects.GRATING_COUPLER_LAB).ingest(
    data=LayoutNode(gds_path="x.gds"),  # reportArgumentType (pyright) / call-overload (mypy)
    prompt="...",
)

For a deeper explanation of the typing mechanism, see Type Safety Guarantees.

What is generated vs hand-written

PartOrigin
Header banner and importsGenerated by emit.py from the schema
Node dataclasses and to_payloadGenerated by emit.py + typemap.py
Projects constantsGenerated from the projects list
Union aliasesGenerated from project_node_types
_Project_* wrapper classesGenerated per project
OptixClient bodyGenerated (template in templates/generated.py.j2)
_PROJECT_WRAPPERS dictGenerated
SDKClient, Pipeline, PipelineProjectHand-written runtime in the optixlog package
resolve_credentialHand-written in optixlog._credentials
IngestResultHand-written in optixlog.pipeline

On this page