Repository navigation
feat(limiter): rate limiting on limits directly, slowapi removed - #22
Merged
Merged
Conversation
The <2.1 constraint existed because 2.1 had no wheels, then because 2.1 is a new minor. SQLModel 0.0.48 added 2.1 support, and 2.1 is the first SQLAlchemy with a Python 3.15 wheel, so 2.0 ran as pure Python since the move to 3.15. Declared as sqlalchemy[asyncio]: 2.1 installs greenlet only through that extra. The lock changes one version; greenlet stays at 3.5.6. Checked against the 2.1 migration guide: asyncpg is named in the URL (the psycopg default does not apply), no named types, unconditional autoflush is harmless here. Full suite passes on 3.15 with SADeprecationWarning, SAWarning and SQLModel DeprecationWarnings as errors; lint, mypy and pip-audit clean. Removes the Known Issues entry about the pure wheel. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
slowapi 0.1.10 calls asyncio.iscoroutinefunction, removed in Python 3.16, and the fix has sat in open upstream pull requests since 2025. The limiter is now built on limits (the library slowapi wraps, already installed): MemoryStorage, FixedWindowRateLimiter, parse_many, so the configuration syntax and the fixed-window behaviour are unchanged. A limited route declares Depends(limiter.limit(...)) instead of a decorator plus request and response parameters it never used; the dependency counts the request, writes X-RateLimit-Limit, -Remaining and -Reset onto the response and raises RateLimitExceeded, which main.py maps to the same 429 body and Retry-After as before. New: the 429 also carries the X-RateLimit headers, X-RateLimit-Reset is a whole number of seconds, and counters are keyed by method and route path. The pytest.ini filter for slowapi's warning is gone: the suite passes with -W error. Tests cover the handler, the headers, the 429, per-route counters and the disabled limiter without a database. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Last of the migrations: removes the one thing standing between this code base and Python 3.16. slowapi 0.1.10 calls
asyncio.iscoroutinefunction, removed in 3.16; upstream has had fixes open since 2025 (laurentS/slowapi#246, #264, #291) and none merged.Branched from
chore/sqlalchemy-2-1(#21) because both touchpoetry.lockand the changelog; merge #21 first.Changes
limiter.pyis rebuilt onlimits5.8, the library slowapi wraps and the project already had:MemoryStorage,FixedWindowRateLimiter,parse_many. Same configuration syntax (RATE_LIMIT_LOGIN=60/minute), same fixed-window behaviour, same per-IP keys.dependencies=[Depends(limiter.limit(RATE_LIMIT_LOGIN))]. Therequestandresponseparameters the routes carried only for slowapi are gone; the dependency writes theX-RateLimitheaders onto FastAPI's response object and raisesRateLimitExceeded, mapped bymain.pyto the same429body andRetry-After.429also carries theX-RateLimitheaders (withRemaining: 0), andX-RateLimit-Resetis a whole number of seconds. Counters are keyed by method and route path template.pyproject.toml:slowapireplaced bylimits (>=5.8.0,<6.0); the lock loses only slowapi.pytest.ini: the slowapi warning filter is removed. The suite now runs with no filter at all.limitsstorages.test_limiter.pycovers the handler, the headers, the429with its headers, per-route counters and the disabled limiter on a throwaway app without a database.test_rate_limit.py(database-backed) is unchanged in substance and still assertsRetry-After: 60and the remaining budget.Behaviour kept on purpose
Retry-Afteris still the window length (60 for a per-minute limit), not the seconds left in the window. Changing that is a client-visible contract change; theX-RateLimit-Resetheader already gives the exact reset time.Verification, local on Python 3.15.0 with PostgreSQL 17
black, flake8, mypy clean; importing the app with
-W errorclean; full suite, 140 tests, passed with-W error(every warning an error, nothing filtered).🤖 Generated with Claude Code