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: noqaThe # 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, PipelineProjectfrom __future__ import annotations— defers annotation evaluation (PEP 563), needed for the union aliases used as type hints before the classes are fully defined.dataclasses.dataclassis always imported.fieldis added only when a node has a mutable default (adictorlistliteral), which requiresfield(default_factory=...).typingimports are sorted and trimmed to what the schema actually requires.Mappingappears only when at least one node uses ajson/objectfield.resolve_credentialis a runtime helper that reads~/.optixlog/credentials.tomland merges environment variables soOptixClient()works without explicitbase_url/api_keyarguments.
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.
@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:
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:
_GratingCouplerLabNodes = SimulationNode | MeasurementNode
_ModulatorProgramNodes = SimulationNode | LayoutNodeThese 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:
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:
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_urlandapi_keyare both optional. When omitted,resolve_credentialfills them fromOPTIXLOG_API_KEY/OPTIXLOG_BASE_URLenvironment variables or from~/.optixlog/credentials.toml.- The
sdkproperty exposes the underlyingSDKClientfor use withManagementandPipelinedirectly. close()closes the underlying HTTP connection pool. Call it when you are done or useOptixClientas 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:
_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.
- Each project gets a union alias — the union of the node types in its schema.
- Each project wrapper's
ingestacceptsdata: <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. OptixClient.project()has one@overloadperLiteral["<id>"]. TheProjectsconstants areLiteral-typed, soProjects.GRATING_COUPLER_LABresolves to the_Project_proj_grating_7f3aoverload 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
| Part | Origin |
|---|---|
| Header banner and imports | Generated by emit.py from the schema |
Node dataclasses and to_payload | Generated by emit.py + typemap.py |
Projects constants | Generated from the projects list |
| Union aliases | Generated from project_node_types |
_Project_* wrapper classes | Generated per project |
OptixClient body | Generated (template in templates/generated.py.j2) |
_PROJECT_WRAPPERS dict | Generated |
SDKClient, Pipeline, PipelineProject | Hand-written runtime in the optixlog package |
resolve_credential | Hand-written in optixlog._credentials |
IngestResult | Hand-written in optixlog.pipeline |