Skip to content

add ratelimiting decorator#863

Open
RensDimmendaal wants to merge 2 commits intomainfrom
ratelimit
Open

add ratelimiting decorator#863
RensDimmendaal wants to merge 2 commits intomainfrom
ratelimit

Conversation

@RensDimmendaal
Copy link
Copy Markdown

@RensDimmendaal RensDimmendaal commented Apr 17, 2026

This PR adds a token-bucket rate limiter for FastHTML routes. I figured we needed something like this for the MagicKey PR to avoid abuse of the routes that send emails and I saw it was a common pattern for other web app frameworks. What's different about this implementation is that I've made it easy to re-use the request parameters as a key in the limiter.

I didn't add a separate explainer doc page, because the source doc page is quite extensive and builds up nicely. But i can add one if you like.

I've uploaded a demo app here

Example usage:

 from fasthtml.ratelimit import Limiter

 app, rt = fast_app()
 limiter = Limiter(app)

 @rt
 @limiter('5/m')
 def index(): return 'ok'

 @rt
 @limiter('100/h', key='user_id')
 def api(user_id: str): return 'data'

Types of changes
What types of changes does your code introduce? Put an x in all the boxes that apply:

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist
Go over all the following points, and put an x in all the boxes that apply:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.
  • All new and existing tests passed.
  • I am aware that this is an nbdev project, and I have edited, cleaned, and synced the source notebooks instead of editing .py or .md files directly.

Additional Information

The changes to nbs 00 and 01 are due to the latest nbdev-version and not related to this PR.

@RensDimmendaal RensDimmendaal added the enhancement New feature or request label Apr 17, 2026
@RensDimmendaal RensDimmendaal requested a review from jph00 April 17, 2026 11:35
Comment thread fasthtml/ratelimit.py
def __init__(self, app):
self._req_var = ContextVar('limiter_req')
async def _store(req): self._req_var.set(req)
app.before.insert(0, _store)
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ive been wondering if we could do this in a cleaner way. The issue is that req needs to be available in the decorator even when the route doesnt have req as a param.
If we can do that then Limiter can just be a function.

Copy link
Copy Markdown
Author

@RensDimmendaal RensDimmendaal Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One way could do it is incore.py create this global current_req ContextVar...but I'm not sure if thats agood or a really bad idea...Curious what you think!

+current_req = ContextVar('fh_current_req', default=None)
+
+@contextmanager
+def req_ctx(req):
+    token = current_req.set(req)
+    try: yield
+    finally: current_req.reset(token)

 @patch
 def _endp(self:FastHTML, f, body_wrap):
     "Create endpoint wrapper with before/after middleware processing"
     sig = signature_ex(f, True)
     for n,p in sig.parameters.items(): (msg:=_check_anno(n,p.annotation)) and warn(msg)
     async def _f(req):
+        with req_ctx(req):
            ...  # rest as before

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant