Skip to main content

async-limiter

An asyncio rate limiter that enforces two limits at once:
how many requests run at the same time, and how many start per time period.

PyPI CI Python Downloads License

Many APIs publish both kinds of limit, for example "at most 5 requests in flight and at most 100 per minute". DualRateLimiter holds a caller back until both allow it. It has no dependencies.

pip install async-limiter
import asyncio
from async_limiter import DualRateLimiter

limiter = DualRateLimiter(max_concurrent=5, max_requests=100, time_period=60)


async def fetch(i):
    async with limiter:  # waits here until both limits allow the call
        await asyncio.sleep(0.1)  # your API call
        return i


async def main():
    results = await asyncio.gather(*[fetch(i) for i in range(20)])
    print(results)


asyncio.run(main())

Three ways to use it

# As a context manager
async with limiter:
    await call_api()


# As a decorator
@limiter.limit()
async def call_api_limited():
    return await call_api()


# With explicit acquire and release
await limiter.acquire()
try:
    await call_api()
finally:
    limiter.release()

A limiter can be created at module level and shared by every task that calls the same API. Use one limiter per API.

Options

Argument Default Meaning
max_concurrent Most requests allowed in flight at once
max_requests Most requests allowed to start within time_period
time_period Length of the window, in seconds
conservative True Allow one request fewer than max_requests per window
name None Label used in log messages
wait_margin 0.1 Extra seconds to sleep past the moment a slot opens

How the limits behave

  • The window slides. A request may start when fewer than max_requests have started in the last time_period seconds, counted from each request's start.
  • conservative is on by default. With max_requests=100 the limiter lets 99 through per window, to leave a margin for clock differences with the server. Pass conservative=False to use the full limit.
  • Waiting adds a small margin. A caller that has to wait for the window sleeps wait_margin seconds (0.1 by default) past the moment a slot opens. On windows of a second or less this lowers throughput noticeably.
  • A waiting caller holds a concurrency slot. The concurrency limit is taken first, so callers queued on the time limit count as in flight.
  • Cancellation is safe. A caller cancelled while waiting, for example by asyncio.wait_for, gives its slot back.
  • One process, one event loop at a time. The limiter is not shared between processes or threads; acquire() raises RuntimeError if another event loop is running with it. It can be reused by successive asyncio.run() calls, and the request history carries over between them.

Using the full limit

Both safety margins are on by default. To get exactly the numbers you pass in:

limiter = DualRateLimiter(
    max_concurrent=5,
    max_requests=100,
    time_period=60,
    conservative=False,  # allow 100 per window, not 99
    wait_margin=0,  # start as soon as a slot opens
)

Keep the margins when the limit is enforced by a server whose clock and counting you cannot see; drop them when you control both sides.

Metrics

limiter.get_metrics()
{
    "total_requests": 100,  # requests let through
    "current_requests": 2,  # holding a concurrency slot right now
    "rate_limit_delays": 5,  # requests that had to wait for the window
    "total_delay_time": 12.5,  # seconds spent in those waits
    "errors": 0,
    "current_queue_size": 3,  # requests started within the current window
    "max_concurrent": 5,
    "max_requests_per_period": 99,  # after the conservative adjustment
    "time_period": 60,
}

limiter.reset_metrics() zeroes the counters.

Logging

The limiter logs to the rate_limiter logger: a line at INFO when it is created and each time a caller has to wait, and details at DEBUG.

import logging

logging.getLogger("rate_limiter").setLevel(logging.WARNING)  # quiet it down

Example

examples/api_client.py fetches 40 resources from a public test API through one shared limiter and prints its metrics. It needs aiohttp.

Project

Changelog · Contributing · Roadmap · MIT License

Metadata

Release files for async-limiter 1.2.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 async-limiter 1.2.0
File Size Uploaded
async_limiter-1.2.0.tar.gz 11.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for async-limiter 1.2.0
File Interpreter ABI Platform
async_limiter-1.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 19.8 kB

Release files / async_limiter-1.2.0.tar.gz

Download URL async_limiter-1.2.0.tar.gz
Size 11.4 kB
Tags Source
SHA-256 checksum
How to use checksums
8e37c23ca38451e518104d854f6b41b630a4686b6a30824e7453af7bc39dc860
BLAKE2b-256 checksum
How to use checksums
a5d47099d8552a8c9d312d9ad7016c0fbc25f74a872c97fea90645369085469c
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 Oct 12, 2026.

Transparency log

Release files / async_limiter-1.2.0-py3-none-any.whl

Download URL async_limiter-1.2.0-py3-none-any.whl
Size 8.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0fbd64f95ce2547a966caef028faa77cd786f659f7bfe7296628b41293f4a5ef
BLAKE2b-256 checksum
How to use checksums
3cee33ea2367da20581987556a56fd0b2b895da9d1b0a38bb1071307e7af4776
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 Oct 12, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.2.0 This release

2 release files

1.1.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