Skip to main content
Documentation

Python

Parse and serialize shared models with Pydantic.

The Python generator produces a typed package built on Pydantic 2. Records become models, wrappers become root models, and variants get case models and JSON adapters. It also emits a py.typed marker for type checkers.

Generate a Package

Generate the task bundle into a Python package directory:

sidex generate py task_models
python -m pip install "pydantic>=2.5"

Use Python 3.12 or newer, as used by Sidex’s Python integration tests. Run your application from a directory where task_models is importable.

Parse and Serialize a Record

from task_models.task import Task

task = Task.model_validate_json(
    '{"title":"Write a schema","completed":false}'
)

assert task.title == "Write a schema"
assert task.description is None

encoded = task.model_dump_json(by_alias=True, exclude_none=True)
print(encoded)

Pydantic validates incoming values according to its model configuration. by_alias=True writes JSON field names, and exclude_none=True omits absent optional fields.

Python models use Python field identifiers. JSON aliases follow Sidex’s naming rules, which default to camelCase. Do not assume that Python’s field name is also the wire key.

Variants and Wrappers

Use Pydantic’s TypeAdapter for a generated variant type:

#[json(tagged = adjacently, tag = "state", content = "value")]
variant Outcome {
    Pending,
    Complete: string,
}

After generating a package with this schema in outcomes.sidex:

from pydantic import TypeAdapter
from task_models.outcomes import Outcome

adapter = TypeAdapter(Outcome)
value = adapter.validate_json('{"state":"Complete","value":"done"}')
encoded = adapter.dump_json(value, by_alias=True, exclude_none=True)

The adapter applies the wire representation specified by the schema. For wrappers, access the wrapped value through the Pydantic root model’s root property.

Access a Case Payload

Generated cases with a payload provide a payload() method. For an internally tagged record case, the fields are inlined in the case model. payload() constructs a fresh instance of the original record:

record Point {
    x: i32,
    y: i32,
}

variant Position {
    Unknown,
    Known: Point,
}

With these definitions in outcomes.sidex:

from pydantic import TypeAdapter
from task_models.outcomes import Position, Point

position = TypeAdapter(Position).validate_json('{"tag":"Known","x":3,"y":4}')
point = position.payload()
assert isinstance(point, Point)
assert (point.x, point.y) == (3, 4)

Map Native Opaque Types

#[json(type = "string")]
#[py(typ = "uuid.UUID")]
opaque UserId

The generator imports the native module and uses its type. Your Python environment must provide that module. Without a Python mapping, opaque types fall back to their JSON shape.

Set opaque_lowering = "json" to ignore native Python mappings, for example when testing raw JSON round trips:

[backend.py]
opaque_lowering = "json"

Reuse Other Bundles

[backend.py.external]
identity = "identity_models"

Generate identity separately into an importable package. The generated bundle initializer resolves references between sibling schemas after loading them, including recursive references.

Validation Rules

Pydantic performs model parsing, but Sidex’s #[validate(...)] rules currently generate validators only for Rust and TypeScript. They are not enforced by the Python backend. Apply equivalent application checks when your Python code requires those constraints.