The PyPI package is queen-mq; the import name is queen. It is fully async, built on httpx,
and ships a py.typed marker. Version 1.0.0, aligned with the broker.
from queen import Queen, Admin, Stream, token_bucket_gate, sliding_window_gateThe package also exports the typed dicts Message, AckResponse, BufferStats, DLQResponse,
TransactionResponse, and the default dicts CLIENT_DEFAULTS, QUEUE_DEFAULTS,
CONSUME_DEFAULTS, POP_DEFAULTS, BUFFER_DEFAULTS.
Constructor
Queen(config=None, *, urls=None, url=None, timeout_millis=None, retry_attempts=None,
retry_delay_millis=None, load_balancing_strategy=None, affinity_hash_ring=None,
enable_failover=None, health_retry_after_millis=None, bearer_token=None,
headers=None, retry_429=None, transport=None)config may be a URL string, a list of URL strings, or a dict. Keyword arguments override
whatever config supplied; a None keyword is ignored rather than treated as an override. URLs
are validated. Neither url nor urls raises ValueError("Must provide urls or url in configuration").
| Argument | Default | Effect |
|---|---|---|
url |
none | Single broker URL. |
urls |
none | Multiple brokers. More than one entry activates the load balancer. |
timeout_millis |
30000 |
Per-request deadline. |
retry_attempts |
3 |
Attempts for 5xx and network failures. |
retry_delay_millis |
1000 |
First retry delay; doubles per attempt. |
load_balancing_strategy |
"affinity" |
"affinity", "round-robin" or "session". |
affinity_hash_ring |
128 |
Virtual nodes per backend on the hash ring. |
enable_failover |
True |
On a 5xx or network error, try the next backend. |
health_retry_after_millis |
5000 |
How long a backend stays marked unhealthy. |
bearer_token |
None |
Sent as Authorization: Bearer <token>. |
headers |
{} |
Extra headers on every request. |
retry_429 |
None |
HTTP 429 backoff policy. See below. |
transport |
None |
httpx.BaseTransport override, e.g. httpx.MockTransport, so tests need no broker. |
The load-balancing arguments are inert with a single URL: the LoadBalancer is only constructed
when urls has more than one entry.
retry_attempts and failover are mutually exclusive paths. With one URL, or with
enable_failover=False, a call makes up to retry_attempts attempts against the same backend
(3 by default). With several URLs and failover on, retry_attempts is not consulted at all: the
call walks the backend list, trying each at most once.
retry_429
A plain dict; see queen.types.Retry429Config. Separate from retry_attempts, which covers 5xx
and network faults.
| Key | Default | Effect |
|---|---|---|
max_attempts |
10 for ordinary requests; unbounded for a long-poll pop |
Total attempts including the first. Setting it applies the same bound to both kinds. |
base_ms |
500 |
First delay absent a Retry-After header. Doubles per attempt. |
cap_ms |
30000 |
Ceiling for the exponential delay. |
A Retry-After header (seconds) wins over the computed delay. Every delay gets ±20% jitter. A
429 is retried against the same backend and never triggers failover.
Queen
| Member | Signature | Returns |
|---|---|---|
queue |
queue(name=None) |
QueueBuilder |
admin |
property | Admin, lazily created, one per client |
transaction |
transaction() |
TransactionBuilder |
ack |
async ack(message, status=True, context=None) |
dict (see below) |
renew |
async renew(message_or_lease_id) |
one dict for a scalar input, a list for a list input |
flush_all_buffers |
async flush_all_buffers() |
None |
get_buffer_stats |
get_buffer_stats() |
{"activeBuffers", "totalBufferedMessages", "oldestBufferAge", "flushesPerformed"} |
delete_consumer_group |
async delete_consumer_group(consumer_group, delete_metadata=True) |
broker body, or {"success": True} on a 204 |
update_consumer_group_timestamp |
async update_consumer_group_timestamp(consumer_group, timestamp) |
broker body, or {"success": True} |
close |
async close() |
None. Flushes buffers, cleans up, closes the HTTP client, removes signal handlers |
Queen is an async context manager: async with Queen(url) as client: closes on exit.
ack(message, status, context)
message is a message dict, a transaction-id string, or a list of either. status is True →
completed, False → failed, or a status string passed through verbatim. context accepts
{"group": ..., "error": ...}.
partitionId is mandatory on every message dict. Without it a single ack returns
{"success": False, "error": "Message must have partitionId property to ensure message uniqueness"}
and a batch raises ValueError. A leaseId on the message is forwarded when present. Per-message
statuses inside a batch work by tagging items with _status and _error; if any item carries
either key the whole batch switches to per-item mode.
Return shapes differ between the two paths:
| Call | Success | Rejection |
|---|---|---|
| single | {"success": True, **first_result} |
{"success": False, "error": ...} |
| batch | {"success": True, "results": <broker array>} |
{"success": False, "error": ...} |
| empty list | {"processed": 0, "results": []} |
none |
renew(message_or_lease_id)
Accepts a lease-id string, a message dict, or a list of either. Lease ids are deduplicated in
insertion order: with multi-partition pop, every message in a batch shares one leaseId and one
extend call renews every claimed partition, so passing the whole list issues one HTTP call.
QueueBuilder
client.queue(name) returns a fresh builder. Configuration methods return self; terminal
methods are push(), pop(), consume(), create(), delete(), dlq() and flush_buffer().
Addressing
| Method | Default | Notes |
|---|---|---|
partition(name) |
"Default" |
The ordered lane. Anything other than Default switches pop to the partition-scoped route. |
namespace(name) |
None |
Grouping label at create time; also a pop filter. |
task(name) |
None |
Second grouping label; same dual role. |
group(name) |
None |
Consumer group. Absent, the broker uses __QUEUE_MODE__. |
Queue lifecycle
| Method | Wire call | Returns |
|---|---|---|
config(options) |
none | self. Merges options over QUEUE_DEFAULTS. |
create() |
POST /api/v1/configure |
OperationBuilder |
delete() |
DELETE /api/v1/resources/queues/:queue |
OperationBuilder; raises ValueError with no queue name |
QUEUE_DEFAULTS uses snake_case keys and the builder converts them to the wire’s camelCase
before sending:
| Key | Default | Sent as | Unit |
|---|---|---|---|
lease_time |
300 |
leaseTime |
seconds |
retry_limit |
3 |
retryLimit |
attempts |
delayed_processing |
0 |
delayedProcessing |
seconds |
window_buffer |
0 |
windowBuffer |
seconds |
retention_seconds |
0 |
retentionSeconds |
seconds |
completed_retention_seconds |
0 |
completedRetentionSeconds |
seconds |
encryption_enabled |
False |
encryptionEnabled |
none |
Any extra key you pass to config() is converted the same way and sent, so options absent from
the defaults dict are still reachable. /configure is a full replace: keys absent from the
body reset to the broker’s defaults, so send the complete intended configuration every time. The
full option list is in Reference.
Producing
| Method | Default | Notes |
|---|---|---|
buffer(options) |
off | {"message_count": 100, "time_millis": 1000}. Turns push() into an enqueue. |
push(payload) |
none | Returns a PushBuilder; payload is one dict or a list of dicts. Raises ValueError with no queue name. |
Each item may carry data, payload, or be used as the payload itself. The client mints a
UUIDv7 transactionId when the item does not supply one, and forwards traceId only when it is
a valid UUID.
res = await client.queue("test-queue-v2").push([{"data": {"message": "Hello, world!"}}])PushBuilder
Awaitable: await executes the push. on_success(cb), on_error(cb) and on_duplicate(cb)
register callbacks and return the builder. Awaiting a non-buffered push resolves to the broker’s
per-item array; a buffered push resolves immediately to a buffered marker without sending
anything.
Consuming
| Method | pop() default |
consume() default |
Notes |
|---|---|---|---|
batch(size) |
1 |
1 |
Clamped to ≥ 1. With partitions(n) it is the total budget across all claimed partitions. |
partitions(n) |
1 |
1 |
Claim up to N partitions per call; all share one leaseId. Sent only when > 1. |
wait(enabled) |
False |
True |
Long-poll. |
timeout_millis(ms) |
30000 |
30000 |
Server-side long-poll deadline. The HTTP call adds 5 s of slack so the server times out first. |
auto_ack(enabled) |
False |
True |
The two meanings differ. See the warning. |
subscription_mode(mode) |
None |
None |
all | new. |
subscription_from(from_) |
None |
None |
"now" or an ISO timestamp. |
concurrency(count) |
n/a | 1 |
consume() only. Worker tasks. Clamped to ≥ 1. |
limit(count) |
n/a | None |
consume() only. Stop after N messages per worker. |
idle_millis(ms) |
n/a | None |
consume() only. Stop after N ms without a message. |
renew_lease(enabled, interval_millis=None) |
n/a | False |
consume() only. |
each() |
n/a | batch mode | consume() only. One message per handler call. |
pop()
async pop() → list[dict], [] when there is nothing to return.
res = await client.queue("test-queue-v2-pop-non-empty").batch(1).wait(True).pop()Route selection: queue plus non-Default partition uses
/api/v1/pop/queue/:queue/partition/:partition; queue alone uses /api/v1/pop/queue/:queue;
namespace or task without a queue uses /api/v1/pop. None of the three raises ValueError.
consume(handler, *, signal=None)
Returns a ConsumeBuilder, awaitable. signal is an asyncio.Event; setting it stops every
worker at the top of its next iteration. on_success(cb) and on_error(cb) register callbacks.
await client.queue("orders").group("workers").batch(20).each().consume(handle)Worker loop behaviour:
- A handler exception with
auto_ackon nacks the message and does not re-raise, so the worker continues. - In
each()mode a nack abandons the rest of the popped batch: the nack released the lease and clamped the broker cursor, so the remainder will be redelivered anyway. - A 429 that escapes the HTTP layer’s own retry sleeps for
Retry-After(or 1 s) and continues. - A network error sleeps and continues.
- A 403 stops the worker and re-raises.
message["trace"]
Messages delivered through consume() gain a trace coroutine that posts to /api/v1/traces:
await msg["trace"]({"traceName": ["tenant-acme"], "eventType": "info", "data": {"step": 1}})data is required, traceName accepts a string or list of strings, eventType defaults to
"info". It never raises; failures return {"success": False, "error": ...}.
Buffering
| Method | Notes |
|---|---|
buffer(options) |
Enable per-queue/partition buffering for this builder’s pushes. |
flush_buffer() |
async. Flush this builder’s buffer. Raises ValueError with no queue name. |
A buffer flushes at message_count messages or time_millis after its first message, whichever
comes first. Buffers are keyed on "<queue>/<partition>", so builders addressing the same queue
and partition share one.
Dead-letter queue
dlq(consumer_group=None) returns a DLQBuilder. Raises ValueError with no queue name.
| Method | Default |
|---|---|
limit(count) |
100 |
offset(count) |
0 |
from_(timestamp) |
none (trailing underscore, from is a keyword) |
to(timestamp) |
none |
get() |
async → {"messages": [...], "total": n} |
Read-only. Replay lives on the admin façade: admin.retry_message(partition_id, transaction_id).
TransactionBuilder
client.transaction(). Pushes and acks in one PostgreSQL transaction, all-or-nothing.
| Method | Notes |
|---|---|
ack(messages, status="completed", context=None) |
One message or a list. Requires transactionId and partitionId; a leaseId is collected into requiredLeases. Returns self. |
queue(queue_name) |
Returns a TransactionQueueBuilder with .partition(key) and .push(items); push() returns the parent builder. |
commit() |
async. Raises on an empty bundle and on a non-success response. |
await (client.transaction()
.ack(msg)
.queue("orders.enriched").push([{"data": {"id": 1}}])
.commit())Admin
client.admin (property) or Admin(http_client). Every method is async and returns the
broker’s JSON body verbatim. Methods taking **params build a query string, dropping None.
Resources
| Method | Route |
|---|---|
get_overview() |
GET /api/v1/resources/overview |
get_namespaces() |
GET /api/v1/resources/namespaces |
get_tasks() |
GET /api/v1/resources/tasks |
list_queues(**params) |
GET /api/v1/resources/queues |
get_queue(name) |
GET /api/v1/resources/queues/:name |
Messages and traces
| Method | Route |
|---|---|
list_messages(**params) |
GET /api/v1/messages |
get_message(partition_id, transaction_id) |
GET /api/v1/messages/:partitionId/:transactionId |
delete_message(partition_id, transaction_id) |
DELETE /api/v1/messages/:partitionId/:transactionId |
retry_message(partition_id, transaction_id) |
POST /api/v1/messages/:partitionId/:transactionId/retry |
get_trace_names(**params) |
GET /api/v1/traces/names |
get_traces_by_name(trace_name, **params) |
GET /api/v1/traces/by-name/:traceName |
get_traces_for_message(partition_id, transaction_id) |
GET /api/v1/traces/:partitionId/:transactionId |
Status and analytics
| Method | Route |
|---|---|
get_status(**params) |
GET /api/v1/status |
get_queue_stats(**params) |
GET /api/v1/status/queues |
get_queue_detail(name, **params) |
GET /api/v1/status/queues/:name |
get_analytics(**params) |
GET /api/v1/status/analytics |
get_system_metrics(**params) |
GET /api/v1/analytics/system-metrics |
get_worker_metrics(**params) |
GET /api/v1/analytics/worker-metrics |
get_postgres_stats() |
GET /api/v1/analytics/postgres-stats |
Consumer groups
| Method | Route |
|---|---|
list_consumer_groups() |
GET /api/v1/consumer-groups |
get_consumer_group(name) |
GET /api/v1/consumer-groups/:name |
get_lagging_consumers(min_lag_seconds=60) |
GET /api/v1/consumer-groups/lagging?minLagSeconds= |
delete_consumer_group_for_queue(consumer_group, queue_name, delete_metadata=True) |
DELETE /api/v1/consumer-groups/:group/queues/:queue |
seek_consumer_group(consumer_group, queue_name, options) |
POST /api/v1/consumer-groups/:group/queues/:queue/seek |
refresh_consumer_stats() |
POST /api/v1/stats/refresh |
seek_consumer_group posts options verbatim: {"toEnd": True} or {"timestamp": "<RFC3339>"}.
System
| Method | Route |
|---|---|
health() |
GET /health |
metrics() |
GET /metrics |
get_maintenance_mode() |
GET /api/v1/system/maintenance |
set_maintenance_mode(enabled) |
POST /api/v1/system/maintenance |
get_pop_maintenance_mode() |
GET /api/v1/system/maintenance/pop |
set_pop_maintenance_mode(enabled) |
POST /api/v1/system/maintenance/pop |
Streaming SDK
Stream builds an immutable operator chain over a queue and runs it against /streams/v1/*.
Every combinator returns a new Stream.
Stream.from_(client.queue("orders"), **options) # or Stream.from_queue(...)| Group | Methods |
|---|---|
| Stateless | map(fn), filter(predicate), flat_map(fn) |
| Keying | key_by(fn) (at most one, must precede window / reduce / gate) |
| Windows | window_tumbling(**opts), window_sliding(**opts), window_session(**opts), window_cron(**opts) (at most one) |
| Reduce | reduce(fn, initial=None), aggregate(extractors) (require a preceding window; at most one) |
| Gate | gate(fn) (at most one, incompatible with windowing and reduce) |
| Sink | to(sink_queue_builder, partition=None), foreach(fn) (must be last in the chain) |
| Terminal | run(query_id, url, **opts) |
Window keyword arguments mirror the JavaScript names in snake_case and are converted before
they reach the wire. token_bucket_gate(...) and sliding_window_gate(...) return ready-made
gate functions.
run(query_id, url, **opts)
async, returns a Runner. query_id and url are positional-or-keyword and both required.
An empty query_id raises ValueError("run requires query_id=...").
| Option | Default |
|---|---|
batch_size |
200 |
max_partitions |
4 |
max_wait_millis |
1000 |
subscription_mode |
None ('all' | 'new') |
subscription_from |
None (ISO timestamp or 'now') |
consumer_group |
streams.<query_id> |
reset |
False (wipe state on a config-hash mismatch) |
on_error |
None (callable(err, ctx)) |
bearer_token |
None |
logger |
None |
Options are passed as snake_case and mapped onto the runner’s camelCase keys internally. The
chain shape is fingerprinted into a config hash; re-deploying a different chain under the same
query_id is rejected at registration unless reset=True. Only operator kinds and their
structural config are hashed, not the bodies of your functions.
Runner exposes await stop() and metrics().
Logging
Off unless QUEEN_CLIENT_LOG=true is set in the environment. Levels are emitted through the
module logger in queen.utils.logger.
Errors
HTTP failures raise httpx exceptions; the client attaches the parsed body’s code to the
exception when present. The proxy codes are rate_limited and quota_exceeded on 429, and
cluster_suspended, storage_quota_exceeded, feature_gated, forbidden on 403. A 204
response yields None: the broker sends no body at all on 204.