Which waiting job may start now?
Fair admission of work to shared capacity, for asyncio Python:
priority, fairness between tenants, and limits that hold across every process.
Why wicket
One tenant submits a thousand jobs, another submits one. A plain queue makes the second wait behind the first thousand. wicket sits beside the queue you already have and tells your workers what to take next.
- Fair from the first grant. Tenants get their turns by Start-time Fair Queuing; one who comes back after a break gets no burst of turns.
- Priority you can read. A weighted sum of factors: whose turn it is, a trait such as the plan tier, how long the job has waited.
- Limits across processes. Concurrency and rate hold for any number of workers: every grant is a compare-and-swap on the pool's version.
- Safe when a worker dies. Its lease ends and the job is delivered again; a fencing token rejects the old worker's late write.
- Small. One dependency, msgspec. An in-memory store, and a PostgreSQL one that commits a job in your own transaction.
wicket does not run the work, retry it or keep its status. It decides what starts.
Quick start
pip install wicket
import asyncio
from datetime import timedelta
from wicket import (
Concurrency,
FairShare,
MemoryBacklog,
MemoryLedger,
MemoryState,
Pool,
Priority,
Ticket,
)
async def main() -> None:
state = MemoryState()
backlog, ledger = MemoryBacklog(state), MemoryLedger(state)
# alice floods the queue first, bob asks once
for n in range(3):
await backlog.submit("reports", Ticket(id=f"alice-{n}", tenant="alice"))
await backlog.submit("reports", Ticket(id="bob-0", tenant="bob"))
pool = Pool(
ledger,
"reports",
priority=Priority((FairShare(), 1)),
limits=(Concurrency(2),),
lease=timedelta(seconds=30),
poll=timedelta(milliseconds=50),
)
async def worker() -> None:
async with pool.permit() as permit: # waits for its turn, keeps the lease while it runs
print("started", permit.waiting.ticket.id)
await asyncio.sleep(0.1) # the work
await asyncio.gather(*(worker() for _ in range(4)))
asyncio.run(main())
started alice-0
started bob-0
started alice-1
started alice-2
Bob submitted last and starts second. Only a block that ends normally releases its ticket; after an error the ticket comes back once its lease ends.
Priority and limits
Factors score tickets in 0..1 and are weighted; limits hold together:
from wicket import Age, Rate, Trait, TraitFactor
pool = Pool(
ledger,
"materials",
priority=Priority(
(FairShare(), 4),
(TraitFactor("tier"), 2),
(Age(full_after=timedelta(minutes=30)), 1),
),
limits=(Concurrency(4), Rate(10, per=timedelta(minutes=1))),
lease=timedelta(minutes=2),
poll=timedelta(seconds=1),
)
ticket = Ticket("task-1", "alice", traits=(Trait("tier", 1.0), Trait("urgent", 0.5)))
Trait(name, value) is an immutable number in 0..1 on a ticket. TraitFactor(name) scores
tickets by that named value, or 0 when absent. A ticket takes a tuple of traits with unique names;
neither the tuple nor its traits can change. Tickets encode and decode with msgspec without hooks.
PostgreSQL
The extra brings SQLAlchemy; bring your driver, asyncpg or psycopg.
pip install "wicket[postgres]"
from wicket.postgres import PostgresBacklog, PostgresLedger, metadata
# in your Alembic env.py: target_metadata = [your_metadata, metadata]
async with session.begin(): # your transaction: the row and its ticket commit together
session.add(row)
await session.flush() # the row gets its id
await PostgresBacklog(session).submit("materials", Ticket(id=str(row.id), tenant=str(user_id)))
ledger = PostgresLedger(engine, retention=timedelta(hours=1)) # the same retention in every process
Upgrading from 0.1.0 to 0.2.0
This release changes ticket traits and the Ledger contract. Upgrade all producers, workers and custom adapters sharing a pool together. Existing databases need the submission ID migration before using the updated adapters; follow its stop, migrate and upgrade instructions before resuming work.
Status
wicket is pre-alpha: the API may change before 1.0, and every change is in the changelog.
Documentation
- Architecture: how the code is laid out and the rules it keeps.
- Decisions: why it is shaped this way.
Contributing
Issues and pull requests are welcome. Read CONTRIBUTING.md first, and follow the Code of Conduct. Report vulnerabilities privately, as the security policy says.
License
Metadata
Release files for wicket 0.2.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 | Actions |
|---|---|---|---|---|
| wicket-0.2.0.tar.gz | 16.4 kB | With provenance | Details for wicket-0.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| wicket-0.2.0-py3-none-any.whl | Python 3 | none | any | Details for wicket-0.2.0-py3-none-any.whl |
Total release size: 36.3 kB
Release files / wicket-0.2.0.tar.gz
| Download URL | wicket-0.2.0.tar.gz |
|---|---|
| Size | 16.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4f92ce910f695efa25f0f7519dd6861e3f2992d769fbb2530af57a4469907f46
|
|
BLAKE2b-256 checksum How to use checksums |
1cbaccb15033f8788f9abe43ad1c1aa6dc4f6adc016407321e38e134310785c8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 25, 2026.
Transparency logRelease files / wicket-0.2.0-py3-none-any.whl
| Download URL | wicket-0.2.0-py3-none-any.whl |
|---|---|
| Size | 19.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
98b1e43d23cb951d9e3c34163e1518e0191d617d8ce9d0eebd9c426dfd16e544
|
|
BLAKE2b-256 checksum How to use checksums |
a9a92b52c564396e26d254f18a856a66c8ae2c0d6d6cfbeb1dc7a6dcdbcef792
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 25, 2026.
Transparency logSecurity for wicket 0.2.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.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for wicket-0.2.0.tar.gz
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 Oct 25, 2026.
Transparency logA 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.
To learn more about protecting yourself from malicious actors, visit our package security documentation.