Skip to content
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

Open
wants to merge 11 commits into
base: v3.0
Choose a base branch
from

Conversation

provinzkraut
Copy link
Member

@provinzkraut provinzkraut commented Apr 27, 2024

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 the HandlerClass("/")(handler_fn) pattern.

@github-actions github-actions bot added area/handlers This PR involves changes to the handlers area/private-api This PR involves changes to the privatized API area/testing area/types This PR involves changes to the custom types size: large type/feat area/channels pr/internal area/controller labels Apr 27, 2024
Copy link

codecov bot commented Apr 27, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 98.00%. Comparing base (a4d7ea1) to head (293956c).

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.
📢 Have feedback on the report? Share it here.

@github-actions github-actions bot added the area/docs This PR involves changes to the documentation label Apr 27, 2024
@provinzkraut provinzkraut marked this pull request as ready for review April 27, 2024 14:25
@provinzkraut provinzkraut requested review from a team as code owners April 27, 2024 14:25
@github-actions github-actions bot added area/ci This PR involves changes to the CI/Infra area/connection area/constants This PR involves changes to the constants area/contrib This PR involves changes to the contrib (Deprecated) area/dependencies This PR involves changes to the dependencies area/kwargs area/middleware This PR involves changes to the middleware area/openapi This PR involves changes to the OpenAPI schema area/response area/router area/static-files area/template labels Apr 27, 2024
@github-actions github-actions bot removed area/router area/ci This PR involves changes to the CI/Infra area/constants This PR involves changes to the constants area/dependencies This PR involves changes to the dependencies area/template area/kwargs area/response area/static-files area/connection area/contrib This PR involves changes to the contrib (Deprecated) area/openapi This PR involves changes to the OpenAPI schema area/middleware This PR involves changes to the middleware labels Apr 28, 2024
Copy link

sonarcloud bot commented Apr 28, 2024

litestar/handlers/asgi_handlers.py Outdated Show resolved Hide resolved
@@ -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(
Copy link
Member

Choose a reason for hiding this comment

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

is this public/documented?

Copy link
Contributor

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?

Copy link
Member

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

Copy link
Member Author

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?

litestar/handlers/base.py Show resolved Hide resolved
Copy link
Contributor

@peterschutt peterschutt left a 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.

docs/release-notes/whats-new-3.rst Show resolved Hide resolved
litestar/handlers/asgi_handlers.py Outdated Show resolved Hide resolved
@@ -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(
Copy link
Contributor

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?

litestar/handlers/http_handlers/base.py Outdated Show resolved Hide resolved
litestar/handlers/http_handlers/base.py Outdated Show resolved Hide resolved
@@ -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
Copy link
Contributor

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()
Copy link
Contributor

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?

Copy link
Contributor

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:

def on_registration(self, app: Litestar) -> None:
self._set_listener_context()

Copy link
Member Author

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).

Copy link
Contributor

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]:
Copy link
Contributor

Choose a reason for hiding this comment

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

This need a docstring?

provinzkraut and others added 2 commits May 19, 2024 16:07
Co-authored-by: Peter Schutt <[email protected]>
Co-authored-by: Jacob Coffee <[email protected]>
@provinzkraut provinzkraut changed the title feat: Make route handlers functional decorators feat!: Make route handlers functional decorators May 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/channels area/controller area/docs This PR involves changes to the documentation area/handlers This PR involves changes to the handlers area/private-api This PR involves changes to the privatized API area/testing area/types This PR involves changes to the custom types Breaking 🔨 pr/internal size: large type/feat
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants