Metadata-Version: 2.4
Name: larzlimit
Version: 0.1.0
Summary: Rate limiting in pure Python: token bucket, sliding & fixed window, per-key, with a decorator and WSGI middleware. Zero dependencies.
Author: larz-scripter
License: MIT
Project-URL: Homepage, https://github.com/larz-scripter/larzlimit
Project-URL: Repository, https://github.com/larz-scripter/larzlimit
Project-URL: Documentation, https://github.com/larz-scripter/larzlimit#readme
Project-URL: Issues, https://github.com/larz-scripter/larzlimit/issues
Keywords: rate-limiting,rate-limiter,throttling,token-bucket,sliding-window,wsgi,middleware,flask-limiter-alternative,zero-dependency,pure-python
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# larzlimit

**Rate limiting in pure Python. Zero dependencies.**

Enforce "at most N events per T seconds" — per IP, per user, per API token, or
globally — with three strategies, a decorator, and WSGI middleware. No Redis, no
server, nothing to install.

```python
from larzlimit import RateLimiter

limiter = RateLimiter(limit=100, window=60)      # 100 requests/minute per key
d = limiter.hit("user:42")
if not d.allowed:
    retry_after = d.retry_after                  # seconds until allowed again
```

## Why

- **Zero dependencies, in-process.** No Redis, no `flask-limiter` stack — just the
  standard library, thread-safe.
- **Three strategies, one API.** Pick what fits:
  - **sliding window** (default) — accurate, no burst at window edges;
  - **fixed window** — cheapest, allows edge bursts;
  - **token bucket** — smooth average rate with a configurable burst.
- **Per-key.** Bucket by any string — IP, user id, API key — or use one global
  limit. Keys are independent.
- **Batteries included.** A `@rate_limit` decorator and a drop-in
  `RateLimitMiddleware` (returns `429` + `Retry-After`).
- **Tells you `retry_after`.** Every decision carries how long to wait, so you can
  set headers or back off precisely.

## Install

```bash
pip install larzlimit
```

## The limiter

```python
from larzlimit import RateLimiter, RateLimitExceeded

limiter = RateLimiter(limit=5, window=60, strategy="sliding")  # or "fixed" / "token"

limiter.allow("ip:1.2.3.4")     # True/False (consumes on True)
d = limiter.hit("ip:1.2.3.4")   # Decision(allowed, remaining, retry_after, limit)
limiter.check("ip:1.2.3.4")     # raises RateLimitExceeded when over
limiter.reset("ip:1.2.3.4")

# token bucket with a burst of 20 but ~5/sec sustained
RateLimiter(limit=5, window=1, strategy="token", capacity=20)
```

## Decorator

```python
from larzlimit import rate_limit

@rate_limit(5, 60)                          # 5 calls/minute, global
def send_email():
    ...

@rate_limit(100, 60, key=lambda uid: uid)   # 100/min per user
def api(uid):
    ...

@rate_limit(1, 10, on_limited=lambda d: {"error": "slow down"})   # handle instead of raise
def poll():
    ...
```

## WSGI middleware

```python
from larzlimit import RateLimitMiddleware

app = RateLimitMiddleware(app, limit=60, window=60)     # 60/min per client IP
# over the limit -> 429 Too Many Requests + Retry-After header
```

Bucket by something other than IP with `key=lambda environ: ...` (an API key
header, a session id, …).

## Scope

larzlimit is an **in-process** limiter — perfect for a single service, a worker,
or behind a load balancer with sticky routing. For a limit shared across many
machines you'd back it with shared storage; the strategy classes give you the
correct algorithms to build on.

## Tests

```bash
python -m unittest discover -s tests -v      # 16 tests, zero deps
```

## The Larz stack

Pure-Python, zero-dependency building blocks: **[larz](https://github.com/larz-scripter/larz)** · **[larzchain](https://github.com/larz-scripter/larzchain)** · **[larzmoney](https://github.com/larz-scripter/larzmoney)** · **[larzcrypt](https://github.com/larz-scripter/larzcrypt)** · **[larzdb](https://github.com/larz-scripter/larzdb)** · **[larzagent](https://github.com/larz-scripter/larzagent)** · **[larzchart](https://github.com/larz-scripter/larzchart)** · **[larzmark](https://github.com/larz-scripter/larzmark)** · **[larztask](https://github.com/larz-scripter/larztask)** · **[larzvault](https://github.com/larz-scripter/larzvault)** · **[larzvm](https://github.com/larz-scripter/larzvm)** · **[larzcache](https://github.com/larz-scripter/larzcache)** · **[larzvalidate](https://github.com/larz-scripter/larzvalidate)** · **[larzid](https://github.com/larz-scripter/larzid)** · **[larzrpc](https://github.com/larz-scripter/larzrpc)** · **[larzstate](https://github.com/larz-scripter/larzstate)** · **[larzhttp](https://github.com/larz-scripter/larzhttp)** · **[larzconf](https://github.com/larz-scripter/larzconf)** · **[larzcron](https://github.com/larz-scripter/larzcron)** · **larzlimit**

## License

MIT © larz-scripter
