New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat!: Make route handlers functional decorators #3436
base: v3.0
Are you sure you want to change the base?
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## v3.0 #3436 +/- ##
=======================================
Coverage 97.99% 98.00%
=======================================
Files 326 326
Lines 14537 14531 -6
Branches 2302 2294 -8
=======================================
- Hits 14246 14241 -5
Misses 154 154
+ Partials 137 136 -1 ☔ View full report in Codecov by Sentry. |
98bf1ab
to
48ab468
Compare
Quality Gate passedIssues Measures |
@@ -101,4 +104,28 @@ async def handle(self, connection: ASGIConnection[ASGIRouteHandler, Any, Any, An | |||
await self.fn(scope=connection.scope, receive=connection.receive, send=connection.send) | |||
|
|||
|
|||
asgi = ASGIRouteHandler | |||
def asgi( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this public/documented?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is the replacement for v2's asgi
type. Needs a docstring?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and a .. versionadded::
directive :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I'm not so sure about the versionadded
with these. The names aren't newly introduced, and their usage remains largely the same. The fact that they're now implemented as decorator functions instead of callable class instance is an implementation detail. I think it's fair to say this is merely a change and not addition of a new feature.
wdyt @JacobCoffee @peterschutt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love it 😍
I do have a question about how we are handling layer resolution in the websocket listener _prepare_fn()
method. But I'm pretty sure that this, and the refactoring suggestions I've made, are for things that you've just moved around in the PR, not things you've changed.
Overall though, feels way more pythonic, and I like the simplified wording you've used in the docs in places.
@@ -101,4 +104,28 @@ async def handle(self, connection: ASGIConnection[ASGIRouteHandler, Any, Any, An | |||
await self.fn(scope=connection.scope, receive=connection.receive, send=connection.send) | |||
|
|||
|
|||
asgi = ASGIRouteHandler | |||
def asgi( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is the replacement for v2's asgi
type. Needs a docstring?
@@ -285,7 +302,7 @@ def __init__( | |||
self.response_cookies: Sequence[Cookie] | None = narrow_response_cookies(response_cookies) | |||
self.response_headers: Sequence[ResponseHeader] | None = narrow_response_headers(response_headers) | |||
|
|||
self.sync_to_thread = sync_to_thread | |||
self.has_sync_callable = has_sync_callable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can remove the class-level annotation for this on line 133 now that we assign it here.
listener=self, fn=fn, parsed_signature=parsed_signature, namespace=self.resolve_signature_namespace() | ||
) | ||
return ListenerHandler( | ||
listener=self, fn=fn, parsed_signature=parsed_signature, namespace=self.resolve_signature_namespace() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its been a while since I've had my head in this part of the code, so I could very well be forgetting something.
However, when this was first rolled out, I recall we had a context object that allowed us to delay resolution of things like the signature namespace until the handler was actually registered on the application. FWICT, this is called as part of BaseRouteHandler.__init__()
flow, which will be called when a function is decorated, and at that point it doesn't have any reference to its owner. This means that calling any layer resolution methods is redundant, right?
I can see that this is just transferring logic from the __call__()
method in the previous implementation, but how does the layer resolution work here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For reference, this is what he had in place at that time:
litestar/litestar/handlers/websocket_handlers/listener.py
Lines 152 to 153 in fb0e98a
def on_registration(self, app: Litestar) -> None: | |
self._set_listener_context() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can see that this is just transferring logic from the call() method in the previous implementation, but how does the layer resolution work here?
I think it doesn't.. I'll have to check this later, but this might be a bug (one that would also exist in the current implementation).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
one that would also exist in the current implementation
Yep, absolutely not introduced here, I just happen to be reading this code now.
signature_namespace: Mapping[str, Any] | None = None, | ||
websocket_class: type[WebSocket] | None = None, | ||
**kwargs: Any, | ||
) -> Callable[[AsyncAnyCallable], WebsocketRouteHandler]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This need a docstring?
Co-authored-by: Jacob Coffee <[email protected]>
Co-authored-by: Peter Schutt <[email protected]> Co-authored-by: Jacob Coffee <[email protected]>
Make route handler decorators functional decorators.
This gets rids of the
__call__
method having to mutate internal state and lets us clean up things a bit.It also makes for a nicer syntax and improved type/runtime checking of the handler classes, because they can now receive a
fn
as a required keyword argument, which gets rid of theHandlerClass("/")(handler_fn)
pattern.