Typed Python models for the Sienna power system data format — every component, association, and time series row as a validated pydantic v2 model.
Warning
Pre-release. Every version below 1.0 can change incompatibly in any
release; no stability is promised until 1.0. Under PEP 440, 0.1.0 is
not itself a "pre-release" version, so pip install power-openapi-models
installs it with no --pre flag and no warning. Pin an exact version.
A data model library. It reads, validates, and writes the JSON interchange format that Sienna tools produce and consume, with full type information.
What it is not: a power-flow solver, an optimization framework, or a simulation engine. It carries no numerics. If you want to solve something, these models are the input format, not the tool.
pip install power-openapi-modelsuv add power-openapi-modelsfrom power_openapi_models.core.models import ACBus, ACBusType
bus = ACBus(
id=1,
name="bus-1",
available=True,
number=1,
bustype=ACBusType.PV,
base_voltage=230.0,
)
print(bus.name, bus.bustype, bus.base_voltage)
print(bus.model_dump_json(exclude_none=True))| Module | Holds | Reach for it when |
|---|---|---|
infrastructure_core |
Units (UnitSystem), function data, shared value shapes (MinMax, UpDown, ...) |
You need a domain-neutral building block used across every other module |
core |
Power enums, curves, costs, buses | You are working with shared power types or need ACBus/DCBus |
operations |
Topology, branches, injections, services, market | You are working with the grid itself |
investments |
Technologies, financials, requirements, regions | You are doing capacity expansion |
dynamics |
Dynamic generator and inverter components | You are doing transient stability |
timeseries |
The six time series association types | You are handling forecasts or profiles |
document |
SystemDocument, the hand-written envelope, plus read_document/write_document |
You are loading or saving a whole serialized system |
Import a class from its module's .models submodule:
from power_openapi_models.core.models import ACBus.
Many component fields carry a sibling *_units field (for example
ThermalStandard.power_units) that says how to read every other power-family
field on that same component. There is no document-level unit system: each
component blob is self-describing.
from power_openapi_models.infrastructure_core.models import UnitSystem
print(list(UnitSystem))
print(UnitSystem.NATURAL_UNITS.value)NATURAL_UNITS means values are in physical units — MW, MVAr, kV, ohm.
COMPONENT_BASE means per-unit against the component's own recorded
base_power. Getting this wrong is the most common source of
wrong-by-a-factor-of-100 bugs across the ecosystem.
Several types are one of several shapes, chosen by a discriminator field.
They are pydantic RootModels, and the concrete variant is reached through
.root.
from power_openapi_models.infrastructure_core.models import (
FunctionData,
LinearFunctionData,
)
parsed = FunctionData.model_validate(
{"function_type": "LINEAR", "proportional_term": 2.0, "constant_term": 5.0}
)
assert isinstance(parsed.root, LinearFunctionData)
print(parsed.root.proportional_term)There are no object references on the wire. ThermalStandard.bus, for
example, names its bus by id, and resolving it is the reader's job.
from power_openapi_models.core.models import ACBus
buses = [
ACBus(id=1, name="a", available=True, number=1, base_voltage=230.0),
ACBus(id=2, name="b", available=True, number=2, base_voltage=230.0),
]
by_id = {bus.id: bus for bus in buses}
print(by_id[2].name)A serialized system is one object: components keyed by type name, a flat
supplemental_attributes array, several association arrays, and
time_series_storage_file naming a sidecar. There is no document-level
unit_system or base_power — SystemDocument forbids both fields
outright, since every component already carries its own basis.
Time series values never appear in the document.
time_series_associations carries only the metadata rows; the values live
in the sidecar named by time_series_storage_file, and reading it is the
consumer's job.
from power_openapi_models.document import read_document
doc = read_document("tests/fixtures/case14_operations.NATURAL_UNITS.json")
buses = doc.components["ACBus"]
generators = doc.components["ThermalStandard"]
print(f"{len(buses)} buses, {len(generators)} thermal generators")
gen = generators[0]
limits = gen["active_power_limits"]
print(gen["name"], "active power limits (MW):", limits["min"], "-", limits["max"])Bad data raises pydantic.ValidationError, which names the field and the
rule:
from pydantic import ValidationError
from power_openapi_models.infrastructure_core.models import LinearFunctionData
try:
LinearFunctionData(function_type="NOT_A_TYPE", proportional_term=1.0, constant_term=0.0)
except ValidationError as exc:
print(exc.error_count(), "error(s)")
print(exc.errors()[0]["loc"], exc.errors()[0]["type"])The package ships a PEP 561 py.typed marker, so mypy and pyright pick the
annotations up with no configuration.
These models are generated. The schema release they came from is recorded in the package:
import power_openapi_models
print(power_openapi_models.__version__)
print(power_openapi_models.__schema_version__)Quote __schema_version__ in bug reports — it identifies the exact schema
release the models were built from.
- SiennaSchemas — the schemas these models are generated from, and where schema bugs belong
- PowerOpenAPIModels.jl — the Julia packages generated from the same schemas
- CONTRIBUTING.md — regenerating, and why you must not edit the models by hand
- CHANGELOG.md
BSD 3-Clause. See LICENSE.