Metadata-Version: 2.4
Name: throttlepy
Version: 0.1.0
Summary: Token bucket and sliding window rate limiting for Python. In-memory or Redis-backed. Sync and async.
Project-URL: Homepage, https://github.com/gunasekharpaidi/throttlepy
Project-URL: Repository, https://github.com/gunasekharpaidi/throttlepy
Project-URL: Issues, https://github.com/gunasekharpaidi/throttlepy/issues
Project-URL: Changelog, https://github.com/gunasekharpaidi/throttlepy/blob/main/CHANGELOG.md
Author-email: Guna Sekhar P <gunasekharpaidi@gmail.com>
License: MIT
License-File: LICENSE
Keywords: rate-limit,redis,sliding-window,throttle,token-bucket
Classifier: Development Status :: 4 - Beta
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.11
Provides-Extra: redis
Requires-Dist: redis>=5.0; extra == 'redis'
Description-Content-Type: text/markdown

# throttlepy

> Token bucket and sliding window rate limiting for Python. In-memory or Redis-backed. Sync and async.

[![CI](https://github.com/gunasekharpaidi/throttlepy/actions/workflows/ci.yml/badge.svg)](https://github.com/gunasekharpaidi/throttlepy/actions/workflows/ci.yml)
[![Python](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/github/license/gunasekharpaidi/throttlepy.svg)](https://github.com/gunasekharpaidi/throttlepy/blob/main/LICENSE)

## Install

```bash
pip install throttlepy               # in-memory only
pip install throttlepy[redis]        # + distributed Redis backend
```

## Quickstart

```python
from throttlepy import Limiter, MemoryStorage, TokenBucket

limiter = Limiter(
    algorithm=TokenBucket(capacity=10, refill_per_sec=1.0),
    storage=MemoryStorage(),
)

if limiter.try_acquire("user:42"):
    ...  # do the thing
```

Two algorithms. Two storages. Two API styles (sync + async). Three ergonomics (`try_acquire`, `acquire`+exception, `with throttle():`).

## Algorithms

| Algorithm | When to use |
|---|---|
| `TokenBucket(capacity, refill_per_sec)` | Bursts are fine; smooth long-term rate |
| `SlidingWindow(limit, window_sec)` | Strict limit-per-window (billing, quotas) |

See [`docs/algorithms.md`](docs/algorithms.md) for the trade-offs.

## Async

```python
from throttlepy import AsyncLimiter, AsyncMemoryStorage, TokenBucket

limiter = AsyncLimiter(
    algorithm=TokenBucket(capacity=10, refill_per_sec=1.0),
    storage=AsyncMemoryStorage(),
)

async with limiter.throttle("user:42"):
    ...
```

## Distributed (Redis)

```python
from redis import Redis
from throttlepy import Limiter, TokenBucket
from throttlepy.storage.redis import RedisStorage

limiter = Limiter(
    algorithm=TokenBucket(capacity=100, refill_per_sec=10.0),
    storage=RedisStorage(Redis(host="localhost")),
)
```

Redis backend uses atomic Lua scripts for both algorithms — safe under concurrent multi-process load.

## Decorator

```python
from throttlepy import Limiter, MemoryStorage, TokenBucket, rate_limit

limiter = Limiter(TokenBucket(capacity=5, refill_per_sec=1.0), MemoryStorage())

@rate_limit(limiter, key=lambda user_id: f"user:{user_id}")
def make_request(user_id: str) -> None:
    ...  # RateLimitExceeded is raised if over limit
```

Works on async functions too (pass an `AsyncLimiter`).

## Testing your own code

Inject a `Clock` implementation to control time in tests:

```python
class FakeClock:
    def __init__(self, t: float = 0.0) -> None: self.t = t
    def now(self) -> float: return self.t
    def advance(self, seconds: float) -> None: self.t += seconds

limiter = Limiter(TokenBucket(1, 1.0), MemoryStorage(), clock=FakeClock())
```

## Examples

- [`examples/basic.py`](examples/basic.py) — sync token bucket
- [`examples/decorator_example.py`](examples/decorator_example.py) — `@rate_limit`
- [`examples/async_example.py`](examples/async_example.py) — async sliding window

## Benchmarks

```bash
just bench
```

Runs `benchmarks/bench.py` against MemoryStorage. Report numbers from your own machine — don't quote mine.

## Contributing

```bash
just setup    # install deps into .venv
just test     # run pytest
just lint     # ruff + mypy
just format   # apply fixes
```

## License

MIT
