Skip to content

Commit 062d9f9

Browse files
committed
Fix StaticResource routes not resolving
1 parent 438cb40 commit 062d9f9

File tree

5 files changed

+72
-58
lines changed

5 files changed

+72
-58
lines changed

CHANGES/12255.bugfix.feature.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Fixed :py:class:`~aiohttp.StaticResource` not resolving urls when adding them via the internal ``_routes`` property.
2+
You had to also add the method to the ``_allowed_methods`` property set, which is not ideal.
3+
4+
Added :py:meth:`~aiohttp.StaticResource.add_route` and :py:meth:`~aiohttp.StaticResource.register_route`
5+
to :py:class:`~aiohttp.StaticResource` to handle this same :py:class:`~aiohttp.Resource`.
6+
-- by :user:`BlindChickens`.

CONTRIBUTORS.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ Ivan Larin
182182
J. Nick Koston
183183
Jacob Champion
184184
Jacob Henner
185+
Jacques Combrink
185186
Jaesung Lee
186187
Jake Davis
187188
Jakob Ackermann

aiohttp/web_urldispatcher.py

Lines changed: 31 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ class _InfoDict(TypedDict, total=False):
101101
class AbstractResource(Sized, Iterable["AbstractRoute"]):
102102
def __init__(self, *, name: str | None = None) -> None:
103103
self._name = name
104+
self._routes: dict[str, ResourceRoute] = {}
105+
self._any_route: ResourceRoute | None = None
106+
self._allowed_methods: set[str] = set()
104107

105108
@property
106109
def name(self) -> str | None:
@@ -119,6 +122,33 @@ def canonical(self) -> str:
119122
def url_for(self, **kwargs: str) -> URL:
120123
"""Construct url for resource with additional params."""
121124

125+
def add_route(
126+
self,
127+
method: str,
128+
handler: type[AbstractView] | Handler,
129+
*,
130+
expect_handler: _ExpectHandler | None = None,
131+
) -> "ResourceRoute":
132+
if route := self._routes.get(method, self._any_route):
133+
raise RuntimeError(
134+
"Added route will never be executed, "
135+
f"method {route.method} is already "
136+
"registered"
137+
)
138+
139+
route_obj = ResourceRoute(method, handler, self, expect_handler=expect_handler)
140+
self.register_route(route_obj)
141+
return route_obj
142+
143+
def register_route(self, route: "ResourceRoute") -> None:
144+
assert isinstance(
145+
route, ResourceRoute
146+
), f"Instance of Route class is required, got {route!r}"
147+
if route.method == hdrs.METH_ANY:
148+
self._any_route = route
149+
self._allowed_methods.add(route.method)
150+
self._routes[route.method] = route
151+
122152
@abc.abstractmethod # pragma: no branch
123153
async def resolve(self, request: Request) -> _Resolve:
124154
"""Resolve resource.
@@ -308,36 +338,6 @@ async def _default_expect_handler(request: Request) -> None:
308338
class Resource(AbstractResource):
309339
def __init__(self, *, name: str | None = None) -> None:
310340
super().__init__(name=name)
311-
self._routes: dict[str, ResourceRoute] = {}
312-
self._any_route: ResourceRoute | None = None
313-
self._allowed_methods: set[str] = set()
314-
315-
def add_route(
316-
self,
317-
method: str,
318-
handler: type[AbstractView] | Handler,
319-
*,
320-
expect_handler: _ExpectHandler | None = None,
321-
) -> "ResourceRoute":
322-
if route := self._routes.get(method, self._any_route):
323-
raise RuntimeError(
324-
"Added route will never be executed, "
325-
f"method {route.method} is already "
326-
"registered"
327-
)
328-
329-
route_obj = ResourceRoute(method, handler, self, expect_handler=expect_handler)
330-
self.register_route(route_obj)
331-
return route_obj
332-
333-
def register_route(self, route: "ResourceRoute") -> None:
334-
assert isinstance(
335-
route, ResourceRoute
336-
), f"Instance of Route class is required, got {route!r}"
337-
if route.method == hdrs.METH_ANY:
338-
self._any_route = route
339-
self._allowed_methods.add(route.method)
340-
self._routes[route.method] = route
341341

342342
async def resolve(self, request: Request) -> _Resolve:
343343
if (match_dict := self._match(request.rel_url.path_safe)) is None:
@@ -589,12 +589,7 @@ def get_info(self) -> _InfoDict:
589589
}
590590

591591
def set_options_route(self, handler: Handler) -> None:
592-
if "OPTIONS" in self._routes:
593-
raise RuntimeError("OPTIONS route was set already")
594-
self._routes["OPTIONS"] = ResourceRoute(
595-
"OPTIONS", handler, self, expect_handler=self._expect_handler
596-
)
597-
self._allowed_methods.add("OPTIONS")
592+
self.add_route("OPTIONS", handler)
598593

599594
async def resolve(self, request: Request) -> _Resolve:
600595
path = request.rel_url.path_safe

docs/web_reference.rst

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2098,6 +2098,26 @@ Resource classes hierarchy::
20982098

20992099
.. versionadded:: 3.3
21002100

2101+
.. method:: add_route(method, handler, *, expect_handler=None)
2102+
2103+
Add a :term:`web-handler` to resource.
2104+
2105+
:param str method: HTTP method for route. Should be one of
2106+
``'GET'``, ``'POST'``, ``'PUT'``,
2107+
``'DELETE'``, ``'PATCH'``, ``'HEAD'``,
2108+
``'OPTIONS'`` or ``'*'`` for any method.
2109+
2110+
The parameter is case-insensitive, e.g. you
2111+
can push ``'get'`` as well as ``'GET'``.
2112+
2113+
The method should be unique for resource.
2114+
2115+
:param collections.abc.Callable handler: route handler.
2116+
2117+
:param collections.abc.Coroutine expect_handler: optional *expect* header handler.
2118+
2119+
:returns: new :class:`ResourceRoute` instance.
2120+
21012121
.. method:: resolve(request)
21022122
:async:
21032123

@@ -2135,27 +2155,6 @@ Resource classes hierarchy::
21352155
A base class for new-style resources, inherits :class:`AbstractResource`.
21362156

21372157

2138-
.. method:: add_route(method, handler, *, expect_handler=None)
2139-
2140-
Add a :term:`web-handler` to resource.
2141-
2142-
:param str method: HTTP method for route. Should be one of
2143-
``'GET'``, ``'POST'``, ``'PUT'``,
2144-
``'DELETE'``, ``'PATCH'``, ``'HEAD'``,
2145-
``'OPTIONS'`` or ``'*'`` for any method.
2146-
2147-
The parameter is case-insensitive, e.g. you
2148-
can push ``'get'`` as well as ``'GET'``.
2149-
2150-
The method should be unique for resource.
2151-
2152-
:param collections.abc.Callable handler: route handler.
2153-
2154-
:param collections.abc.Coroutine expect_handler: optional *expect* header handler.
2155-
2156-
:returns: new :class:`ResourceRoute` instance.
2157-
2158-
21592158
.. class:: PlainResource
21602159
:canonical: aiohttp.web_urldispatcher.PlainResource
21612160

@@ -2206,7 +2205,7 @@ Resource classes hierarchy::
22062205
.. class:: StaticResource
22072206
:canonical: aiohttp.web_urldispatcher.StaticResource
22082207

2209-
A resource, inherited from :class:`Resource`.
2208+
A resource, inherited from :class:`PrefixResource`.
22102209

22112210
The class corresponds to resources for :ref:`static file serving
22122211
<aiohttp-web-static-file-handling>`.

tests/test_urldispatch.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,19 @@ async def test_add_static_access_resources(router: web.UrlDispatcher) -> None:
551551
assert allowed_methods == {hdrs.METH_GET, hdrs.METH_OPTIONS, hdrs.METH_HEAD}
552552

553553

554+
async def test_add_static_access_resources_method(router: web.UrlDispatcher) -> None:
555+
"""Test adding a route via add_route to static resource."""
556+
resource = router.add_static(
557+
"/st", pathlib.Path(aiohttp.__file__).parent, name="static"
558+
)
559+
resource.add_route(hdrs.METH_POST, make_handler())
560+
mapping, allowed_methods = await resource.resolve(
561+
make_mocked_request("POST", "/st/path")
562+
)
563+
assert mapping is not None
564+
assert allowed_methods == {hdrs.METH_GET, hdrs.METH_POST, hdrs.METH_HEAD}
565+
566+
554567
async def test_add_static_set_options_route(router: web.UrlDispatcher) -> None:
555568
"""Ensure set_options_route works as expected."""
556569
resource = router.add_static(

0 commit comments

Comments
 (0)