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 90 capabilities REST does not serve — webhook management, agent tools, time travel, pub/sub consumer groups, function versioning, executable deployments, raw SQL, 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.

Metadata

Release files for ironflow-py 0.38.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ironflow-py 0.38.0
File Size Uploaded
ironflow_py-0.38.0.tar.gz 198.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ironflow-py 0.38.0
File Interpreter ABI Platform
ironflow_py-0.38.0-py3-none-any.whl Python 3 none any Details

Total release size: 358.4 kB

Release files / ironflow_py-0.38.0.tar.gz

Download URL ironflow_py-0.38.0.tar.gz
Size 198.2 kB
Tags Source
SHA-256 checksum
How to use checksums
c66ad32ec9e9445540579b2c97c8b51c874d300a238343b1d52610f711de0dff
BLAKE2b-256 checksum
How to use checksums
c28af7f3a0c154e28b788bcd2446f6759153a436e985147d7f9f69bb6a5d3d39
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

Release files / ironflow_py-0.38.0-py3-none-any.whl

Download URL ironflow_py-0.38.0-py3-none-any.whl
Size 160.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bad7e3bb70f51c878090e317e0c92c96a15bcf8b2cb94ba3395412a9a78fc920
BLAKE2b-256 checksum
How to use checksums
deabf6e9cc5766a5c272dcf623485f09befee08b61d0ab91ddb86335f5c5f9f9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

Release history Release notifications | RSS feed

0.47.0

2 release files

0.46.0

2 release files

0.45.2

2 release files

0.45.1

2 release files

0.44.1

2 release files

0.44.0

2 release files

0.40.0

2 release files

0.39.0

2 release files

This release

0.38.0 This release

2 release files

0.37.1

2 release files

0.37.0

2 release files

0.33.1

2 release 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