Skip to main content

Zero-dependency Python package for easy throttling with asyncio support

Project description

Throttler

Python PyPI License: MIT

Python Tests codecov

Zero-dependency Python package for easy throttling with asyncio support.

Demo

🎒 Install

Just

pip install throttler

🧪 Development

uv sync --locked --all-extras --dev
uv run pytest -vl --cov=./ --cov-report=xml
uv run ruff check .
uv run ruff format .
uv run python -m build

📌 API Overview

This package exposes four context managers and six decorators:

  • Throttler / throttle: rate-limits how often a block or async function can be entered.
  • ThrottlerSimultaneous / throttle_simultaneous: caps how many async calls can run at once.
  • ExecutionTimer / execution_timer: enforces a minimum period between entries by sleeping.
  • Timer / timer: prints timing information, including average duration across iterations.

Throttler and ThrottlerSimultaneous are async context managers and must be used within an event loop. ExecutionTimer and Timer support both sync and async usage.

✅ Choosing the Right Tool

  • Use Throttler when you need rate limiting (e.g., 10 requests per 1 second).
  • Use ThrottlerSimultaneous when you need concurrency limiting (e.g., at most 5 in-flight tasks).
  • Use ExecutionTimer when you want fixed spacing between iterations (e.g., run a job once per minute).
  • Use Timer when you want timing output for repeated operations.

It is common to combine Throttler with ThrottlerSimultaneous for APIs that enforce both rate and concurrency.

🛠 Usage Examples

All run-ready examples are here.

Throttler and ThrottlerSimultaneous

Both are async context managers. Use them inside an async function or event loop.

Throttler:

Async context manager that limits how often the block can be entered.

from throttler import Throttler

# Limit to three calls per second
t = Throttler(rate_limit=3, period=1.0)
async with t:
    pass

Or

import asyncio

from throttler import throttle

# Limit to three calls per second
@throttle(rate_limit=3, period=1.0)
async def task():
    return await asyncio.sleep(0.1)

ThrottlerSimultaneous:

Async context manager that limits concurrent access to a block.

from throttler import ThrottlerSimultaneous

# Limit to five simultaneous calls
t = ThrottlerSimultaneous(count=5)
async with t:
    pass

Or

import asyncio

from throttler import throttle_simultaneous

# Limit to five simultaneous calls
@throttle_simultaneous(count=5)
async def task():
    return await asyncio.sleep(0.1)

Simple Example

import asyncio
import time

from throttler import throttle


# Limit to two calls per second
@throttle(rate_limit=2, period=1.0)
async def task():
    return await asyncio.sleep(0.1)


async def many_tasks(count: int):
    coros = [task() for _ in range(count)]
    for coro in asyncio.as_completed(coros):
        _ = await coro
        print(f'Timestamp: {time.time()}')

asyncio.run(many_tasks(10))

Result output:

Timestamp: 1585183394.8141203
Timestamp: 1585183394.8141203
Timestamp: 1585183395.830335
Timestamp: 1585183395.830335
Timestamp: 1585183396.8460555
Timestamp: 1585183396.8460555
...

API Example

import asyncio
import time

import aiohttp

from throttler import Throttler, ThrottlerSimultaneous


class SomeAPI:
    api_url = 'https://example.com'

    def __init__(self, throttler):
        self.throttler = throttler

    async def request(self, session: aiohttp.ClientSession):
        async with self.throttler:
            async with session.get(self.api_url) as resp:
                return resp

    async def many_requests(self, count: int):
        async with aiohttp.ClientSession() as session:
            coros = [self.request(session) for _ in range(count)]
            for coro in asyncio.as_completed(coros):
                response = await coro
                print(f'{int(time.time())} | Result: {response.status}')


async def run():
    # Throttler can be of any type
    t = ThrottlerSimultaneous(count=5)        # Five simultaneous requests
    t = Throttler(rate_limit=10, period=3.0)  # Ten requests in three seconds

    api = SomeAPI(t)
    await api.many_requests(100)

asyncio.run(run())

Result output:

1585182908 | Result: 200
1585182908 | Result: 200
1585182908 | Result: 200
1585182909 | Result: 200
1585182909 | Result: 200
1585182909 | Result: 200
1585182910 | Result: 200
1585182910 | Result: 200
1585182910 | Result: 200
...

ExecutionTimer

Context manager that enforces a minimum period between entries by sleeping. It is not a rate limiter like Throttler. With align_sleep=True, it aligns to wall-clock boundaries (e.g. each minute).

import time

from throttler import ExecutionTimer

et = ExecutionTimer(60, align_sleep=True)

while True:
    with et:
        print(time.asctime(), '|', time.time())

Or

import time

from throttler import execution_timer

@execution_timer(60, align_sleep=True)
def f():
    print(time.asctime(), '|', time.time())

while True:
    f()

Result output:

Thu Mar 26 00:56:17 2020 | 1585173377.1203406
Thu Mar 26 00:57:00 2020 | 1585173420.0006166
Thu Mar 26 00:58:00 2020 | 1585173480.002517
Thu Mar 26 00:59:00 2020 | 1585173540.001494

Timer

Context manager for pretty printing start, end, elapsed and average times. Elapsed timing uses a monotonic clock, while start/end timestamps are wall-clock time.

import random
import time

from throttler import Timer

timer = Timer('My Timer', verbose=True)

for _ in range(3):
    with timer:
        time.sleep(random.random())

Or

import random
import time

from throttler import timer

@timer('My Timer', verbose=True)
def f():
    time.sleep(random.random())

for _ in range(3):
    f()

Result output:

#1 | My Timer | begin: 2020-03-26 01:46:07.648661
#1 | My Timer |   end: 2020-03-26 01:46:08.382135, elapsed: 0.73 sec, average: 0.73 sec
#2 | My Timer | begin: 2020-03-26 01:46:08.382135
#2 | My Timer |   end: 2020-03-26 01:46:08.599919, elapsed: 0.22 sec, average: 0.48 sec
#3 | My Timer | begin: 2020-03-26 01:46:08.599919
#3 | My Timer |   end: 2020-03-26 01:46:09.083370, elapsed: 0.48 sec, average: 0.48 sec

🧠 Behavior Notes

  • Throttler uses a sliding window. Each entry may sleep until the oldest recorded entry exits the time window.
  • ThrottlerSimultaneous uses an async semaphore under the hood.
  • ExecutionTimer uses a monotonic clock for waiting; when align_sleep=True it aligns to wall time.
  • Timer prints elapsed duration per entry and the running average across all entries.

⚠️ Error Handling

  • Throttler(rate_limit, period) requires a positive integer rate and a positive float period.
  • ThrottlerSimultaneous(count) requires a positive integer count.

These validations raise ValueError early so invalid configuration fails fast.

💬 Contributing

Contributions, issues and feature requests are welcome!

📝 License

This project is MIT licensed.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

throttler-1.2.3.tar.gz (11.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

throttler-1.2.3-py3-none-any.whl (9.7 kB view details)

Uploaded Python 3

File details

Details for the file throttler-1.2.3.tar.gz.

File metadata

  • Download URL: throttler-1.2.3.tar.gz
  • Upload date:
  • Size: 11.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for throttler-1.2.3.tar.gz
Algorithm Hash digest
SHA256 d2f5b0b499d62f1fc984dcac8043450b606549b0097753a9c8a707f7427c27e1
MD5 78df9987a03794313ff560a64c207eee
BLAKE2b-256 ce3f47baf510c31e0e52ac0d80d9071e5e166ca069167fee4a6c13841f9d5f5f

See more details on using hashes here.

Provenance

The following attestation bundles were made for throttler-1.2.3.tar.gz:

Publisher: pythonpublish.yml on uburuntu/throttler

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file throttler-1.2.3-py3-none-any.whl.

File metadata

  • Download URL: throttler-1.2.3-py3-none-any.whl
  • Upload date:
  • Size: 9.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for throttler-1.2.3-py3-none-any.whl
Algorithm Hash digest
SHA256 241ea3e97438dec4dc2f31ddc56dbd96262787a9b1d0598adfcc0bada1134b66
MD5 5dcbdef7ebe9a6637ded065cc6eec971
BLAKE2b-256 667042d8796acc57c8bcd9ae395b1a6a0bbc833f738492a8ed192a44ccd58035

See more details on using hashes here.

Provenance

The following attestation bundles were made for throttler-1.2.3-py3-none-any.whl:

Publisher: pythonpublish.yml on uburuntu/throttler

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page