Petrus: an embedded Petri net workflow engine for Python.
Petrus executes workflows defined as Petri nets. You describe the structure of a workflow — places, transitions, and arcs — and provide the behavior separately as plain Python callables. The engine takes care of token flow, enabling rules, guards, timed transitions, and a full state history with time travel.
What you can do with Petrus?
- Define nets in JSON files (editable by tooling or a visual editor) or with a concise Python DSL.
- Keep structure and behavior separate: transitions reference guards and handlers by name; you bind the callables at runtime.
- Flow typed payloads through the net with colored tokens — guards and handlers declare typed parameters and the engine extracts the right payload automatically.
- Compose nets from reusable subnets via file references, with input/output/awaiting interface places marking the boundaries.
- Travel in time: every fire is recorded, so you can undo, redo, and branch from any past state.
- Drive long-running workflows with split-phase firing, designed to await async handlers — with zero runtime dependency on any workflow platform.
How to use it?
Install from PyPI:
uv add petrus-engine # or: pip install petrus-engine
Build a net with the DSL, bind behavior, and run it:
from petrus.dsl import Net
from petrus.engine import PetriNetRuntime
net = Net("approval")
p, t = net.p, net.t
p.pending(initial=1) > t.validate > p.validated
p.validated > t.approve(guard="is_approved") > p.approved
p.validated > t.reject(guard="is_rejected") > p.rejected
guards = {
"is_approved": lambda d: d.value == "approve",
"is_rejected": lambda d: d.value == "reject",
}
rt = PetriNetRuntime.from_schema(net.build(), guards=guards)
rt.fire(abs(net.t.validate))
A transition is enabled when its input places hold enough tokens, its inhibitor and
read arcs are satisfied, and its guard (if any) returns True for the payloads peeked
from the input tokens. Firing consumes the input tokens and deposits a merged output
token in every output place.
Loading nets from JSON files
Nets can live in JSON files (fileVersion: "2" flat format) and reference other net
files as subnets. Resolve a root file and hand the schema to the engine:
from petrus.schema import FlatNet
net = FlatNet.from_filename("nets/net_approval.json")
rt = PetriNetRuntime.from_schema(net.to_schema(), guards=guards, handlers=handlers)
CLI tools
# Generate a .pyi stub from a module exposing SCHEMA (for IDE autocomplete)
petrus-cli gen-stub myapp.nets -o nets/__init__.pyi
# Validate and canonically format net JSON files in-place (pre-commit friendly)
python -m petrus.cli.format_json --fail-on-change nets/net_*.json
Stub generation formats its output with black; install the cli extra to use it:
uv add "petrus-engine[cli]"
Timed transitions
petrus.timers.TimerManager syncs timer tasks with the net's enabled timed
transitions. It is clock- and sleep-agnostic: by default it uses the system UTC
clock and asyncio.sleep, and you can inject your own primitives — to control
time in tests, or to integrate with a scheduling runtime:
from petrus.timers import TimerManager
tm = TimerManager() # system UTC clock + asyncio.sleep
tm = TimerManager(now=my_clock, sleep=my_sleep) # custom primitives
Thank you to Routable
Routable sponsored the development of this library. Working at Routable is an awesome experience, with a developer-first culture that fosters innovation and growth. If you're interested in joining a dynamic team, check out our job opportunities here!
Authors
- Henrique Bastos henrique@bastos.net
Metadata
Release files for petrus-engine 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| petrus_engine-0.1.0.tar.gz | 73.3 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| petrus_engine-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 110.9 kB
Release files / petrus_engine-0.1.0.tar.gz
| Download URL | petrus_engine-0.1.0.tar.gz |
|---|---|
| Size | 73.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
dccfeee55a02c683c74891876342a73a72ed94c5de05dcbd72b0e43c7728bb8b
|
|
BLAKE2b-256 checksum How to use checksums |
ef5b157c32f047c10074f06cbfe75cd72fe00a0800bf1d3a0b4907b0c247205a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / petrus_engine-0.1.0-py3-none-any.whl
| Download URL | petrus_engine-0.1.0-py3-none-any.whl |
|---|---|
| Size | 37.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8706d1ce8dba160b973ee7505c0d4f1725e83d39444ec06e521d53939f14f577
|
|
BLAKE2b-256 checksum How to use checksums |
13050bd55c144317ccd8d63fd2af94cae6fa582e59617c8e83ead11e2b766dd5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for petrus-engine 0.1.0
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.