API and rate limits
This reference covers Aqute 0.10.3 for Python 3.11+.
For runnable workflows, start with Choose an API and the usage guide.
Import the engine with from aqute import Aqute.
Constructor
Aqute(handle_coro, workers_count, ...) takes two required arguments.
All remaining parameters are keyword-only. TData and TResult are the handler's
input and output types; public methods preserve these types.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
handle_coro |
Callable[[TData], Coroutine[Any, Any, TResult]] |
Required | Async handler called with one input item per attempt. |
workers_count |
int |
Required | Maximum occupied workers, including rate-limit and retry waits. |
rate_limiter |
RateLimiter or None |
None |
Attempt admission limiter; retries acquire it again. |
result_queue |
asyncio.Queue[AquteTask[TData, TResult]] or None |
None |
Supplied result queue; its capacity also limits the internal result relay. Omission gives each capacity workers_count. |
retry_count |
int |
0 |
Extra attempts after the first handler call. |
retry_delay |
Callable[[int, Exception], float] or None |
None |
Delay in seconds before a permitted retry; omission means zero delay. |
specific_errors_to_retry |
Exception class, tuple of exception classes, or None |
None |
Eligible handler errors; None selects all caught handler errors while retry budget remains; () selects none. |
errors_to_not_retry |
Exception class, tuple of exception classes, or None |
None |
Excluded errors; takes precedence over the retry selection. |
start_timeout_seconds |
int, float, or None |
None |
Limit on waiting for the first input after start; None disables the limit; prequeued inputs need no wait, even at zero or negative values. |
input_task_queue_size |
int or None |
None |
Pending input capacity; None means workers_count, zero means unlimited. |
use_priority_queue |
bool |
False |
Order admitted pending tasks by increasing task_priority. |
task_timeout_seconds |
int, float, or None |
None |
Timeout per handler attempt, excluding limiter and retry waits; None disables it. |
total_failed_tasks_limit |
int or None |
None |
Stop when collected terminal failures reach this inclusive limit; None disables it. |
Queue limits count items, not bytes or caller-retained data. See buffering for the complete bound and errors, retries, and timeouts for failure behavior.
Helper options
process_all(items, *, submission_batch_size=1) returns an awaited list in input
order. iter_results(items, *, submission_batch_size=1) returns an async context
for results in completion order. Both accept Iterable[TData] and
AsyncIterable[TData]. submission_batch_size is a positive integer; larger
batches can improve throughput for small tasks at the cost of result latency.
Handlers still receive one item per call. See streaming cleanup.
Rate limiters
Available implementations are in aqute.ratelimiter:
TokenBucketRateLimitercontrols a shared rate, with optional bursts.SlidingRateLimiterlimits calls within a moving time window.PerWorkerRateLimiterapplies a separate token bucket to each worker.RandomizedIntervalRateLimiteradds bounded random delays after rolling-cap waits.PausableRateLimiterwraps any limiter with a shared monotonic pause.
For TokenBucketRateLimiter and SlidingRateLimiter, max_rate applies over
time_period seconds; the default period is one second. To allow five grants
in each rolling 0.2-second window:
from aqute.ratelimiter import SlidingRateLimiter
limiter = SlidingRateLimiter(max_rate=5, time_period=0.2)
The sliding limiter can grant all five permits together. A token bucket with
allow_burst=False (the default) instead spaces grants by
time_period / max_rate seconds.
These limits apply to limiter grants; a pause wrapper can delay handler starts
after a grant, as described below.
Shared pauses
To pause attempt admission after service throttling, share one wrapper:
from aqute.ratelimiter import PausableRateLimiter, TokenBucketRateLimiter
limiter = PausableRateLimiter(TokenBucketRateLimiter(max_rate=10))
engine = Aqute(handle, workers_count=4, rate_limiter=limiter)
# Inside handle(), before raising a retryable throttle error:
limiter.pause_for(2.0)
Call pause_for() in the handler before raising the retryable throttle error;
the result consumer receives only terminal outcomes and cannot pause earlier retries.
pause_for(seconds) extends the pause from time.monotonic() now.
pause_until(deadline) takes an absolute deadline from that same clock.
Both accept finite, nonnegative values and raise ValueError otherwise.
The later deadline wins; a shorter pause cannot shorten the current one.
Zero seconds and past deadlines add no wait. The read-only paused_until
property starts at zero and retains the latest deadline after it expires.
Use the wrapper on one event loop. Each acquire() checks the deadline in a loop
before and after acquiring the inner limiter, including extensions set while
either wait is active. Cancellation propagates. Requests already admitted are
not interrupted. Inner grants delayed by a pause can resume together; the wrapper
does not reacquire their permits or guarantee request spacing after that delay.
The pause is outside task_timeout_seconds. A throttled attempt still consumes
retry_count; select retryable errors and allow enough retries in the application.
SDK-internal retries inside the handler multiply requests without acquiring the
limiter again. The HTTP recipe parses
Retry-After seconds or HTTP dates in application code and applies the shared
pause. Missing or invalid headers use a one-second fallback; past dates use zero.
The example applies no upper bound to valid Retry-After delays. Apply any
required delay cap in application code.
Randomized intervals
RandomizedIntervalRateLimiter(N, T) grants at most N acquisitions in each
rolling T seconds. Each acquisition waits for quota, then for its full additional
random delay. This delay also applies at startup, with sparse traffic and after
idle. Quota waits and event-loop scheduling can increase the total wait beyond the
configured jitter bounds. Sustained throughput can be below N / T.
mean_target_multiplier and std_dev describe the Gaussian input, before scaling
and bounding. They do not specify the mean or deviation of emitted intervals.
An independent uniform phase supplies an absolute sine scale for each acquisition.
The same scale multiplies the Gaussian input and lower_upper_fluctuation, which
moves both multiplier bounds inward. The resulting bounded multiplier converts
to seconds through T / N. Use nonnegative bounds and fluctuation, with
2 * lower_upper_fluctuation <= upper_multiplier_bound - lower_multiplier_bound.
The lower bound remains an additional minimum delay even when quota is available.
Independent phases replace the previous request-counter ordering while retaining coupled amplitude and bound modulation. Seeded sequences and startup delays change. This reduces conspicuous counter-linked regularity; it does not model human behavior or guarantee avoidance of detection. Removing the old ordering can also reduce throughput, even when the overall delay distribution is similar.
Custom limiters
A custom limiter implements async acquire(name="", task=None). It must propagate
cancellation. CPU-heavy or blocking work in a handler blocks the event loop;
Aqute does not move it to threads or processes automatically.
Synchronous I/O handlers
For a synchronous I/O handler, wrap the call in an async handler with
return await asyncio.to_thread(fn, item). The default executor caps its threads
at min(32, (cpu_count or 1) + 4): Python 3.11–3.12 uses os.cpu_count(), while
Python 3.13+ uses os.process_cpu_count(). Effective concurrency can therefore be
below workers_count. If needed, configure a custom ThreadPoolExecutor(max_workers=...)
with asyncio.get_running_loop().set_default_executor(executor) before submitting
work, and own its shutdown. See Python's executor defaults
and to_thread.
Cancellation or an Aqute timeout does not stop an already running thread; retries
can overlap the original call, so use operation-level timeouts and safe retry
policies. Threads generally do not make CPU-bound Python work parallel under the GIL.
Lower-level worker queues
For lower-level worker queues,
aqute.worker.Foreman exposes start(), add_task(AquteTask(...)),
get_handled_task(), finalize(), and stop(). Consume finite result queues while
waiting for finalize().
Cancellation of Foreman.add_task() can race completed queue admission. It does
not prove that the task was not accepted or prevent its processing.
Inspect error and result on tasks returned directly by Foreman. It does not
set success; unwrap() requires the engine's terminal success flag to return
a value.