-
-
Notifications
You must be signed in to change notification settings - Fork 252
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
refactor: openApi made lazy. #1066
Conversation
…a tests (in all the decorators). Fixes a potential issue in __init__ where the default argument creates a shared object at parse time rather than a separate one for each instance. As only one instance, not normally an issue but not good practice. One FIXME is an edge case in init_openapi where neither if clause evaluates to True for openapi_file_path (should we log an error or what?)
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
CodSpeed Performance ReportMerging #1066 will degrade performances by 47.63%Comparing Summary
Benchmarks breakdown
|
All add_route methods have auth_required, openapi_name and openapi_tags which get stored in the route. Instead of incrementally adding them to openapi routes they are added all at once in app.start include_routes now additionally tracks the list of routers whose routes have been added to the main app. This will be used more later to avoid merging routes until app.start for more flexibility
Second Commit 642eac9 This should reduce the performance problem as we no longer add openapi routes in the decorators. Instead we store the openapi_name and openapi_tags (plus auth_rerquired because it will be needed) in the Route. All tests are passing. This will complete the openapi part of the add_routes refactor. |
…rom allowing None for openApi_tags to not
…00 loops. This is marginally faster. This PR lowers slows full test suite by 0.004s on my system. However, those benchmarks are mostly setup. Once we are making calls to a running server none of this code is being executed.
…fects in the future. No consistent peformance impact on 100,000 Robyn(__file__) calls
@sansyrox if you are happy with this direction then this PR is ready Once this is in I'll start another PR for the add_router next step (which will keep routes local to the router/subrouter until we start the server). |
self.openapi.add_openapi_path_obj("get", endpoint, openapi_name, openapi_tags, handler) | ||
|
||
return self.add_route(HttpMethod.GET, endpoint, handler, const, auth_required) |
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.
Hey @dave42w 👋
Could you explain this modification a bit?
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.
Sure.
My goal is that we do the minimum processing of routes while in the app setup stage. ie between app = Robyn(__file__)
and app.start(...)
My logic is that things can happen in quite a dynamic way in this section of the code, developers might do things in an odd order (include a subrouter and then add more routes to it). Or include a subrouter in a subrouter and then include the subrouter and all it's subsubrouters. Or add middleware before the routes or in the middle of them. This could happen because we bring in a subrouter via a module and there it includes routes and middleware. Or want different middleware for the subrouter from accounting compared to sales etc.
Therefore if we call add_openapi_path_obj
in add_route the path might not yet be fixed. So it takes more logic (and potentially doing things multiple times) to handle these variations.
When we have calls to openapi scattered around the code then it gets expensive in runtime and maintenance if in every case we want to only make the call if openapi is enabled. It gets slow to maintain if we have 10 calls to add_openapi_path_obj
and need to change it.
Of course if we are going to collect together all the calls to add_openapi_path_obj
and do them in one place as the server starts then we need to have the handler, openapi_name and openapi_tags available during app.start()
. This is why I have added them to the Route class.
I'm pretty sure that when I refactor add_route it will be a lot cleaner if we create an incomplete Route instance much earlier and then pass the single object around the system rather than 7 arguments. Deep in the system we do the work to complete function_info to finish the Route instance. That will also reduce the need for parsing the arguments all over the place (maybe Route needs to be a dataclass to validate itself). All that is for a future PR.
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.
Hi @sansyrox Thanks |
@@ -182,7 +182,7 @@ def spawn_process( | |||
server.set_response_headers_exclude_paths(excluded_response_headers_paths) | |||
|
|||
for route in routes: | |||
route_type, endpoint, function, is_const = route | |||
route_type, endpoint, function, is_const, auth_required, openapi_name, openapi_tags = route |
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 question @dave42w 👋
Where are the auth_required
, openapi_name
, etc used here? Could we do a better job in unpacking if not?
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.
It's preparation for the full route refactor. Then the authentication route processing happens in server start rather than in add_route.
I wanted to reduce the times I need to touch the code and so put this in before it's needed.
|
||
# TODO! after include_routes does not immediatelly merge all the routes | ||
# for router in included_routers: | ||
# for route in router: | ||
# openapi.add_openapi_path_obj(lower_http_method(route.route_type), route.route, route.openapi_name, route.openapi_tags, route.function.handler) | ||
|
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.
@dave42w , what do we mean by this comment?
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.
At the moment, when we include_routes all the routes from a subrouter get copied into the Robyn router immediately. This means we shouldn't add extra routes after calling include_routes.
The full routes refactor will move all evaluation and processing of routes and routers to when the server starts. So add_route is just adding route objects to the current router. Also include_routes just adds a router to the parent without copying any routes. This massively simplifies these processes and means that the ordering of adding routes and routers does not matter.
The server start then builds the complete route table, handling nesting of routers, open_api registration, authentication, middleware after the application developer has registered everything in any order.
The rust processes will get the same routes as they do now but we do all the route processing once in one place.
The comment is a placeholder to remind me not to forget to make sure that openapi still handles things properly (it might not be this code depending on the order that seems best for handling both include _routes and add_openapi_path_obj).
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.
Hey @dave42w 👋
I've gone through the changes and all looks good. I just have a few comments and suggestions 😄
Merry shipmas btw 😉 🎄
Thanks. I've replied to all the comments. Hopefully, this is just a stepping stone to the bigger router refactor where the real benefits lie (much simpler functions without side effects; support for nesting subrouters; support for building routes/routers in more flexible ways) |
Makes openAPI lazy (only created if enabled, all methods only called if enabled).
Fixes a potential issue in init where the default argument creates a shared object at parse time rather than a separate one for each instance. As only one instance, not normally an issue but not good practice.
One FIXME is an edge case in init_openapi where neither if clause evaluates to True for openapi_file_path (should we log an error or what?)
Description
This PR is progressing towards the larger refactor to simplify the processing of add_route. See #1059
Summary
This PR does make it so that
Please look carefully at the TODO We should cover this edge case
PR Checklist
Please ensure that:
Pre-Commit Instructions: