Skip to main content

jetio-ratelimit

PyPI version Tests Coverage Status Python versions License

Rate limiting for Jetio: sliding-window by default, IP- or account-keyed (or both, stacked), usable as middleware for routes you don't own the handler for (like jetio-auth's /login) and as a Depends()-composable dependency for routes you do (a CrudRouter policy, a hand-written route).

  • Usage Guide -- the full guide: every mode, every key function, stacking/reusing policies, handling a 429, testing your integration, troubleshooting. Start here for "how do I do X."
  • Design & Architecture -- the reasoning: why sliding window over token bucket, why IP-only limiting is weak against real credential stuffing, and the real bugs found in Jetio/jetio-auth along the way. Start here for "why does it work this way."

Install

pip install jetio-ratelimit

For local development (running the test suite, contributing):

pip install -e .[dev]   # from this directory

Quickstart

from jetio import Jetio, CrudRouter
from jetio_auth import AuthRouter
from jetio_ratelimit import RateLimiter, InMemoryStore, Limit, by_ip, by_field, by_user

app = Jetio()
auth = AuthRouter(User, company_name="My App")
auth.register_routes(app)  # POST /register, POST /login

limiter = RateLimiter(store=InMemoryStore())

# Middleware mode: protects /login, which AuthRouter registers internally --
# there's no Depends() hook to attach to on a route we don't define.
# protect_many() stacks two independent limits in one call; either tripping
# blocks the request. AUTH_POLICY is a plain list, so the same two rules
# can be applied to /register or any other auth-adjacent route with one
# more protect_many() call each -- see "Reusing a policy across routes" below.
AUTH_POLICY = [
    Limit(max_attempts=5, window_seconds=60, key_func=by_ip),
    Limit(max_attempts=3, window_seconds=60, key_func=by_field("username")),
]
limiter.protect_many(app, path="/login", limits=AUTH_POLICY)

# Dependency mode: composes into a CrudRouter policy, keyed by the
# authenticated user rather than IP.
CrudRouter(
    model=Order,
    secure=True,
    policy={
        "POST": limiter.dependency(
            max_attempts=10, window_seconds=60,
            key_func=by_user, identity_dependency=auth.get_auth_dependency(),
        ),
    },
).register_routes(app)

Run examples/demo_app.py and hit it with curl to see both modes working against a real jetio-auth-backed app. For why /login gets two stacked limits instead of one, how to reuse one policy across many routes, by_header-keyed API endpoints, using dependency mode outside CrudRouter, and more, see the Usage Guide.

Status

Published on PyPI as v0.1.0: sliding window algorithm, in-memory store, both API modes, IP/account/ header/user keying, protect_many() for stacking multiple limits (or reusing one policy across many routes) in one call. Not yet done: Redis store (for anything running more than one worker -- InMemoryStore's state is per-process), progressive lockout on repeat violations, an equivalent stacking helper for dependency mode, trusted-proxy-aware X-Forwarded-For support. See Roadmap.

Known limitations

  • InMemoryStore is single-process. Behind multiple workers/replicas, each one has its own counters, so the effective limit becomes limit * worker_count. Don't treat this as sufficient for a horizontally-scaled deployment yet.
  • No X-Forwarded-For support. Behind a reverse proxy, by_ip sees the proxy's IP, not the real client's. This is a deliberate default, not an oversight: X-Forwarded-For is a client-suppliable header, so trusting it unconditionally lets any caller spoof their rate-limit identity -- evading their own limit, or framing another IP for one. Other rate limiters that do support it require you to explicitly configure how many proxy hops (or which ones) to trust, precisely to avoid this; naive header-trusting is a known footgun, not just a missing feature. See Roadmap.

Release files for jetio-ratelimit 0.1.1

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

Source distribution (sdist)

Source distribution for jetio-ratelimit 0.1.1
File Size Uploaded
jetio_ratelimit-0.1.1.tar.gz 18.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for jetio-ratelimit 0.1.1
File Interpreter ABI Platform
jetio_ratelimit-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 33.1 kB

Release files / jetio_ratelimit-0.1.1.tar.gz

Download URL jetio_ratelimit-0.1.1.tar.gz
Size 18.1 kB
Tags Source
SHA-256 checksum
How to use checksums
4ea4059ea3071ffde647b710ddecc1326c951361631f0a42b52a1d77eee41543
BLAKE2b-256 checksum
How to use checksums
820db2d930c386d330333b90ec1f7bf499fdc715c810bcb23e6bd4505bbdb2ec
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.10

Release files / jetio_ratelimit-0.1.1-py3-none-any.whl

Download URL jetio_ratelimit-0.1.1-py3-none-any.whl
Size 14.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1adce3b21388fcb94723f07dbbf73eb2abf8cccebb9c3e0df153d6f3452942c6
BLAKE2b-256 checksum
How to use checksums
596e9e0c433388d6e6e6133c98f84e48ce1c8156cd19bd63e0fe3019ca55ddd2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.10

Release history Release notifications | RSS feed

This release

0.1.1 This release

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