Skip to main content

hawkapi-ratelimit

Production-grade rate limiting for HawkAPI. Token bucket + sliding window. In-memory + Redis-Lua backends. Per-IP / per-user / per-API-key / composite identities. Standard X-RateLimit-* + Retry-After headers. Decorator or middleware.

Install

pip install hawkapi-ratelimit
pip install 'hawkapi-ratelimit[redis]'    # adds redis client for cluster-safe limits

Quickstart

from hawkapi import HawkAPI
from hawkapi_ratelimit import init_ratelimit, rate_limit

app = HawkAPI()
init_ratelimit(app)                   # default: MemoryLimiter (single-process)


@app.get("/search")
@rate_limit(rate=10, per=60)          # 10 requests per minute, per IP
async def search(request, q: str):
    return {"q": q}

When the budget is exhausted the route returns 429 Too Many Requests with Retry-After and the X-RateLimit-* headers set. Allowed requests get the same headers so clients can self-pace.

Strategies

from hawkapi_ratelimit import MemoryLimiter, init_ratelimit

# Token bucket — smoothed, allows short bursts (default).
init_ratelimit(app, limiter=MemoryLimiter(strategy="token_bucket"))

# Sliding window — precise, rejects the moment the threshold is crossed.
init_ratelimit(app, limiter=MemoryLimiter(strategy="sliding_window"))

Redis backend

For multi-process / multi-host deployments, swap in RedisLimiter — atomic check-and-increment via Lua scripts, cluster-safe:

from hawkapi_ratelimit import RedisLimiter, init_ratelimit

init_ratelimit(
    app,
    limiter=RedisLimiter(
        url="redis://localhost:6379/0",
        strategy="token_bucket",
        key_prefix="myapp:rl:",
        socket_timeout=0.2,    # < 1s — don't let the limiter add tail latency
        fail_closed=False,     # default: allow on Redis error (fail-open)
    ),
)

Both strategies are implemented as single atomic Lua scripts — no read-then-write race.

Identity strategies

from hawkapi_ratelimit import api_key, composite_key, header_key, ip_key, rate_limit, user_key


# Per-IP — default, honors X-Forwarded-For only if you opt in.
@rate_limit(rate=100, per=60, identity=ip_key(trusted_proxy=True))
async def f(request): ...


# Per-authenticated-user (reads ``request.scope["user"]``).
@rate_limit(rate=1000, per=60, identity=user_key(attribute="user_id"))
async def f(request): ...


# Per-API-key (reads Authorization: Bearer <token>).
@rate_limit(rate=10000, per=60, identity=api_key())
async def f(request): ...


# Combine — IP AND user, separately budgeted.
@rate_limit(rate=100, per=60, identity=composite_key(ip_key(), user_key()))
async def f(request): ...

ip_key(trusted_proxy=False) (the default) reads request.client.host — the socket peer. ip_key(trusted_proxy=True) reads the left-most token of X-Forwarded-For (RFC-correct client). Never enable trusted_proxy=True unless your edge proxy strips inbound X-Forwarded-For from clients.

Middleware mode

For a global policy across the whole app:

from hawkapi_ratelimit import RateLimit, RateLimitMiddleware

app.add_middleware(
    RateLimitMiddleware,
    limit=RateLimit(rate=1000, per=60),
    exclude_paths=("/health", "/metrics"),
)

Per-route @rate_limit decorators stack on top of the middleware — they share the same backend.

Response shape

On rejection:

HTTP/1.1 429 Too Many Requests
Content-Type: application/json
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1700000060
Retry-After: 7

{"detail": "rate limit exceeded", "retry_after": 7.0}

Known accepted risks

These are real tradeoffs that operators should layer additional protection around.

  • user_key() collapses anonymous traffic to one shared bucket ("user:anon"). A single attacker can exhaust the anonymous budget. Pair with composite_key(ip_key(), user_key()) or restrict the route to authenticated users.
  • Middleware does not set X-RateLimit-* on allowed responses — only on 429. The decorator sets them on every response. If you need response-headers everywhere, use the decorator (or attach headers in a downstream middleware).
  • @rate_limit skipped when handler has no Request parameter — silently no-ops because there's nothing to key on. A UserWarning fires at import time if you forget it.
  • MemoryLimiter is single-process — multi-worker deployments must use RedisLimiter for cluster-wide budgets.

Security notes

  • trusted_proxy=False by default — opt in only when your edge strips client-supplied X-Forwarded-For. Honoring XFF from an untrusted source lets attackers forge their identity by setting the header.
  • Key length capped at 256 chars — prevents unbounded memory growth in MemoryLimiter when an attacker submits very long identifiers.
  • MemoryLimiter evicts the oldest entries when the store exceeds max_keys (default 100k). The store cannot grow without bound.
  • Redis socket_timeout=0.2s — short, so a partially-degraded Redis cannot add seconds of tail latency to every request.
  • Fail-open by default (RedisLimiter(fail_closed=False)) — Redis outage = log warning and allow the request, rather than DoS'ing the app. Choose fail_closed=True for endpoints where rate-limit MUST hold (admin / payments).
  • Lua atomicity — token-bucket refill + consume runs in a single EVAL round, so concurrent workers cannot race the budget.

Development

git clone https://github.com/Hawk-API/hawkapi-ratelimit.git
cd hawkapi-ratelimit
uv sync --extra dev
uv run pytest -q
uv run ruff check . && uv run ruff format --check .
uv run pyright src/

License

MIT.

Metadata

Release files for hawkapi-ratelimit 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for hawkapi-ratelimit 0.1.2
File Size Uploaded
hawkapi_ratelimit-0.1.2.tar.gz 27.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for hawkapi-ratelimit 0.1.2
File Interpreter ABI Platform
hawkapi_ratelimit-0.1.2-py3-none-any.whl Python 3 none any Details

Total release size: 44.4 kB

Release files / hawkapi_ratelimit-0.1.2.tar.gz

Download URL hawkapi_ratelimit-0.1.2.tar.gz
Size 27.6 kB
Tags Source
SHA-256 checksum
How to use checksums
16c8e8de4700480d89c58ccf0328077e4cfcfd23536e4f1605ee864a470adb79
BLAKE2b-256 checksum
How to use checksums
aa819e27957691380b12e73b70aacd7b195d86e541c91e12d0da875a2cb289b4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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 Jun 10, 2026.

Transparency log

Release files / hawkapi_ratelimit-0.1.2-py3-none-any.whl

Download URL hawkapi_ratelimit-0.1.2-py3-none-any.whl
Size 16.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a59f8a22ac00b667e0f2389d70de7770c5a243423c37a4cb9ed6b3e60fd50e2e
BLAKE2b-256 checksum
How to use checksums
020361f24b94c45cdf264da1e8235ce023aac33baab7629b9fc2f510abebf0b2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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 Jun 10, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.2 This release

2 release files

0.1.1

2 release files

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