Skip to main content

Ironflow — Python SDK

Python client for Ironflow — the Continuous History platform for backend systems.

PyPI

Installation

pip install ironflow-py
import ironflow

Install ironflow-py, not ironflow. The distribution name is ironflow-py; the import name is ironflow. The bare name ironflow on PyPI belongs to an unrelated third-party project (a materials-science tool from the pyiron group), which also installs a top-level ironflow module — a virtualenv holding both has two claimants on that name and the install order silently decides which wins. Keep them in separate environments.

Requires Python 3.10+.

Available from v0.33.0. Versions track the Ironflow engine release, so ironflow-py 0.33.0 is the client for engine 0.33.0.

Status

Experimental and client-only. It can emit events and query runs, projections, KV, and config, but it ships no worker runtime — there is no step.run, no step.sleep, and no push or pull mode.

Two clients, two protocols, neither a superset of the other. IronflowClient speaks REST and retries idempotent methods. IronflowRPC / AsyncIronflowRPC speak ConnectRPC and reach 86 capabilities REST does not serve — webhook management, agent tools, time travel, pub/sub consumer groups, function versioning, and environment lookup/key rotation — including four server streams. IronflowRPC retries only the unary methods the protos annotate side-effect-free, and reconnects a subscription only when you position it (see Retries).

Routes the server's manifest annotates with schemas get a typed models.* TypedDict return and keyword-only query parameters; routes it does not annotate still return Any and take no query kwargs. TypedDict is a type-checker construct only — there is no runtime validation. Headers declared by a route, including If-Match, If-None-Match, and Idempotency-Key, are generated as typed keyword arguments. BaseClient.request() remains the escape hatch for undeclared headers. WebSocket and watch endpoints (/ws, config watch, KV watch) have no generated methods at all and request() cannot reach them either; poll the corresponding read method, use a ConnectRPC subscription, or use the Go or JavaScript SDK for a live watch.

For durable step execution, use the Go or JavaScript SDK.

Quick Start

from protobuf.wkt import Struct
from ironflow.rpc import v1
from ironflow import IronflowRPC
from ironflow import IronflowClient

client = IronflowClient(
    server_url="http://localhost:9123",
    api_key="ifkey_...",
)

# Public server inspection — REST
health = client.health()
readiness = client.ready()
capabilities = client.capabilities()

# Stored events — REST
events = client.events_list()

# Runs and projections are ConnectRPC-only; there is no REST sibling.
with IronflowRPC(server_url=client.server_url, api_key=client.api_key) as rpc:
    # Emit an event
    rpc.events.emit(v1.TriggerRequest(event='user.created', data=Struct.from_python({'user_id': '123', 'email': 'user@example.com'})))

    # List runs
    runs = rpc.runs.list(v1.ListRunsRequest())

    # Get a specific run
    run = rpc.runs.get(v1.GetRunRequest(id="run_abc123"))

    # List projections
    projections = rpc.projections.list(v1.ListProjectionsRequest())

ConnectRPC client

For the capabilities REST does not serve. Request and response types come from ironflow.rpc.v1; ironflow._gen is private and its layout may change.

from ironflow import IronflowRPC
from ironflow.rpc.v1 import CreateWebhookSourceRequest, SubscribeRequest

with IronflowRPC(server_url="http://localhost:9123", api_key="ifkey_...") as rpc:
    source = rpc.webhooks.create_source(
        CreateWebhookSourceRequest(name="Stripe", event_prefix="stripe.")
    )

    # Server streams are ordinary iterators; breaking out cancels.
    for event in rpc.pubsub.subscribe(SubscribeRequest(pattern="topic:orders.*")):
        print(event.event_id)
        break

AsyncIronflowRPC mirrors it method for method — await the unary calls, async for the streams, and await rpc.aclose() instead of close().

Failures raise IronflowRPCError, which subclasses IronflowError, so except IronflowError still catches every failure from either client. Full reference, including timeouts, NO_TIMEOUT, and stream lifetime: docs.ironflow.run/reference/api/python-sdk.

Retries, and what can still go wrong

A unary call that fails with unavailable — a refused connection, a socket dropped mid-response, a server shedding load — is sent again, up to 3 attempts with exponential backoff. Nothing else is retried: every other Connect code is a decision the server will reach again identically.

Only side-effect-free methods are retried. The protobuf definitions annotate them idempotency_level = NO_SIDE_EFFECTS, and the client reads that annotation rather than any list of its own. A method without it — every create, update, delete, rotate and emit — is sent exactly once and its failure is raised to you immediately, because a transport error cannot tell you whether the server committed the write before the connection dropped.

Two things this does not promise:

  • A retried read may execute more than once on the server. A response lost on the way back is indistinguishable from a request that never arrived, so the retry re-runs the method. That is harmless for a read by definition, and it is the reason the annotation gates the behaviour.
  • A write is never retried for you. If you need one repeated safely, repeat it yourself with an idempotency key — EmitRequest and PublishRequest both carry idempotency_key — and treat a failed write as unknown, not failed.

timeout= still bounds the whole call including every retry and backoff, not each attempt. Pass max_attempts=1 to the constructor to switch retries off.

subscribe reconnects only if you positioned it. Set options.start_after_sequence to the last event.sequence you processed, and a dropped connection is retried from there. That field is both the cursor and the opt-in: without it there is no honest place to resume from, so the stream raises and re-subscribing is yours.

from ironflow.rpc.v1 import SubscribeRequest, SubscribeOptions

cursor = 0
for event in rpc.pubsub.subscribe(SubscribeRequest(
    pattern="topic:orders.*",
    options=SubscribeOptions(start_after_sequence=cursor),
)):
    handle(event)
    cursor = event.sequence   # persist this if you need to resume across restarts

A resumed stream is at-least-once: the frame in flight when the connection dropped may arrive twice, because the server sending it is not you having processed it. Make handle idempotent, or dedupe on event.sequence.

The other three streams are not reconnected, and do not need to be. stream_events, wait_catchup_stream and join_consumer_group are positioned by the server on a durable consumer, so simply calling them again resumes where they left off. A client-side cursor there would move a position other readers share.

API Coverage

This SDK is auto-generated from the Ironflow server's route manifest. Run make sdk-health for the current method count — it changes on every regeneration, so it is not reproduced here. Route coverage is not the same as usable coverage; see the Status section above.

See the SDK Comparison Matrix for full coverage details.

Dependencies

Two direct runtime dependencies, connectrpc and pyqwest, which resolve to six packages — two of them compiled:

connectrpc ─┬─ protobuf-py ── protobuf-py-ext   (native, CPython only)
            ├─ pyqwest                          (Rust)
            │    └─ opentelemetry-api
            └─ typing_extensions

They are required, not an extra. The ConnectRPC client is part of the default contract, so putting it behind a flag would turn a heavier install into a runtime ImportError — see ADR 0062 (engine repo, internal).

IronflowClient itself still uses only the standard library (urllib, json), and import ironflow does not load the ConnectRPC stack — that cost is paid on first use of IronflowRPC.

What lives here

  • ironflow/ — SDK source: client.py (REST), rpc/ (ConnectRPC), models.py, _http.py
  • ironflow/_gen/ — generated protobuf + ConnectRPC code, vendored from the engine repo
  • tests/ — the same suite the engine repo gates on
  • pyproject.toml, rpc-capabilities.yaml, and the install-smoke script under scripts/
  • LICENSE and security policy

Where the engine source lives

The Ironflow engine is closed source and lives at sahina/ironflow (private). This mirror exists so that:

  • PyPI's "Repository" link resolves to public source
  • README source links (/blob/main/...) resolve to public source
  • PyPI Trusted Publishing attests each artifact to a publicly verifiable Git SHA

Building locally

pip install -e '.[dev]'
pytest
python -m build

Requires Python 3.10+.

Import-check a built wheel from a neutral working directory. At the package root the source ironflow/ directory shadows the installed one, so a wheel shipping no modules still imports cleanly:

python -m venv .venv-smoke
.venv-smoke/bin/pip install --only-binary=:all: dist/*.whl
SMOKE_PY="$PWD/.venv-smoke/bin/python"
SMOKE="$PWD/scripts/python-install-smoke.py"
cd / && "$SMOKE_PY" "$SMOKE"

--only-binary=:all: is the point: without it a dependency missing a wheel on your target starts a Rust build (pyqwest) or a C build (protobuf-py-ext), and the check passes having proved your toolchain works rather than that the wheel does.

Read-only mirror

This repo is read-only. Pull requests will be closed without review. Source changes land in the engine repo and are synced here at each release.

Bug reports

Issues are disabled on this repo. All Ironflow bug reports — SDK, engine, CLI, dashboard, desktop — go to one tracker:

  • Bugs and feature requests → sahina/ironflow-issues. Pick Python SDK as the component, and include your Python version, platform, and a minimal repro.
  • Security issues → private advisory or see SECURITY.md — do not open a public issue
  • Commercial-licensing enquiries → the support address in LICENSE

Verifying release provenance

Two independent trails.

The published artifact. PyPI uploads run from this mirror over Trusted Publishing. No PyPI API token exists for this project, so every file carries an attestation binding it to a public commit. Take a file URL from the project's PyPI download list and verify it with pypi-attestations:

pipx run pypi-attestations verify pypi \
  --repository https://github.com/sahina/ironflow-py \
  https://files.pythonhosted.org/packages/.../ironflow_py-<version>-py3-none-any.whl

The mirror commit. Each release tag carries an annotated message containing the engine-side commit SHA the snapshot was built from:

git fetch --tags
git for-each-ref --format='%(contents)' refs/tags/v<version>

This forensic trail correlates a mirror release to the private engine commit. The mirror's Git history is squash-snapshot per release (no engine commit messages leak through).

License

See LICENSE — SPDX LicenseRef-Ironflow-EULA. Not an OSI-approved open source licence; read it before deploying commercially.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ironflow_py-0.37.1.tar.gz (198.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ironflow_py-0.37.1-py3-none-any.whl (160.1 kB view details)

Uploaded Python 3

File details

Details for the file ironflow_py-0.37.1.tar.gz.

File metadata

  • Download URL: ironflow_py-0.37.1.tar.gz
  • Upload date:
  • Size: 198.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for ironflow_py-0.37.1.tar.gz
Algorithm Hash digest
SHA256 2e8c609908b8561eb8744c2b74700a6af7c1727b45a3597fb8fa40ebed4c57ab
MD5 aa8f3a8dc6faf8839f5a3cd945b5f6a5
BLAKE2b-256 148cffeee3a8b7425e56ad743dd0c1bbca7c4ee20079e36afb34121803286bc5

See more details on using hashes here.

Provenance

The following attestation bundles were made for ironflow_py-0.37.1.tar.gz:

Publisher: publish.yml on sahina/ironflow-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ironflow_py-0.37.1-py3-none-any.whl.

File metadata

  • Download URL: ironflow_py-0.37.1-py3-none-any.whl
  • Upload date:
  • Size: 160.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for ironflow_py-0.37.1-py3-none-any.whl
Algorithm Hash digest
SHA256 8130dd5157e9229e6ef2d39961637959ac8d74546ddbc279cc8c8c545ac630e5
MD5 1771c16cb778121b6a302c28bdeaed0e
BLAKE2b-256 c8557825df6ed5f53dfcf308f768f3ca789443e93dd18f0615e605cb39fd3347

See more details on using hashes here.

Provenance

The following attestation bundles were made for ironflow_py-0.37.1-py3-none-any.whl:

Publisher: publish.yml on sahina/ironflow-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.37.1 This release

2 files

0.37.0

2 files

0.36.1

2 files

0.36.0

2 files

0.35.0

2 files

0.34.0

2 files

0.33.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page