Metadata-Version: 2.4
Name: halt-rate
Version: 0.6.0
Summary: SaaS-aware, cross-language rate limiting: per-user/API-key/plan limits, quotas, weighted endpoints, abuse controls, atomic Redis, and built-in observability
Project-URL: Documentation, https://github.com/surafel-kindu/halt#readme
Project-URL: Repository, https://github.com/surafel-kindu/halt
Author: Halt Contributors
License-Expression: MIT
Keywords: api-keys,distributed,django,fastapi,flask,middleware,observability,opentelemetry,quota,rate-limiting,redis,saas,throttling
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
Requires-Python: >=3.8
Provides-Extra: dev
Requires-Dist: black>=23.0.0; extra == 'dev'
Requires-Dist: fakeredis>=2.20.0; extra == 'dev'
Requires-Dist: mypy>=1.0.0; extra == 'dev'
Requires-Dist: opentelemetry-api>=1.20.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
Requires-Dist: pytest>=7.0.0; extra == 'dev'
Requires-Dist: redis>=4.0.0; extra == 'dev'
Requires-Dist: ruff>=0.1.0; extra == 'dev'
Provides-Extra: django
Requires-Dist: django>=3.2.0; extra == 'django'
Provides-Extra: fastapi
Requires-Dist: fastapi>=0.100.0; extra == 'fastapi'
Requires-Dist: starlette>=0.27.0; extra == 'fastapi'
Provides-Extra: flask
Requires-Dist: flask>=2.0.0; extra == 'flask'
Provides-Extra: otel
Requires-Dist: opentelemetry-api>=1.20.0; extra == 'otel'
Provides-Extra: redis
Requires-Dist: redis>=4.0.0; extra == 'redis'
Description-Content-Type: text/markdown

# Halt — Python SDK

**SaaS-aware, cross-language rate limiting.** Per-user / per-API-key / per-plan limits, quotas, weighted endpoints, abuse controls, atomic Redis accuracy (sync + async), and built-in observability — with a matching [TypeScript package](https://www.npmjs.com/package/halt-rate).

[![PyPI](https://img.shields.io/pypi/v/halt-rate.svg)](https://pypi.org/project/halt-rate/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

📖 **Full documentation: [halt.afroawi.com](https://halt.afroawi.com)**

## Install

```bash
pip install halt-rate
# optional extras
pip install "halt-rate[redis]"    # production store (sync + async)
pip install "halt-rate[fastapi]"  # framework adapter
pip install "halt-rate[otel]"     # OpenTelemetry metrics
```

Installs as `halt-rate`; import as `halt`.

## Quick start

```python
from halt import RateLimiter, InMemoryStore, presets

limiter = RateLimiter(
    store=InMemoryStore(),      # use RedisStore in production
    policy=presets.PUBLIC_API,  # 100 req/min per IP
)

decision = limiter.check(request)   # or: await limiter.acheck(request)
if not decision.allowed:
    # respond 429, Retry-After: decision.retry_after
    ...
```

Production Redis (sync + async), FastAPI/Flask/Django adapters, plan-based limits, quotas,
penalties, and observability are covered in the docs → **[halt.afroawi.com/docs](https://halt.afroawi.com/docs)**.

## Resilience

Rate limiting sits in the critical path of every request, so a Redis outage or latency spike must
not take down your service. Choose what happens when Redis is unavailable:

```python
import redis
from halt import RateLimiter, RedisStore, presets

store = RedisStore(
    client=redis.Redis.from_url(REDIS_URL, socket_timeout=0.2),  # surface slow Redis as errors
    fail_mode="fallback",  # "open" (default) | "closed" | "fallback"
    # circuit breaker on by default: after 5 failures, skip Redis for 5s (prevents cascades)
)
```

- **`open`** (default) — allow the request; don't take traffic down.
- **`closed`** — block (429); safest for abuse-sensitive routes.
- **`fallback`** — evaluate against a local in-memory store (`LocalStore`), so limits are still
  enforced (per-instance, approximate) instead of blanket allow. Works with `AsyncRedisStore` too.
- **Circuit breaker** (on by default) stops hammering a down Redis and serves from the fallback
  until it recovers. Set the client's `socket_timeout` so latency spikes trip it.

## Features

- Algorithms: token bucket, fixed/sliding window, leaky bucket
- Keys: IP, user, API key, composite, or custom
- Atomic **Redis** store (Lua, cluster-safe), sync **and** async, + in-memory dev store
- **Resilient**: configurable fail-open / fail-closed / local-fallback + built-in circuit breaker
- SaaS: per-plan limits, quotas, weighted endpoints, abuse penalties
- Observability: `StatsCollector` + OpenTelemetry metrics
- Adapters: FastAPI, Flask, Django

## Links

- Docs: https://halt.afroawi.com
- PyPI: https://pypi.org/project/halt-rate/
- Source & issues: https://github.com/surafel-kindu/halt

## License

MIT
