ironflow-py 0.33.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. ironflow_py-0.33.1/.gitignore +30 -0
  2. ironflow_py-0.33.1/CONTRIBUTING.md +27 -0
  3. ironflow_py-0.33.1/LICENSE +183 -0
  4. ironflow_py-0.33.1/PKG-INFO +253 -0
  5. ironflow_py-0.33.1/README.md +219 -0
  6. ironflow_py-0.33.1/SECURITY.md +33 -0
  7. ironflow_py-0.33.1/ironflow/__init__.py +83 -0
  8. ironflow_py-0.33.1/ironflow/_gen/__init__.py +17 -0
  9. ironflow_py-0.33.1/ironflow/_gen/agent_tools_connect.py +347 -0
  10. ironflow_py-0.33.1/ironflow/_gen/agent_tools_pb.py +523 -0
  11. ironflow_py-0.33.1/ironflow/_gen/audit_connect.py +221 -0
  12. ironflow_py-0.33.1/ironflow/_gen/audit_pb.py +311 -0
  13. ironflow_py-0.33.1/ironflow/_gen/entity_stream_connect.py +558 -0
  14. ironflow_py-0.33.1/ironflow/_gen/entity_stream_pb.py +1016 -0
  15. ironflow_py-0.33.1/ironflow/_gen/environment_connect.py +408 -0
  16. ironflow_py-0.33.1/ironflow/_gen/environment_pb.py +348 -0
  17. ironflow_py-0.33.1/ironflow/_gen/event_schema_connect.py +420 -0
  18. ironflow_py-0.33.1/ironflow/_gen/event_schema_pb.py +506 -0
  19. ironflow_py-0.33.1/ironflow/_gen/ironflow_connect.py +1612 -0
  20. ironflow_py-0.33.1/ironflow/_gen/ironflow_pb.py +1776 -0
  21. ironflow_py-0.33.1/ironflow/_gen/projection_connect.py +1468 -0
  22. ironflow_py-0.33.1/ironflow/_gen/projection_pb.py +2143 -0
  23. ironflow_py-0.33.1/ironflow/_gen/pubsub_connect.py +880 -0
  24. ironflow_py-0.33.1/ironflow/_gen/pubsub_pb.py +1466 -0
  25. ironflow_py-0.33.1/ironflow/_gen/query_connect.py +152 -0
  26. ironflow_py-0.33.1/ironflow/_gen/query_pb.py +144 -0
  27. ironflow_py-0.33.1/ironflow/_gen/timetravel_connect.py +290 -0
  28. ironflow_py-0.33.1/ironflow/_gen/timetravel_pb.py +486 -0
  29. ironflow_py-0.33.1/ironflow/_gen/types_pb.py +1224 -0
  30. ironflow_py-0.33.1/ironflow/_gen/webhook_connect.py +811 -0
  31. ironflow_py-0.33.1/ironflow/_gen/webhook_pb.py +1123 -0
  32. ironflow_py-0.33.1/ironflow/_gen/worker_connect.py +148 -0
  33. ironflow_py-0.33.1/ironflow/_gen/worker_pb.py +1391 -0
  34. ironflow_py-0.33.1/ironflow/_http.py +526 -0
  35. ironflow_py-0.33.1/ironflow/client.py +910 -0
  36. ironflow_py-0.33.1/ironflow/models.py +1757 -0
  37. ironflow_py-0.33.1/ironflow/py.typed +0 -0
  38. ironflow_py-0.33.1/ironflow/rpc/__init__.py +25 -0
  39. ironflow_py-0.33.1/ironflow/rpc/_client.py +1638 -0
  40. ironflow_py-0.33.1/ironflow/rpc/_runtime.py +737 -0
  41. ironflow_py-0.33.1/ironflow/rpc/v1.py +415 -0
  42. ironflow_py-0.33.1/pyproject.toml +161 -0
  43. ironflow_py-0.33.1/rpc-capabilities.yaml +412 -0
  44. ironflow_py-0.33.1/tests/__init__.py +0 -0
  45. ironflow_py-0.33.1/tests/api_snapshot.json +164 -0
  46. ironflow_py-0.33.1/tests/conftest.py +18 -0
  47. ironflow_py-0.33.1/tests/harness.py +104 -0
  48. ironflow_py-0.33.1/tests/integration/__init__.py +1 -0
  49. ironflow_py-0.33.1/tests/integration/conftest.py +117 -0
  50. ironflow_py-0.33.1/tests/integration/test_live.py +358 -0
  51. ironflow_py-0.33.1/tests/rpc_api_snapshot.json +184 -0
  52. ironflow_py-0.33.1/tests/rpc_server.py +288 -0
  53. ironflow_py-0.33.1/tests/test_api_snapshot.py +70 -0
  54. ironflow_py-0.33.1/tests/test_client.py +1021 -0
  55. ironflow_py-0.33.1/tests/test_errors.py +105 -0
  56. ironflow_py-0.33.1/tests/test_hostile_server.py +285 -0
  57. ironflow_py-0.33.1/tests/test_request.py +165 -0
  58. ironflow_py-0.33.1/tests/test_retry.py +224 -0
  59. ironflow_py-0.33.1/tests/test_rpc_api_snapshot.py +85 -0
  60. ironflow_py-0.33.1/tests/test_rpc_client.py +407 -0
  61. ironflow_py-0.33.1/tests/test_rpc_retry.py +327 -0
  62. ironflow_py-0.33.1/tests/test_rpc_stream_resume.py +343 -0
  63. ironflow_py-0.33.1/tests/test_rpc_streams.py +373 -0
  64. ironflow_py-0.33.1/tests/test_rpc_surface.py +165 -0
  65. ironflow_py-0.33.1/tests/test_rpc_wire_names.py +195 -0
@@ -0,0 +1,30 @@
1
+ # Build artifacts
2
+ build/
3
+ dist/
4
+ *.egg-info/
5
+ .eggs/
6
+
7
+ # Bytecode
8
+ __pycache__/
9
+ *.py[cod]
10
+
11
+ # Virtualenvs
12
+ .venv/
13
+ .venv-*/
14
+
15
+ # Tool caches
16
+ .mypy_cache/
17
+ .pytest_cache/
18
+ .ruff_cache/
19
+ .tox/
20
+ htmlcov/
21
+ .coverage
22
+ .coverage.*
23
+
24
+ # OS noise
25
+ .DS_Store
26
+
27
+ # Editor
28
+ *.swp
29
+ .idea/
30
+ .vscode/
@@ -0,0 +1,27 @@
1
+ # Contributing
2
+
3
+ **This repository is a read-only mirror.** External contributions are not accepted here.
4
+
5
+ The Ironflow Python SDK source is maintained in a private engine repository. This mirror is updated automatically by the release pipeline at each version tag.
6
+
7
+ ## Reporting bugs
8
+
9
+ Issues are disabled here. Everything goes to one tracker: [sahina/ironflow-issues](https://github.com/sahina/ironflow-issues/issues/new/choose).
10
+
11
+ - SDK bugs (in `ironflow-py`) → file there and pick **Python SDK** as the component. Include your Python version, platform, `pip --version`, repro steps, and a minimal example.
12
+ - Engine, CLI, dashboard, and desktop bugs → same tracker, pick the matching component.
13
+ - Security issues → [private advisory](https://github.com/sahina/ironflow-issues/security/advisories/new) or see [SECURITY.md](SECURITY.md). Do **not** open a public issue.
14
+
15
+ ## Pull requests
16
+
17
+ Pull requests opened against this repository will be closed without review. The only commits expected here come from the release pipeline mirroring source from the engine repo.
18
+
19
+ If you have a fix in mind, file an issue describing the bug and the proposed fix. The engine team will land the change in the private repo and it will appear here at the next release.
20
+
21
+ ## Generated code
22
+
23
+ `ironflow/_gen/` is generated from the engine's protobuf definitions and vendored here as source. Do not hand-edit it, in this repo or anywhere else — the next release overwrites it. The generator lives in the engine repo (`make proto-python`).
24
+
25
+ ## License inquiries
26
+
27
+ For commercial licensing, see the contact in [LICENSE](LICENSE).
@@ -0,0 +1,183 @@
1
+ # Ironflow License
2
+
3
+ **Version 1.0 — 2026-05-08**
4
+
5
+ This file contains the binding legal terms under which Ironflow is offered.
6
+ A plain-English summary lives at `docs/explanation/licensing.md`. If the two
7
+ disagree, this file controls.
8
+
9
+ > **Lawyer review status:** v1 DIY-substitute. The licensor entity is
10
+ > resolved. Three sections below are original prose rather than FSL template
11
+ > text and have not been counsel-reviewed. Review is tracked in issue #777 and
12
+ > is due before the first paid customer ships:
13
+ >
14
+ > - "Personal-Use and Evaluation Carve-Out" — supplemental to the FSL
15
+ > Permitted Purpose, added to match the worked examples in
16
+ > `docs/explanation/licensing.md` per the plan body. JetBrains Toolbox and
17
+ > Postman free-tier EULAs were the reference for the carve-out shape, but
18
+ > the wording is ours.
19
+ > - "Governing Law and Venue"
20
+ > - "Commercial Licensing"
21
+ >
22
+ > This is a disclosure, not a publication gate. These terms ship on every
23
+ > public release channel today — npm, the Go module proxy, PyPI, the releases
24
+ > mirror and ironflow.run — and an earlier wording of this block claimed
25
+ > otherwise while four releases went out past it.
26
+ >
27
+ > **Versioning:** revised terms apply on next install. Each release ships
28
+ > the LICENSE that was effective at the time the artifact was built.
29
+ > Continuing to use a previously installed version under its prior terms
30
+ > is permitted; upgrading to a newer release accepts the LICENSE shipped
31
+ > with that release.
32
+
33
+ ---
34
+
35
+ ## Functional Source License, Version 1.1, ALv2 Future License
36
+
37
+ ## Abbreviation
38
+
39
+ FSL-1.1-ALv2
40
+
41
+ ## Notice
42
+
43
+ Copyright 2026 AEC Office LLC
44
+
45
+ ## Terms and Conditions
46
+
47
+ ### Licensor ("We")
48
+
49
+ The party offering the Software under these Terms and Conditions.
50
+
51
+ ### The Software
52
+
53
+ The "Software" is each version of the software that we make available under
54
+ these Terms and Conditions, as indicated by our inclusion of these Terms and
55
+ Conditions with the Software.
56
+
57
+ ### License Grant
58
+
59
+ Subject to your compliance with this License Grant and the Patents,
60
+ Redistribution and Trademark clauses below, we hereby grant you the right to
61
+ use, copy, modify, create derivative works, publicly perform, publicly display
62
+ and redistribute the Software for any Permitted Purpose identified below.
63
+
64
+ ### Permitted Purpose
65
+
66
+ A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
67
+ means making the Software available to others in a commercial product or
68
+ service that:
69
+
70
+ 1. substitutes for the Software;
71
+
72
+ 2. substitutes for any other product or service we offer using the Software
73
+ that exists as of the date we make the Software available; or
74
+
75
+ 3. offers the same or substantially similar functionality as the Software.
76
+
77
+ Permitted Purposes specifically include using the Software:
78
+
79
+ 1. for your internal use and access;
80
+
81
+ 2. for non-commercial education;
82
+
83
+ 3. for non-commercial research; and
84
+
85
+ 4. in connection with professional services that you provide to a licensee
86
+ using the Software in accordance with these Terms and Conditions.
87
+
88
+ ### Patents
89
+
90
+ To the extent your use for a Permitted Purpose would necessarily infringe our
91
+ patents, the license grant above includes a license under our patents. If you
92
+ make a claim against any party that the Software infringes or contributes to
93
+ the infringement of any patent, then your patent license to the Software ends
94
+ immediately.
95
+
96
+ ### Redistribution
97
+
98
+ The Terms and Conditions apply to all copies, modifications and derivatives of
99
+ the Software.
100
+
101
+ If you redistribute any copies, modifications or derivatives of the Software,
102
+ you must include a copy of or a link to these Terms and Conditions and not
103
+ remove any copyright notices provided in or with the Software.
104
+
105
+ ### Disclaimer
106
+
107
+ THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
108
+ IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
109
+ PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
110
+
111
+ IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
112
+ SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
113
+ EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
114
+
115
+ ### Trademarks
116
+
117
+ Except for displaying the License Details and identifying us as the origin of
118
+ the Software, you have no right under these Terms and Conditions to use our
119
+ trademarks, trade names, service marks or product names.
120
+
121
+ ## Grant of Future License
122
+
123
+ We hereby irrevocably grant you an additional license to use the Software under
124
+ the Apache License, Version 2.0 that is effective on the second anniversary of
125
+ the date we make the Software available. On or after that date, you may use the
126
+ Software under the Apache License, Version 2.0, in which case the following
127
+ will apply:
128
+
129
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use
130
+ this file except in compliance with the License.
131
+
132
+ You may obtain a copy of the License at
133
+
134
+ http://www.apache.org/licenses/LICENSE-2.0
135
+
136
+ Unless required by applicable law or agreed to in writing, software distributed
137
+ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
138
+ CONDITIONS OF ANY KIND, either express or implied. See the License for the
139
+ specific language governing permissions and limitations under the License.
140
+
141
+ ---
142
+
143
+ ## Personal-Use and Evaluation Carve-Out
144
+
145
+ > _Supplemental to the FSL Permitted Purpose above. Pending counsel review._
146
+
147
+ The following uses are explicitly permitted in addition to the FSL Permitted
148
+ Purposes listed above:
149
+
150
+ 1. **Personal use.** Use of the Software by a natural person for that
151
+ person's personal, non-commercial purposes — including hobby projects,
152
+ self-education, and side projects that do not generate revenue or
153
+ commercial advantage.
154
+
155
+ 2. **Evaluation.** Use of the Software by any individual or organization for
156
+ the purpose of evaluating the Software prior to a commercial purchase
157
+ decision, for a period of up to ninety (90) days from first install.
158
+ Evaluation use does not require notification.
159
+
160
+ These carve-outs are non-transferable and do not extend to redistribution,
161
+ sublicensing, or any Competing Use as defined in the FSL Permitted Purpose
162
+ section.
163
+
164
+ ---
165
+
166
+ ## Governing Law and Venue
167
+
168
+ These Terms and Conditions are governed by the laws of the State of Delaware,
169
+ United States of America, without regard to its conflict-of-laws provisions.
170
+ Any dispute arising out of or relating to these Terms and Conditions shall be
171
+ brought exclusively in the state or federal courts located in Delaware, and
172
+ you consent to the personal jurisdiction of those courts.
173
+
174
+ ## Commercial Licensing
175
+
176
+ For uses that fall outside the Permitted Purpose — including any Competing
177
+ Use, or any commercial use case for which a separate paid license is required
178
+ under our Cloud or Enterprise terms — contact license@ironflow.run to discuss
179
+ a commercial license.
180
+
181
+ ## SPDX Identifier
182
+
183
+ `LicenseRef-Ironflow-EULA`
@@ -0,0 +1,253 @@
1
+ Metadata-Version: 2.5
2
+ Name: ironflow-py
3
+ Version: 0.33.1
4
+ Summary: Ironflow Python SDK — event-driven backend platform client
5
+ Project-URL: Homepage, https://ironflow.run
6
+ Project-URL: Documentation, https://docs.ironflow.run
7
+ Project-URL: Repository, https://github.com/sahina/ironflow-py
8
+ Project-URL: Issues, https://github.com/sahina/ironflow-py/issues
9
+ License-Expression: LicenseRef-Ironflow-EULA
10
+ License-File: LICENSE
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: connectrpc<0.12,>=0.11.1
21
+ Requires-Dist: pyqwest<0.11,>=0.10
22
+ Provides-Extra: dev
23
+ Requires-Dist: build>=1.0; extra == 'dev'
24
+ Requires-Dist: mypy>=1.0; extra == 'dev'
25
+ Requires-Dist: pytest>=7.0; extra == 'dev'
26
+ Requires-Dist: ruff>=0.1; extra == 'dev'
27
+ Requires-Dist: uvicorn>=0.30; extra == 'dev'
28
+ Provides-Extra: gen
29
+ Requires-Dist: connectrpc==0.11.1; extra == 'gen'
30
+ Requires-Dist: protobuf-py==0.1.1; extra == 'gen'
31
+ Requires-Dist: protoc-gen-connectrpc==0.11.1; extra == 'gen'
32
+ Requires-Dist: protoc-gen-py==0.1.1; extra == 'gen'
33
+ Description-Content-Type: text/markdown
34
+
35
+ # Ironflow Python SDK
36
+
37
+ Python client for [Ironflow](https://ironflow.run) — the Continuous History platform for backend systems.
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ pip install ironflow-py
43
+ ```
44
+
45
+ ```python
46
+ import ironflow
47
+ ```
48
+
49
+ > **Install `ironflow-py`, not `ironflow`.** The distribution name is `ironflow-py`; the
50
+ > import name is `ironflow`. The bare name `ironflow` on PyPI belongs to an unrelated
51
+ > third-party project (a materials-science tool from the pyiron group), which also
52
+ > installs a top-level `ironflow` module — a virtualenv holding both has two claimants on
53
+ > that name and the install order silently decides which wins. Keep them in separate
54
+ > environments.
55
+
56
+ Requires Python 3.10+.
57
+
58
+ Available from v0.33.0. Versions track the Ironflow engine release, so `ironflow-py`
59
+ 0.33.0 is the client for engine 0.33.0.
60
+
61
+ ## Status
62
+
63
+ Experimental and client-only. It can emit events and query runs, projections,
64
+ KV, and config, but it ships **no worker runtime** — there is no `step.run`, no
65
+ `step.sleep`, and no push or pull mode.
66
+
67
+ **Two clients, two protocols, neither a superset of the other.** `IronflowClient`
68
+ speaks REST and retries idempotent methods. `IronflowRPC` / `AsyncIronflowRPC`
69
+ speak ConnectRPC and reach 46 capabilities REST does not serve — webhook
70
+ management, agent tools, time travel, pub/sub consumer groups, function
71
+ versioning, and environment lookup/key rotation — including four server
72
+ streams. `IronflowRPC` retries only the unary methods the protos annotate
73
+ side-effect-free, and reconnects a subscription only when you position it (see
74
+ [Retries](#retries-and-what-can-still-go-wrong)).
75
+
76
+ Routes the server's manifest annotates with schemas get a typed `models.*` `TypedDict`
77
+ return and keyword-only query parameters; routes it does not annotate still return `Any`
78
+ and take no query kwargs. `TypedDict` is a type-checker construct only — there is **no
79
+ runtime validation**. Headers declared by a route, including `If-Match`,
80
+ `If-None-Match`, and `Idempotency-Key`, are generated as typed keyword arguments.
81
+ `BaseClient.request()` remains the escape hatch for undeclared headers. WebSocket and
82
+ watch endpoints (`/ws`, config watch, KV watch) have no generated methods at all and
83
+ `request()` cannot reach them either; poll the corresponding read method, use a
84
+ ConnectRPC subscription, or use the Go or JavaScript SDK for a live watch.
85
+
86
+ For durable step execution, use the Go or JavaScript SDK.
87
+
88
+ ## Quick Start
89
+
90
+ ```python
91
+ from ironflow import IronflowClient
92
+
93
+ client = IronflowClient(
94
+ server_url="http://localhost:9123",
95
+ api_key="ifkey_...",
96
+ )
97
+
98
+ # Emit an event
99
+ client.events_create(body={
100
+ "name": "user.created",
101
+ "data": {"user_id": "123", "email": "user@example.com"},
102
+ })
103
+
104
+ # List runs
105
+ runs = client.runs_list()
106
+
107
+ # Get a specific run
108
+ run = client.runs_get("run_abc123")
109
+
110
+ # List projections
111
+ projections = client.projections_list()
112
+
113
+ # Public server inspection
114
+ health = client.health()
115
+ readiness = client.ready()
116
+ capabilities = client.capabilities()
117
+ ```
118
+
119
+ ## ConnectRPC client
120
+
121
+ For the capabilities REST does not serve. Request and response types come from
122
+ `ironflow.rpc.v1`; `ironflow._gen` is private and its layout may change.
123
+
124
+ ```python
125
+ from ironflow import IronflowRPC
126
+ from ironflow.rpc.v1 import CreateWebhookSourceRequest, SubscribeRequest
127
+
128
+ with IronflowRPC(server_url="http://localhost:9123", api_key="ifkey_...") as rpc:
129
+ source = rpc.webhooks.create_source(
130
+ CreateWebhookSourceRequest(name="Stripe", event_prefix="stripe.")
131
+ )
132
+
133
+ # Server streams are ordinary iterators; breaking out cancels.
134
+ for event in rpc.pubsub.subscribe(SubscribeRequest(pattern="topic:orders.*")):
135
+ print(event.event_id)
136
+ break
137
+ ```
138
+
139
+ `AsyncIronflowRPC` mirrors it method for method — `await` the unary calls,
140
+ `async for` the streams, and `await rpc.aclose()` instead of `close()`.
141
+
142
+ Failures raise `IronflowRPCError`, which subclasses `IronflowError`, so
143
+ `except IronflowError` still catches every failure from either client. Full
144
+ reference, including timeouts, `NO_TIMEOUT`, and stream lifetime:
145
+ [docs.ironflow.run/reference/api/python-sdk](https://docs.ironflow.run/reference/api/python-sdk).
146
+
147
+ ### Retries, and what can still go wrong
148
+
149
+ A unary call that fails with `unavailable` — a refused connection, a socket
150
+ dropped mid-response, a server shedding load — is sent again, up to 3 attempts
151
+ with exponential backoff. Nothing else is retried: every other Connect code is a
152
+ decision the server will reach again identically.
153
+
154
+ **Only side-effect-free methods are retried.** The protobuf definitions annotate
155
+ them `idempotency_level = NO_SIDE_EFFECTS`, and the client reads that annotation
156
+ rather than any list of its own. A method without it — every create, update,
157
+ delete, rotate and emit — is sent exactly once and its failure is raised to you
158
+ immediately, because a transport error cannot tell you whether the server
159
+ committed the write before the connection dropped.
160
+
161
+ Two things this does **not** promise:
162
+
163
+ - **A retried read may execute more than once on the server.** A response lost
164
+ on the way back is indistinguishable from a request that never arrived, so the
165
+ retry re-runs the method. That is harmless for a read by definition, and it is
166
+ the reason the annotation gates the behaviour.
167
+ - **A write is never retried for you.** If you need one repeated safely, repeat
168
+ it yourself with an idempotency key — `EmitRequest` and `PublishRequest` both
169
+ carry `idempotency_key` — and treat a failed write as *unknown*, not *failed*.
170
+
171
+ `timeout=` still bounds the whole call including every retry and backoff, not
172
+ each attempt. Pass `max_attempts=1` to the constructor to switch retries off.
173
+
174
+ **`subscribe` reconnects only if you positioned it.** Set
175
+ `options.start_after_sequence` to the last `event.sequence` you processed, and a
176
+ dropped connection is retried from there. That field is both the cursor and the
177
+ opt-in: without it there is no honest place to resume from, so the stream raises
178
+ and re-subscribing is yours.
179
+
180
+ ```python
181
+ from ironflow.rpc.v1 import SubscribeRequest, SubscribeOptions
182
+
183
+ cursor = 0
184
+ for event in rpc.pubsub.subscribe(SubscribeRequest(
185
+ pattern="topic:orders.*",
186
+ options=SubscribeOptions(start_after_sequence=cursor),
187
+ )):
188
+ handle(event)
189
+ cursor = event.sequence # persist this if you need to resume across restarts
190
+ ```
191
+
192
+ A resumed stream is **at-least-once**: the frame in flight when the connection
193
+ dropped may arrive twice, because the server sending it is not you having
194
+ processed it. Make `handle` idempotent, or dedupe on `event.sequence`.
195
+
196
+ **The other three streams are not reconnected, and do not need to be.**
197
+ `stream_events`, `wait_catchup_stream` and `join_consumer_group` are positioned
198
+ by the server on a durable consumer, so simply calling them again resumes where
199
+ they left off. A client-side cursor there would move a position other readers
200
+ share.
201
+
202
+ ## API Coverage
203
+
204
+ This SDK is auto-generated from the Ironflow server's route manifest. Run `make sdk-health`
205
+ for the current method count — it changes on every regeneration, so it is not reproduced
206
+ here. Route coverage is not the same as usable coverage; see the Status section above.
207
+
208
+ See the [SDK Comparison Matrix](https://docs.ironflow.run/reference/sdk-comparison) for
209
+ full coverage details.
210
+
211
+ ## Dependencies
212
+
213
+ Two direct runtime dependencies, `connectrpc` and `pyqwest`, which resolve to six
214
+ packages — two of them compiled:
215
+
216
+ ```text
217
+ connectrpc ─┬─ protobuf-py ── protobuf-py-ext (native, CPython only)
218
+ ├─ pyqwest (Rust)
219
+ │ └─ opentelemetry-api
220
+ └─ typing_extensions
221
+ ```
222
+
223
+ They are required, not an extra. The ConnectRPC client is part of the default
224
+ contract, so putting it behind a flag would turn a heavier install into a runtime
225
+ `ImportError` — see ADR 0062 (engine repo, internal).
226
+
227
+ `IronflowClient` itself still uses only the standard library (`urllib`, `json`),
228
+ and `import ironflow` does not load the ConnectRPC stack — that cost is paid on
229
+ first use of `IronflowRPC`.
230
+
231
+ ## Where this package is published from
232
+
233
+ The Ironflow engine is closed source. This SDK's source is mirrored to
234
+ [`sahina/ironflow-py`](https://github.com/sahina/ironflow-py) at each release, and PyPI
235
+ uploads happen from there over [Trusted Publishing](https://docs.pypi.org/trusted-publishers/).
236
+ No PyPI API token exists for this project, so every release carries an attestation
237
+ binding the artifact to a public commit you can inspect.
238
+
239
+ The mirror is **read-only**. Pull requests against it are closed without review; source
240
+ changes land in the engine repo and appear at the next release.
241
+
242
+ ## Bugs and security
243
+
244
+ Issues are disabled on the mirror. Everything goes to one tracker:
245
+
246
+ - Bugs and feature requests → [sahina/ironflow-issues](https://github.com/sahina/ironflow-issues/issues/new/choose), component **Python SDK**. Include your Python version, platform, and a minimal repro.
247
+ - Security issues → [private advisory](https://github.com/sahina/ironflow-issues/security/advisories/new). Do **not** open a public issue.
248
+ - Commercial licensing → the contact in [LICENSE](LICENSE).
249
+
250
+ ## License
251
+
252
+ See [LICENSE](LICENSE) — SPDX `LicenseRef-Ironflow-EULA`. Not an OSI-approved open source
253
+ licence; read it before deploying commercially.