Set Up a Python Project#

The ovstage Python package binds the ovstage C data plane through ctypes. Its shared loader must be discoverable when the first native operation runs. Programs use Stage and PathDictionary (both context managers) plus numpy for tensor attribute I/O.

Note

ovstage is pre-release. The examples consume ovstage as a published wheel using uv; if the pinned wheel cannot be resolved, the public wheel is not published yet.

Requirements#

  • Python 3.10–3.13 (requires-python = ">=3.10,<3.14"). Recreate the environment with a supported interpreter rather than editing the constraint.

  • NumPy, for tensor I/O (write_attribute(tensors=...), group.array(...)):

    pip install numpy
    

Making ovstage Importable#

Depend on the published ovstage wheel — for example a uv project that pins it in pyproject.toml (the pattern the examples/python projects use), then run with uv run. Alternatively, put the ovstage package on PYTHONPATH directly.

Loading the Shared Library#

The bindings open the ovstage shared loader (ovstage-dynamic.dll on Windows, libovstage-dynamic.so on Linux) the first time you construct a Stage or PathDictionary. The loader opens the ovstage runtime on the first initialize/create call. The wheel bundles both and its <package>/bin layout is searched automatically, so no extra setup is needed. If you are instead running against a locally built shared library, make it discoverable:

# Linux
export LD_LIBRARY_PATH=/path/to/ovstage/lib:$LD_LIBRARY_PATH
# Windows (PowerShell)
#   $env:PATH = "C:\path\to\ovstage\bin;$env:PATH"

Alternatively, set the OVSTAGE_LIBRARY_PATH_HINT environment variable.

Configuring Transform Updates#

Pass a StageConfig when creating the stage to select the hierarchy computation model used for automatic transform updates:

import ovstage

config = ovstage.StageConfig(
    runtime_default_hierarchy_computation_model=(
        ovstage.HierarchyComputationModel.CPU_INCREMENTAL
    )
)

with ovstage.Stage("simulation", config=config) as stage:
    # A manual computation can select the same configured model.
    stage.compute_hierarchy(
        input_ordinal=1,
        output_ordinal=1,
        model=ovstage.HierarchyComputationModel.RUNTIME_DEFAULT,
    )

The setting is process-scoped. Configured stages may coexist when their concrete settings match; creating a stage with a conflicting setting while another stage remains live raises OvstageError. RUNTIME_DEFAULT is meaningful as a manual selector. As a configured value, it does not override the active process default; a fresh process defaults to CPU incremental.

stage.config returns a detached copy of the process configuration captured by that stage. It contains the explicit entries from the initialization that established the active process state; it does not materialize omitted defaults.

Configuring Ordinal Admission#

Use OrdinalAdmissionPolicy to choose how mutations are checked against retained value and prim-lifecycle changes:

import ovstage

config = ovstage.StageConfig(
    runtime_default_ordinal_admission_policy=ovstage.OrdinalAdmissionPolicy.NONDECREASING
)
with ovstage.Stage("simulation", config=config) as stage:
    assert (
        stage.config.runtime_default_ordinal_admission_policy
        == ovstage.OrdinalAdmissionPolicy.NONDECREASING
    )

The default is NONDECREASING, which permits repeated writes at the same unsealed ordinal, while FLOOR_ONLY skips retained-history conflicts. See Ordinal Admission Policies for the comparison rules.

This setting shares the process lifetime of the hierarchy configuration. Omission, None and DEFAULT inherit the active policy; they select the nondecreasing policy only when starting a fresh lifetime. Matching concrete configurations may coexist. A conflicting policy raises OvstageError without acquiring an initialization reference or changing either setting. Close all stages and release any other initialization references and retained instance owners before selecting another policy. stage.config reports the original explicit entries, so an omitted policy remains None and an explicit DEFAULT remains DEFAULT.

Minimal Program#

# Write one value per prim, then advance the watermark before reading.
# wait() reports asynchronous failures; the context manager and finally
# block release the read and its result even if consuming the data fails.
stage.write_attribute(
    query, attr, ordinal=1, tensors=np.array([1.0, 2.0, 3.0], np.float32), is_array=False
).wait()
stage.advance_write_floor(ordinal=1).wait()

with stage.read_attributes(query, [attr], OrdinalRange.latest(1)) as read:
    read.wait()
    group = read.fetch_next()
    if group is None:
        raise SystemExit("read returned no group at ordinal 1")
    try:
        # group.array(i) is a zero-copy numpy view of tensor i (CPU).
        print("read back ordinal", group.ordinal, group.array(0))  # -> [1. 2. 3.]
    finally:
        stage.release_group(group)

Where to Go Next#