From 9ce72fe0fcdae509884f8ccc7ab136fff76738d8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 1 Aug 2024 11:55:05 -0500 Subject: [PATCH] Fix url dispatcher index when variable is preceded by a fixed string after a slash (#8566) (cherry picked from commit f3a1afc51665d9d2180606f250f492a61623ac6b) --- CHANGES/8566.bugfix.rst | 1 + aiohttp/web_urldispatcher.py | 10 ++++++++-- tests/test_web_urldispatcher.py | 24 ++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 CHANGES/8566.bugfix.rst diff --git a/CHANGES/8566.bugfix.rst b/CHANGES/8566.bugfix.rst new file mode 100644 index 00000000000..61365c0bb61 --- /dev/null +++ b/CHANGES/8566.bugfix.rst @@ -0,0 +1 @@ +Fixed url dispatcher index not matching when a variable is preceded by a fixed string after a slash -- by :user:`bdraco`. diff --git a/aiohttp/web_urldispatcher.py b/aiohttp/web_urldispatcher.py index e26fd9dc7a6..a490e100ab3 100644 --- a/aiohttp/web_urldispatcher.py +++ b/aiohttp/web_urldispatcher.py @@ -1131,8 +1131,14 @@ def register_resource(self, resource: AbstractResource) -> None: def _get_resource_index_key(self, resource: AbstractResource) -> str: """Return a key to index the resource in the resource index.""" - # strip at the first { to allow for variables - return resource.canonical.partition("{")[0].rstrip("/") or "/" + if "{" in (index_key := resource.canonical): + # strip at the first { to allow for variables, and than + # rpartition at / to allow for variable parts in the path + # For example if the canonical path is `/core/locations{tail:.*}` + # the index key will be `/core` since index is based on the + # url parts split by `/` + index_key = index_key.partition("{")[0].rpartition("/")[0] + return index_key.rstrip("/") or "/" def index_resource(self, resource: AbstractResource) -> None: """Add a resource to the resource index.""" diff --git a/tests/test_web_urldispatcher.py b/tests/test_web_urldispatcher.py index 1cda0980cc0..a799f4ba146 100644 --- a/tests/test_web_urldispatcher.py +++ b/tests/test_web_urldispatcher.py @@ -937,3 +937,27 @@ async def get(self) -> web.Response: r = await client.get("///a") assert r.status == 200 await r.release() + + +async def test_route_with_regex(aiohttp_client: AiohttpClient) -> None: + """Test a route with a regex preceded by a fixed string.""" + app = web.Application() + + async def handler(request: web.Request) -> web.Response: + assert isinstance(request.match_info._route.resource, Resource) + return web.Response(text=request.match_info._route.resource.canonical) + + app.router.add_get("/core/locations{tail:.*}", handler) + client = await aiohttp_client(app) + + r = await client.get("/core/locations/tail/here") + assert r.status == 200 + assert await r.text() == "/core/locations{tail}" + + r = await client.get("/core/locations_tail_here") + assert r.status == 200 + assert await r.text() == "/core/locations{tail}" + + r = await client.get("/core/locations_tail;id=abcdef") + assert r.status == 200 + assert await r.text() == "/core/locations{tail}"