feat(routing): override default responders via on_request()#2446
feat(routing): override default responders via on_request()#2446vytas7 merged 38 commits intofalconry:masterfrom
Conversation
Add an option to CompiledRouterOptions that allows for overriding the default responders by implementing on_request() in the resource class Closes falconry#2071
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #2446 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 64 64
Lines 7875 7911 +36
Branches 1078 1086 +8
=========================================
+ Hits 7875 7911 +36 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
vytas7
left a comment
There was a problem hiding this comment.
Thanks, that is a great start! 💯
I have still haven't explored all the ramifications of the proposed design, but see some early comments inline.
Support on_request_{suffix}, unit tests for wrong wrong color of the default responder, minor on_request fixes
Closes falconry#2071
There was a problem hiding this comment.
Thanks for working on this so far, this is starting to shape up 💯
I'll try to find another name for the new option, and clean up the tests/docs a bit.
There is one unresolved issue that I just realized we need to either document clearly, or ideally provide workarounds. When decorating a whole class with hooks such as @falcon.before(...), on_request(...) and on_request_suffix(...) wouldn't get decorated. Which is in a sense good, because otherwise it might be seen as a breaking change (it wasn't decorated before).
However, if we make on_request() support enabled by default in 5.0, we will probably want to sync this behaviour too.
So the simplest and cleanest way is to document this as-is for now, and suggest decorating on_request() separately. However, that will cause double decoration if the class-level decorator is also present, and we change the behaviour in 5.0. Is that OK, or should we try to provide some kind of a clever shim here? Thoughts @gespyrop @CaselIT @kgriffs?
| @@ -0,0 +1,6 @@ | |||
| Added the :attr:`~.CompiledRouterOptions.allow_on_request` router option that | |||
| allows for providing a default responder by defining `on_request()` on the | |||
| resource. This option is disabled by default. If enabled, `on_request()` is | |||
There was a problem hiding this comment.
If -> When.
also below If the option -> When the option
falcon/routing/compiled.py
Outdated
| __slots__ = ('converters',) | ||
| allow_on_request: bool | ||
| """Allows for providing a default responder by defining `on_request()` on | ||
| the resource. |
There was a problem hiding this comment.
we could provide a simple example, using maybe req.method to check what method was used, something like
class Responder:
def on_request(self, req: Request, resp: Response) -> None:
if req.method == "GET":
... # handle get
elif req.method == "POST":
... # handle post
else:
raise HTTPMethodNotAllowedWe should also mention that method-named function take precedence, so if a resource defines both on_post and on_request, on_request is not called for posts
There was a problem hiding this comment.
Looks good, I will include the example as it is and I will explain how method-named functions override on_request.
I would expect on_request to be wrapped if the option is enabled. but it may not be that easy to do without changing how the hooks work |
|
I agree that wrapping |
|
Yes, it would need to be done dynamically when calling add_route. That likely requires some other change to the decorator so that it can ve accessed later on. |
|
@gespyrop thanks for the recent update! |
Yes, I don't a have a clear plan for this yet but I'll try to figure something out. |
|
I came up with a dirty hack. I don't really like it but it works. In the hooks I keep a reference of the decorated version of the responder under decorated_responder_name = '__decorated_' + responder_name + '__'
decorated_responder = getattr(resource, decorated_responder_name, None)
if decorated_responder:
setattr(resource, responder_name, decorated_responder)Thoughts? |
…on_request is enabled
Storing it on the method's attribute seems cleaner. Shall I name the attribute something like |
|
Looking at this again, maybe this mangling of private variables gets a bit too complex and hard to follow for the user (as in power user who reads Falcon's code)? I remember we were discussing a similar machinery for suffixed responders when we introduced suffixes, but in the end went for the simple "if it looks like a responder, quacks like a responder, decorate it". In that spirit, could we alternatively just add a module-level constant in The default value of this constant would change to Also, when we encounter an |
This would definitely simplify matters but it would require one additional step from the user, that is setting the value of the environment variable. We could also use this environment variable to determine the default value of |
The easier way would be to monkeypatch the said constant, but it might not always be easy depending on the import order of hooks etc. So an envvar would be just an escape hatch for these cases. Edit: We could still have that environment variable, but it shouldn't really be needed in most "normal" cases, i.e. the below shouldn't be an issue: >>> import falcon
>>> import falcon.hooks
>>> falcon.hooks.decorate_on_request = True
>>> @falcon.after(my_hook)
... class Resource:
... ...All of this is very inelegant for the user, I agree 😞. However, my rationale is that maybe this is a rather uncommon edge case? The user would get a warning when |
In the meantime, shall I push an implementation of this for reference? It is a cleaner and easier-to-understand approach for the people reviewing the PR. |
|
No, I think let's add a module attribute We just need to document this, add a warning emitted when And we need to skip |
|
Done. 👌 |
Add an option to CompiledRouterOptions that allows for overriding the default responders by implementing
on_request()in the resource classCloses #2071
Summary of Changes
Added a new router option (
allow_on_request) infalcon.routing.compiled.CompiledRouterOptionsthat allows for providing a default responder by definingon_request()on the resource. This option is disabled by default. If enabled,on_request()is set as the responder for every unimplemented method except foron_options(). If the option is disabled oron_request()is not provided in the resource, the default responder for "405 Method Not Allowed" is used.Related Issues
on_request()#2071Pull Request Checklist
This is just a reminder about the most common mistakes. Please make sure that you tick all appropriate boxes. But please read our contribution guide at least once; it will save you a few review cycles!
If an item doesn't apply to your pull request, check it anyway to make it apparent that there's nothing to do.
docs/.docs/.versionadded,versionchanged, ordeprecateddirectives.docs/_newsfragments/, with the file name format{issue_number}.{fragment_type}.rst. (Runtowncrier --draftto ensure it renders correctly.)If you have any questions to any of the points above, just submit and ask! This checklist is here to help you, not to deter you from contributing!
PR template inspired by the attrs project.