Python SDK for FerricStore and FerricFlow
Project description
FerricStore Python SDK
Python SDK for FerricStore and FerricFlow.
Status: public alpha 0.3.3. APIs may change before 1.0, but the SDK is
tested against command construction, queue/workflow handlers, leases, retries,
history, indexed attributes, named values, idempotent create, worker loops,
async flows, and local FerricStore integration scenarios.
FerricFlow keeps each workflow or job's state and history in one durable place. It is an explicit durable state pipeline, not a hidden deterministic replay engine:
create -> claim -> handler -> transition/complete/retry/fail
Handlers should be idempotent because work can be retried after lease expiry, worker crash, or explicit retry.
Durability is the default contract. A workflow command returns success only after the state change is accepted through FerricStore's quorum path and written to disk.
First 10 minutes
1. Install
pip install ferricstore
For local development from this repo:
python3 -m venv .venv
. .venv/bin/activate
pip install -e ".[dev]"
2. Start FerricStore
Use a local FerricStore server with the FerricStore protocol listener enabled:
ferricstore start
If you are running from the FerricStore source repo, use that repo's documented server command. The SDK examples assume:
ferric://127.0.0.1:6388
3. Create a durable queue item
from ferricstore import FlowClient, QueueClient
client = QueueClient.from_url("ferric://127.0.0.1:6388")
emails = client.queue(type="email")
emails.enqueue("email-1", payload=b"welcome:user-1", idempotent=True)
Use attributes for small indexed metadata you want to filter/count later:
emails.enqueue(
"email-2",
payload=b"welcome:user-2",
attributes={"tenant": "acme", "campaign": "summer"},
idempotent=True,
)
flow = FlowClient.from_url("ferric://127.0.0.1:6388")
records = flow.list("email", attributes={"tenant": "acme"})
stats = flow.stats("email", attributes={"tenant": "acme"})
Attributes are not payload bytes. Use named values/value refs for large data.
FIFO Flow state policy is opt-in per state:
from ferricstore import FlowStatePolicy
flow.install_policy("email", states={"queued": FlowStatePolicy.fifo()})
emails.enqueue("email-3", payload=b"welcome", partition_key="tenant-a:email")
FIFO states require a partition_key; priority is for parallel states.
4. Run a queue worker
from ferricstore import QueueClient
client = QueueClient.from_url("ferric://127.0.0.1:6388")
emails = client.queue(type="email")
def send_email(job):
print(f"send {job.id}: {job.payload!r}")
return b"sent"
emails.worker(concurrency=10, batch_size=100).run(send_email)
If the handler raises, the default worker policy is retry.
5. Create a workflow/state machine
Use workflows when one durable flow moves through named states.
from ferricstore import WorkflowClient, complete, transition
client = WorkflowClient.from_url("ferric://127.0.0.1:6388")
order = client.workflow(
type="order",
initial_state="created",
partition_by=("tenant_id", "order_id"),
)
@order.state("created")
def created(job):
charge_card(job.payload)
return transition("charged")
@order.state("charged")
def charged(job):
send_receipt(job.id)
return complete(result=b"ok")
order.start(
"order-1",
tenant_id="tenant-a",
order_id="order-1",
payload=b"order payload",
idempotent=True,
)
order.worker(states=["created", "charged"], concurrency=10, batch_size=100).run()
6. Store and fetch named values
Use named values when different states need different pieces of data. Values are stored as FerricFlow value refs and are only hydrated when requested.
emails.enqueue(
"email-2",
payload=b"small routing bytes",
values={
"template": b"welcome template bytes",
"profile": b"user profile snapshot",
},
idempotent=True,
)
emails.worker(claim_values=["template"]).run(send_email)
Fetch one or many values directly when needed:
profile = client.value_get(owner_flow_id="email-2", name="profile")
values = client.value_mget(
owner_flow_id="email-2",
names=["template", "profile"],
)
Use ValueConfig or value_max_bytes in production to cap large value reads.
7. Inspect history
record = emails.get("email-1")
history = emails.history("email-1")
print(record)
for event in history:
print(event)
History is for debugging and audit. Handlers should use claimed job data and requested values, not history replay.
8. Common errors
| Error | Meaning | Usual fix |
|---|---|---|
FlowAlreadyExistsError |
The flow id already exists. | Use idempotent=True for safe producer retries or generate a new id. |
FlowNotFoundError |
The flow does not exist or was retained/expired. | Check id, partition inputs, and retention policy. |
FlowWrongStateError |
The command expected a different current state. | Check worker state filters and handler transitions. |
StaleLeaseError |
A worker tried to complete with an old lease. | Keep handlers under lease_ms or renew/retry safely. |
OverloadedError |
Server backpressure rejected the write. | Let the SDK retry/back off; reduce producer rate under sustained pressure. |
What you use
QueueClient/AsyncQueueClientfor durable queues.WorkflowClient/AsyncWorkflowClientfor explicit durable state machines.FlowClient/AsyncFlowClientfor advanced command-level control.ScheduleResult,EffectResult,ApprovalResult,CircuitBreakerStatus,BudgetResult, andGovernanceOverviewfor typed admin/governance responses with dict fallback.RetryPolicy,WorkerConfig,ValueConfig, andExceptionPolicyfor runtime defaults.RawCodecby default,JsonCodecwhen you want JSON payloads.client.command(...)as the FerricStore low-level command escape hatch.
Async quickstart
import asyncio
from ferricstore import AsyncQueueClient
async def main():
client = AsyncQueueClient.from_url("ferric://127.0.0.1:6388")
emails = client.queue(type="email")
async def handler(job):
await send_email_async(job.payload)
await emails.worker(concurrency=100, batch_size=500).run(handler)
asyncio.run(main())
Production shape
Use one process/service to create work and a separate long-lived worker service to claim and complete work.
web/serverless producer -> FerricStore -> worker service
Before production, configure timeouts, lease duration, backpressure behavior,
graceful shutdown, and value hydration caps. The ferric:// transport defaults
to one multiplexed connection with 8 request lanes; only raise connection or
lane counts after profiling shows client-side saturation.
Docs
- Documentation index
- Quickstart
- SDK guide
- Configuration
- Production readiness
- Data in workflows
- Worker runtime
- Async APIs
- Use cases
- Testing
- Troubleshooting
Examples
examples/order_workflow.py: two-state workflow.examples/queue_worker.py: queue producer and worker.examples/async_queue_worker.py: async queue producer and worker.examples/state_machine_workflow.py: explicit workflow runner.examples/protocol_commands.py: FerricStore command helpers.examples/protocol_kv_benchmark.py: protocol SET/GET benchmark.examples/protocol_dbos_benchmark.py: protocol DBOS-style queued workflow benchmark.examples/dbos_style_benchmark.py: DBOS-style throughput benchmark.
Contributing
See CONTRIBUTING.md, CODE_OF_CONDUCT.md, SECURITY.md, and RELEASE.md.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ferricstore-0.3.3.tar.gz.
File metadata
- Download URL: ferricstore-0.3.3.tar.gz
- Upload date:
- Size: 512.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
35e7e0981f8791d04c0f4ca02af1881933c31c4708baba32a89f56b5bbee9776
|
|
| MD5 |
a29696ecd8613d43c4b9951c6fb68d43
|
|
| BLAKE2b-256 |
4def47f229a777a7c0b482640be62cec37816eccf909c9cfa03665c09b75b417
|
Provenance
The following attestation bundles were made for ferricstore-0.3.3.tar.gz:
Publisher:
publish.yml on ferricstore/ferricstore-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ferricstore-0.3.3.tar.gz -
Subject digest:
35e7e0981f8791d04c0f4ca02af1881933c31c4708baba32a89f56b5bbee9776 - Sigstore transparency entry: 2116133799
- Sigstore integration time:
-
Permalink:
ferricstore/ferricstore-python@9b31151d2bca0a6c2e9835ab25279704689290e0 -
Branch / Tag:
refs/tags/v0.3.3 - Owner: https://github.com/ferricstore
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9b31151d2bca0a6c2e9835ab25279704689290e0 -
Trigger Event:
push
-
Statement type:
File details
Details for the file ferricstore-0.3.3-py3-none-any.whl.
File metadata
- Download URL: ferricstore-0.3.3-py3-none-any.whl
- Upload date:
- Size: 141.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fafecc619f920de7436b7f8d4544e0c71a227880cd28bb46cdb30af2eb609fbb
|
|
| MD5 |
d900b2e8716eb6cf0a40f1d93f86a51f
|
|
| BLAKE2b-256 |
491ac047907a9e3de0d4b24ce88f173dfcb7ec4d32f08fd8d55c107f84598305
|
Provenance
The following attestation bundles were made for ferricstore-0.3.3-py3-none-any.whl:
Publisher:
publish.yml on ferricstore/ferricstore-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ferricstore-0.3.3-py3-none-any.whl -
Subject digest:
fafecc619f920de7436b7f8d4544e0c71a227880cd28bb46cdb30af2eb609fbb - Sigstore transparency entry: 2116133844
- Sigstore integration time:
-
Permalink:
ferricstore/ferricstore-python@9b31151d2bca0a6c2e9835ab25279704689290e0 -
Branch / Tag:
refs/tags/v0.3.3 - Owner: https://github.com/ferricstore
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9b31151d2bca0a6c2e9835ab25279704689290e0 -
Trigger Event:
push
-
Statement type: