Skip to main content

cronoq for Python

PyPI Python License

An elastic, priority-aware task scheduler for asyncio, powered by a Rust engine. cronoq decides when your coroutines run, in what order and with how much concurrency: priorities with aging, deadlines, fixed start times and cron, dependencies, retries with backoff, shared rate limits, and autoscaling, all in-process, without extra infrastructure.

pip install cronoq

Why

  • Important work first, and batch work still finishes. Four priority classes; aging lets long-waiting background work overtake a steady stream of ordinary work, and deadlines raise urgency as they approach.
  • Respect rate limits. Token buckets and semaphores shared across pools gate dispatch, so a burst never turns into a 429 storm; Retryable(retry_after=...) honours Retry-After.
  • Scheduled jobs with capacity kept for them. Fixed-start tasks reserve capacity ahead of their start and get a worker held for them; cron schedules with time zones.
  • Pipelines without glue code. Dependencies by key with success/failure/completion edges, atomic batches, idempotent keys.
  • Elastic concurrency. Pools scale between min and max from queue pressure and wait-time objectives.
  • Fast. The engine makes about a million scheduling decisions per second; payloads stay Python objects in memory, and are serialized only when a durable store is configured.

Quickstart

import asyncio
import cronoq

async def call_llm(ctx: cronoq.TaskContext, prompt: str) -> str:
    # raise cronoq.Retryable("429", retry_after=2.0) to back off,
    # raise cronoq.Permanent("bad input") to fail without retrying
    await asyncio.sleep(0.1)
    return f"answer to {prompt!r}"

async def main() -> None:
    pools = [cronoq.Pool("llm", call_llm, min=1, max=16)]
    limits = [cronoq.Resource.token_bucket("openai", rate=50.0, burst=100)]
    async with cronoq.Scheduler(pools, resources=limits) as sched:
        chat = await sched.submit("hi", pool="llm", priority="high", deadline_in=2.0,
                                  resources={"openai": 1})
        batch = [await sched.submit(f"doc {i}", pool="llm", priority="low",
                                    resources={"openai": 1}) for i in range(10)]
        print(await chat)
        print(len([await h for h in batch]), "batch items done")
        print((await sched.snapshot()).pool("llm"))

asyncio.run(main())
  • A scheduler is bound to the event loop that started it: use async with cronoq.Scheduler(...) (or await sched.start() / await sched.shutdown()) inside that loop. If the loop closes without a shutdown, the scheduler stops and further calls raise cronoq.SchedulerError. Leaving async with waits up to shutdown_grace seconds (default 5) for running attempts.
  • Executors are async def run(ctx, payload) coroutines and run on that loop. ctx has id, attempt, kind, key, pool, tags, timeout, deadline (Unix seconds), is_cancelled() and heartbeat().
  • Plain (sync) functions are accepted too and run in the loop's default thread pool. A thread cannot be interrupted: on timeout or cancellation the attempt is aborted, but the function keeps running until it returns, and the next attempt may overlap with it. Check ctx.is_cancelled() in long-running sync executors.
  • Payloads and results are arbitrary Python objects. They are serialized only when the scheduler has a durable store (see Durable state).
  • Tasks without pool= go to the scheduler's default_pool: the pool named "default", or the first pool.
  • cronoq.Retryable / cronoq.Permanent control retries; any other exception is retryable. Awaiting a task that did not succeed raises cronoq.TaskFailed (.status, .message). All cronoq exceptions can be pickled.
  • Cancelling a task, a timeout, or a lost lease cancels the executor's asyncio task. A CancelledError the executor raises on its own (not caused by cronoq) is a retryable failure. An executor that swallows CancelledError keeps running and may overlap with the retry.
  • Invalid durations or configuration (negative, NaN, out of range) raise ValueError.
  • cronoq.compat.ElasticPriorityScheduler is a synchronous, thread-safe API for code written against ElasticPriorityScheduler.
  • Wheels use the stable ABI (abi3, CPython 3.14+). Free-threaded (no-GIL) CPython builds are not supported.

Status events

sched.events() is an async iterator of cronoq.StatusEvent (id, pool, key, status, attempt, at in Unix seconds, message). It delivers terminal events (succeeded, failed, dead_lettered, cancelled, ...) by default, and every transition (ready, running, retrying, ...) when the scheduler is created with emit_transitions=True.

async with sched.events() as events:          # or: events = sched.events(); ...; events.close()
    async for e in events:
        print(e.id, e.status, e.message)
  • Each stream buffers up to event_capacity events (a Scheduler argument, default 1024). When the consumer falls behind, the oldest events are dropped and counted in events.lagged. With sched.events(on_lag="raise") the next iteration raises cronoq.EventsLagged (.skipped); iterating again continues with the buffered events.
  • A stream ends (StopAsyncIteration) when the scheduler shuts down or the stream is closed.

Durable state

Scheduler(pools, store=cronoq.SqliteStore("state.db")) keeps queued work, schedules and results in a SQLite file across restarts. Payloads and results are stored as JSON unless you pass codec=; several processes can share the file, with one leading and the others standing by.

More: Python quickstart · use cases · API reference.

Client and remote workers for cronoq-server

pip install "cronoq[client]"

The extra installs grpcio, which has no prebuilt wheel for Windows on arm64. The tour below uses top-level await: run it with python -m asyncio, or put it inside an async def.

import os
from cronoq.client import CancelTask, Client, Reject, Worker, task

# Submit and inspect (client token; the server requires tokens of at least 16 characters).
async with Client("localhost:7070", token=os.environ["CRONOQ_CLIENT_TOKEN"]) as c:
    sub = await c.submit({"prompt": "hi"}, pool="llm", priority="high",
                         deadline_in=30, timeout=60, key="job-1")
    result = await c.wait(sub.id, timeout=120)        # Result(id, status, output, message, ...)
    print(result.status, result.json())

    done = await c.submit("quick", wait=True, wait_timeout=10)   # submit and wait in one call
    info = await c.get("job-1")      # TaskInfo: status, pool, key, kind, attempt, timestamps, ...

    await c.submit_batch([task("a", key="a"), task("b", key="b", after=["a"])])
    async for event in c.watch(pools=["llm"], keys=["job-1"], all_transitions=True):
        ...                          # every transition of job-1 (default: terminal statuses only)

# Serve a pool (worker token).
async def handle(lease):  # lease.payload / .json() / .text(), .attempt, .pool, .tags, .cancelled
    if not can_handle(lease):
        raise Reject("missing model")    # hand the lease back unrun
    if obsolete(lease):
        raise CancelTask("superseded")   # end the task as cancelled
    return {"answer": 42}                # bytes, str, JSON-serialisable value or None

worker = Worker("localhost:7070", "llm", handle, slots=8,
                token=os.environ["CRONOQ_WORKER_TOKEN"])
await worker.run()                  # reconnects with backoff; `await worker.stop()` drains
  • Payloads: bytes are sent as-is, str as UTF-8 text, other values as JSON (application/json). Outputs are encoded the same way and carry their content type.
  • Handlers raise cronoq.Retryable(msg, retry_after=...) or cronoq.Permanent(msg); any other exception is a retryable failure. An output larger than the server's max_message_bytes is a permanent failure.
  • Reject(reason) gives the lease back: the server offers it, within the same attempt, to another worker of the pool with a free slot; if there is none, the attempt fails as retryable and counts against the retry budget. CancelTask(reason) ends the task as cancelled.
  • The worker speaks protocol version 3. The server may grant fewer slots than requested (worker.granted_slots, capped by limits.max_slots_per_worker); leases beyond the grant are handed back. When the server announces it is shutting down (worker.last_shutdown) the worker reconnects with backoff, or immediately when leadership moved to another node.
  • Each running lease is heartbeated every lease.lease_ttl / 3; when the server aborts an attempt (timeout, cancellation, lease expiry) the handler task is cancelled and lease.cancelled is set.
  • Failed calls raise cronoq.client.ClientError with the gRPC status name in .code (e.g. unauthenticated, not_found from get/wait on an unknown task, resource_exhausted when a pool is full, unavailable when the server is not the leader or is shutting down); .retryable is true for the last two. Client.wait and Client.submit(..., wait=True) raise TimeoutError when the wait times out (the server caps waits at limits.max_wait_s), as does Client.submit(..., call_timeout=...).
  • Client.snapshot() (metrics or admin token) returns per-pool figures, totals and workers_connected.
  • Client and Worker can be created outside a running loop; they connect on first use. Worker.stop() can be called more than once (callers wait for the same drain), and a stopped worker can be run again. A handler that swallows CancelledError keeps running after an abort and may overlap with the retry of the same task.

Migrating from ElasticPriorityScheduler

from cronoq.compat import ElasticPriorityScheduler, Priority, Task

A synchronous, thread-safe drop-in for the original API. See the migration guide.

Development

python3 -m venv .venv
.venv/bin/pip install maturin pytest pytest-asyncio grpcio protobuf mypy
.venv/bin/maturin develop -m crates/cronoq-py/Cargo.toml
cd crates/cronoq-py && ../../.venv/bin/python -m pytest
../../.venv/bin/python -m mypy.stubtest cronoq._cronoq   # stubs match the extension

python/tests/test_client.py starts a real cronoq-server (building it with cargo if needed, or using CRONOQ_SERVER_BIN). The stubs in cronoq/_proto are generated by scripts/gen_proto.sh. See CONTRIBUTING.md.

License

MIT OR Apache-2.0.

Metadata

Release files for cronoq 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cronoq 0.2.1
File Size Uploaded Provenance
cronoq-0.2.1.tar.gz 285.3 kB With provenance Details

Built distributions (wheels)

Table of built distributions (wheels) for cronoq 0.2.1
File
cronoq-0.2.1-cp314-abi3-win_arm64.whl CPython 3.14 abi3 Windows ARM64 Details
cronoq-0.2.1-cp314-abi3-win_amd64.whl CPython 3.14 abi3 Windows x86-64 Details
cronoq-0.2.1-cp314-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.14 abi3 Linux glibc 2.17+ x86-64 Details
cronoq-0.2.1-cp314-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.14 abi3 Linux glibc 2.17+ ARM64 Details
cronoq-0.2.1-cp314-abi3-macosx_11_0_arm64.whl CPython 3.14 abi3 macOS 11.0+ ARM64 Details
cronoq-0.2.1-cp314-abi3-macosx_10_12_x86_64.whl CPython 3.14 abi3 macOS 10.12+ x86-64 Details

Total release size: 16.6 MB

Release files / cronoq-0.2.1.tar.gz

Download URL cronoq-0.2.1.tar.gz
Size 285.3 kB
Tags Source
SHA-256 checksum
How to use checksums
608cd1f3f4dd50d1cf827f81cabf515cebfe4327c526a3a4e77fbc00965d04c3
BLAKE2b-256 checksum
How to use checksums
06c15c6ea9a7246b7870ff054577a1fcb5f27daeea1d96d8e90f363eaed4ebe1
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 27, 2026.

Transparency log

Release files / cronoq-0.2.1-cp314-abi3-win_arm64.whl

Download URL cronoq-0.2.1-cp314-abi3-win_arm64.whl
Size 2.5 MB
Tags CPython 3.14 Windows ARM64 abi3
SHA-256 checksum
How to use checksums
bf9b4f83bad21a66547158d1e9315246d07a22640619689650849326b7c8aa99
BLAKE2b-256 checksum
How to use checksums
72e375859e9b5472eeedf6c4b04b157f77dd73a33c2e3817b8df0eb0f4530435
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 27, 2026.

Transparency log

Release files / cronoq-0.2.1-cp314-abi3-win_amd64.whl

Download URL cronoq-0.2.1-cp314-abi3-win_amd64.whl
Size 2.7 MB
Tags CPython 3.14 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
c19fd81d01bfe94600f260a5ed518cf165d80591bf5f43b3edbb99fee53c0213
BLAKE2b-256 checksum
How to use checksums
01d6b2d78ba57efb0f2715ef2c2a3a374d00bf7323ce90df98f8e2fb0bd87bd5
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 27, 2026.

Transparency log

Release files / cronoq-0.2.1-cp314-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL cronoq-0.2.1-cp314-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 2.9 MB
Tags CPython 3.14 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
7413fcfc18897a8133360c4831ce53880429535a4711303ebe4a128a98e791b6
BLAKE2b-256 checksum
How to use checksums
48e171ddadc5df0087694c3219f5019a7e81e5b7762e8df669e456730da96a19
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 27, 2026.

Transparency log

Release files / cronoq-0.2.1-cp314-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL cronoq-0.2.1-cp314-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 2.8 MB
Tags CPython 3.14 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
9b894a05692cbed3cf0bc52d86f22e275c03504a1ea35a9d5bee795ac918fea9
BLAKE2b-256 checksum
How to use checksums
376aff49539be0f7e51317f97e53375cf200ba4fc037d1f9d5b5e2c5bf5594e5
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 27, 2026.

Transparency log

Release files / cronoq-0.2.1-cp314-abi3-macosx_11_0_arm64.whl

Download URL cronoq-0.2.1-cp314-abi3-macosx_11_0_arm64.whl
Size 2.6 MB
Tags CPython 3.14 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
117d5007d7214a45bb951569a208212ed001393c2c6b6eb6471758e36873527d
BLAKE2b-256 checksum
How to use checksums
74a85966ed2c7aaf79afc4bf8b3cb1cf54d37a561c4c5dcf7899d9ee3a1ce6dd
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 27, 2026.

Transparency log

Release files / cronoq-0.2.1-cp314-abi3-macosx_10_12_x86_64.whl

Download URL cronoq-0.2.1-cp314-abi3-macosx_10_12_x86_64.whl
Size 2.7 MB
Tags CPython 3.14 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
81b1feb1b0141a544f833128e95f5548705d7911527db1deba32f3b7a302f276
BLAKE2b-256 checksum
How to use checksums
ed523d2fd876240730180f7d370c971f7db4b02b4e8055f4db42939107a95f5e
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 27, 2026.

Transparency log

Security for cronoq 0.2.1

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 7 files in this release have been uploaded with provenance metadata.

Select from 7 files

Attestation for cronoq-0.2.1.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 27, 2026.

Transparency log

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.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.1 This release

7 release files Full provenance

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