Skip to main content

grampy

PyPI Python License: MIT CI

A small workflow graph for work queues that already live in a storage — a database, a key-value store, or plain memory: the storage is a driver.

pip install grampy-q
pip install "grampy-q[postgres]"  # + the PostgreSQL drivers (SQLAlchemy)

The distribution is grampy-q — the q is for queue, grampy being taken on PyPI — and it is imported as quazardous.grampy.

Try the brick sorter → — a sorting line where bricks are sorted by colour, TNT bricks get quarantined, defused with retries or thrown away, and a sorted brick sent back waits in a lane before it runs again. It runs the real library in your browser.

You declare a DAG of nodes. Each subject (a job, a request, an offer…) goes through the nodes; a node journal records, per subject and per node, whether the node is running, done, skipped, failed or omitted. Workers claim a node for eligible subjects, conclude it, and the graph decides what becomes claimable next — forks run in parallel, joins wait for their parents.

from dataclasses import dataclass

from quazardous.grampy import Node, NodeJournal
from quazardous.grampy.drivers.memory import MemoryDriver
from quazardous.grampy.items import Adapter, Items

@dataclass
class Order:                                   # your object, as it is
    id: int
    total: float

class Orders(Adapter):
    def id_of(self, order):  return order.id   # the one thing grampy must know

GRAPH = (Node("pay"), Node("ship", parents=("pay",)))
items = Items(NodeJournal(MemoryDriver(), GRAPH), Orders())
orders = [Order(1, 30.0), Order(2, 12.5)]

lease = items.claim("pay", 10, candidates=orders)       # orders in, orders out
for order in lease:
    print("paying", order.id, order.total)
items.conclude("pay", lease)

print(items.progress(orders[0]))                        # {'pay': Status.DONE}
print(list(items.claim("ship", 10, candidates=orders)))  # pay is done: ship is next

That runs as pasted. A larger graph, with subjects that differ:

from dataclasses import dataclass

from quazardous.grampy import Node, NodeJournal, check_dag
from quazardous.grampy.drivers.memory import MemoryDriver
from quazardous.grampy.items import Adapter, Items

DAG = (
    # `working` and `state`: what YOUR status column says while the node runs
    # and once it is done — the journal records `running` and `done`.
    Node("fetch", working="fetching", state="fetched"),
    Node("crop", parents=("fetch",), optional=True),
    Node("read", parents=("fetch",), optional=True),
    Node("judge", parents=("crop", "read"), state="judged"),
)
check_dag(DAG)

@dataclass(frozen=True)
class Doc:
    id: str
    scanned: bool = True

class DocsAdapter(Adapter):                   # how grampy reads YOUR object
    def id_of(self, doc):  return doc.id
    def applies(self, doc, node):             # one graph, subjects that differ
        return node != "crop" or doc.scanned

def my_loader():                              # your query, your ORM
    return [Doc("s1"), Doc("s2", scanned=False)]

items = Items(NodeJournal(MemoryDriver(), DAG), DocsAdapter())
lease = items.claim("fetch", 10, candidates=my_loader())    # objects in…
for doc in lease:                                           # …and objects out
    ...
items.conclude("fetch", lease)

working and state are projections, not commands: allowed_transitions() turns them into the transitions your own status column may take — see the rules.

Handing grampy your objects is the canonical way to use it — see items. The core underneath works on ids alone and stays available: journal.claim("fetch", 10, candidates=[…]) returns subjects, and never reads your data.

Why not a status column?

A status column and a few UPDATEs hold one line of steps, one worker at a time. What grampy adds, each proven by the shared driver contract (quazardous.grampy.testing.JournalContract):

  • Two workers, one subject. A claim is atomic and returns a token; a slow worker whose lease went to another writes nothing (test_concurrent_claimers_never_take_a_subject_twice, test_a_released_lease_cannot_conclude_the_next_one).
  • A graph, not a line. Forks run in parallel, joins wait for their parents, optional steps and exclusive choices do not block what follows (test_the_driver_claims_exactly_what_the_rule_says, test_a_child_waits_for_its_parent_to_conclude).
  • Going back. Forget, replay and loops keep what they take away, with when and why (test_forget_and_release_archive_what_they_take_away).
  • Time. Retries, leases, waits and grace periods, settled by one janitor call (test_a_failure_is_retried_when_due_then_stands).

What it does

Each line links to the rules, where it is spelled out.

joins as data which parent statuses a node accepts, k of n, a step that runs on a failure
exclusive choices a node names its branch; what only the others lead to is omitted at once
loops and history bounded ways back, every row taken away kept with when and why
retries declared backoff — constant, linear or exponential, capped, with jitter
leases per node, given back by journal.expire() when a worker dies
waits and grace settled by a durable signal, recorded even before the wait, or failed at its timeout
groups subjects worked together — five of a colour, ten thousand for one file — a whole group or none
lanes a subject that comes back waits, merges with the version waiting, runs again after a cooldown — a document edited five times in a minute runs once, on its last version
policies one workflow, subjects treated differently: their own retries, leases and budgets
rate and concurrency several bands at once (GCRA, with bursts), a cap per node, per policy
versions subjects pinned to the graph they started on, migrated all or nothing
items speak your objects: handlers name a branch, or give up a step one kind skips
monitoring per node: what a claim could take now, since when the oldest waits, the longest-running work, retries due — a growing, starving or stuck node shows between two samples
drawings Mermaid flowchart, Mermaid state diagram, Graphviz, with live counts

Storage

The journal never commits and never reads your tables. Eligibility is your query, passed to claim and read inside its transaction; a claim gives back ids, and you load your own objects. See drivers.

driver needs
drivers.memory nothing — the reference the others are confronted with
drivers.sqlite the standard library
drivers.postgres SQLAlchemy Core — three table layouts, your choice

Any other storage: implement the driver protocol and pass the shared contract (quazardous.grampy.testing.JournalContract), concurrency tests included.

Next to other tools

grampy is a journal, not a runner: the comparison is about where the state lives. Each cell links the page that says so; is a question that page does not settle. Checked on 2026-09-18.

your own database enqueue in your transaction a server of its own joins between steps Python
grampy yes: memory, SQLite, PostgreSQL yes no yes: all parents, k of n, on a failure yes
Graphile Worker yes, PostgreSQL — (add_job is SQL; rollback not stated) no no, Node.js
BullMQ Redis; PostgreSQL optional no, but Redis or PostgreSQL flows: all children yes, bullmq
Hatchet no, the engine's yes all parents yes
Inngest no, a managed state store yes, or Inngest Cloud in code, Promise.all yes
Temporal no, the service's yes, or Temporal Cloud in code yes
Oban yes yes, Ecto.Multi no Pro workflows yes, oban-py, PostgreSQL only

How each notion maps, tool by tool: the same notions in other tools.

Documentation

Tests

pip install -e ".[test]"        # or: uvx --with pytest pytest
pytest                          # graph, states, memory and SQLite drivers
GRAMPY_TEST_PG_DSN=postgresql+psycopg://user:pass@localhost/test \
    pytest                      # + the postgres driver (needs sqlalchemy and a driver)

License

MIT

Release files for grampy-q 0.6.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 grampy-q 0.6.0
File Size Uploaded
grampy_q-0.6.0.tar.gz 154.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for grampy-q 0.6.0
File Interpreter ABI Platform
grampy_q-0.6.0-py3-none-any.whl Python 3 none any Details

Total release size: 290.9 kB

Release files / grampy_q-0.6.0.tar.gz

Download URL grampy_q-0.6.0.tar.gz
Size 154.2 kB
Tags Source
SHA-256 checksum
How to use checksums
f20a68d201365d110e8ecc2e62b1bd13b464f4d05f246b83d62ee0950856fd18
BLAKE2b-256 checksum
How to use checksums
a2100f0d10600a17f9dea9923b10f7cea69e13c311975b24f8130f5a345a5dc6
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 18, 2026.

Transparency log

Release files / grampy_q-0.6.0-py3-none-any.whl

Download URL grampy_q-0.6.0-py3-none-any.whl
Size 136.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e1c668e5ae2a680e6c01820288b94f631225463291226414e652ec9d4d1e93a5
BLAKE2b-256 checksum
How to use checksums
30c574a28d5ecc93f223f2a7fd073ca2955e16a3db9b7fcb88e17a68043a4cf5
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 18, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.6.0 This release

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

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