Skip to content

Commit 36d2bef

Browse files
authored
[PR #9899/2249f2d backport][3.11] Refactor Resource.resolve to avoid linear search of methods (#9908)
1 parent 85f3531 commit 36d2bef

File tree

2 files changed

+21
-25
lines changed

2 files changed

+21
-25
lines changed

CHANGES/9899.misc.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improved performance of resolving resources when multiple methods are registered for the same route -- by :user:`bdraco`.

aiohttp/web_urldispatcher.py

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,9 @@ async def _default_expect_handler(request: Request) -> None:
350350
class Resource(AbstractResource):
351351
def __init__(self, *, name: Optional[str] = None) -> None:
352352
super().__init__(name=name)
353-
self._routes: List[ResourceRoute] = []
353+
self._routes: Dict[str, ResourceRoute] = {}
354+
self._any_route: Optional[ResourceRoute] = None
355+
self._allowed_methods: Set[str] = set()
354356

355357
def add_route(
356358
self,
@@ -359,14 +361,12 @@ def add_route(
359361
*,
360362
expect_handler: Optional[_ExpectHandler] = None,
361363
) -> "ResourceRoute":
362-
363-
for route_obj in self._routes:
364-
if route_obj.method == method or route_obj.method == hdrs.METH_ANY:
365-
raise RuntimeError(
366-
"Added route will never be executed, "
367-
"method {route.method} is already "
368-
"registered".format(route=route_obj)
369-
)
364+
if route := self._routes.get(method, self._any_route):
365+
raise RuntimeError(
366+
"Added route will never be executed, "
367+
f"method {route.method} is already "
368+
"registered"
369+
)
370370

371371
route_obj = ResourceRoute(method, handler, self, expect_handler=expect_handler)
372372
self.register_route(route_obj)
@@ -376,23 +376,18 @@ def register_route(self, route: "ResourceRoute") -> None:
376376
assert isinstance(
377377
route, ResourceRoute
378378
), f"Instance of Route class is required, got {route!r}"
379-
self._routes.append(route)
379+
if route.method == hdrs.METH_ANY:
380+
self._any_route = route
381+
else:
382+
self._allowed_methods.add(route.method)
383+
self._routes[route.method] = route
380384

381385
async def resolve(self, request: Request) -> _Resolve:
382-
allowed_methods: Set[str] = set()
383-
384-
match_dict = self._match(request.rel_url.path_safe)
385-
if match_dict is None:
386-
return None, allowed_methods
387-
388-
for route_obj in self._routes:
389-
route_method = route_obj.method
390-
allowed_methods.add(route_method)
391-
392-
if route_method == request.method or route_method == hdrs.METH_ANY:
393-
return (UrlMappingMatchInfo(match_dict, route_obj), allowed_methods)
394-
else:
395-
return None, allowed_methods
386+
if (match_dict := self._match(request.rel_url.path_safe)) is None:
387+
return None, set()
388+
if route := self._routes.get(request.method, self._any_route):
389+
return UrlMappingMatchInfo(match_dict, route), self._allowed_methods
390+
return None, self._allowed_methods
396391

397392
@abc.abstractmethod
398393
def _match(self, path: str) -> Optional[Dict[str, str]]:
@@ -402,7 +397,7 @@ def __len__(self) -> int:
402397
return len(self._routes)
403398

404399
def __iter__(self) -> Iterator["ResourceRoute"]:
405-
return iter(self._routes)
400+
return iter(self._routes.values())
406401

407402
# TODO: implement all abstract methods
408403

0 commit comments

Comments
 (0)