diff --git a/CHANGES/2968.feature b/CHANGES/2968.feature new file mode 100644 index 00000000000..52a3a9d766a --- /dev/null +++ b/CHANGES/2968.feature @@ -0,0 +1 @@ +Add canonical property to routes and resources diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index f29139e747f..b066aa15a5e 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -153,6 +153,7 @@ Paul Colomiets Paulus Schoutsen Pavel Kamaev Pawel Miech +Pepe Osca Philipp A. Pieter van Beek Rafael Viotti diff --git a/aiohttp/web_urldispatcher.py b/aiohttp/web_urldispatcher.py index 16cf008986d..e16d7f2b4dd 100644 --- a/aiohttp/web_urldispatcher.py +++ b/aiohttp/web_urldispatcher.py @@ -45,6 +45,15 @@ def __init__(self, *, name=None): def name(self): return self._name + @property + @abc.abstractmethod + def canonical(self): + """Exposes the route's canonical. + + For example '/foo/bar/{name}' + + """ + @abc.abstractmethod # pragma: no branch def url_for(self, **kwargs): """Construct url for resource with additional params.""" @@ -131,6 +140,15 @@ def handler(self): def name(self): """Optional route's name, always equals to resource's name.""" + @property + @abc.abstractmethod + def canonical(self): + """Exposes the route's canonical. + + For example '/foo/bar/{name}' + + """ + @property def resource(self): return self._resource @@ -300,6 +318,10 @@ def __init__(self, path, *, name=None): assert not path or path.startswith('/') self._path = path + @property + def canonical(self): + return self._path + def freeze(self): if not self._path: self._path = '/' @@ -373,6 +395,10 @@ def __init__(self, path, *, name=None): self._pattern = compiled self._formatter = formatter + @property + def canonical(self): + return self._formatter + def add_prefix(self, prefix): assert prefix.startswith('/') assert not prefix.endswith('/') @@ -414,6 +440,10 @@ def __init__(self, prefix, *, name=None): super().__init__(name=name) self._prefix = URL.build(path=prefix).raw_path + @property + def canonical(self): + return self._prefix + def add_prefix(self, prefix): assert prefix.startswith('/') assert not prefix.endswith('/') @@ -457,6 +487,10 @@ def __init__(self, prefix, directory, *, name=None, 'HEAD': ResourceRoute('HEAD', self._handle, self, expect_handler=expect_handler)} + @property + def canonical(self): + return self._prefix + str(self._directory) + def url_for(self, *, filename, append_version=None): if append_version is None: append_version = self._append_version @@ -669,6 +703,10 @@ def __repr__(self): def name(self): return self._resource.name + @property + def canonical(self): + return self._resource.canonical + def url_for(self, *args, **kwargs): """Construct url for route with additional params.""" return self._resource.url_for(*args, **kwargs) @@ -690,6 +728,10 @@ def url_for(self, *args, **kwargs): def name(self): return None + @property + def canonical(self): + return None + def get_info(self): return {'http_exception': self._http_exception} diff --git a/tests/test_urldispatch.py b/tests/test_urldispatch.py index 293225d3e60..c8daf66e919 100644 --- a/tests/test_urldispatch.py +++ b/tests/test_urldispatch.py @@ -12,7 +12,9 @@ from aiohttp.test_utils import make_mocked_request from aiohttp.web import HTTPMethodNotAllowed, HTTPNotFound, Response from aiohttp.web_urldispatcher import (PATH_SEP, AbstractResource, - ResourceRoute, SystemRoute, View, + DynamicResource, PlainResource, + ResourceRoute, StaticResource, + SystemRoute, View, _default_expect_handler) @@ -1112,3 +1114,57 @@ def handler(request): with pytest.warns(DeprecationWarning): router.add_route('GET', '/handler', handler) + + +def test_plain_resource_canonical(): + canonical = '/plain/path' + res = PlainResource(path=canonical) + assert res.canonical == canonical + + +def test_dynamic_resource_canonical(): + canonicals = { + '/get/{name}': '/get/{name}', + '/get/{num:^\d+}': '/get/{num}', + r'/handler/{to:\d+}': r'/handler/{to}', + r'/{one}/{two:.+}': r'/{one}/{two}', + } + for pattern, canonical in canonicals.items(): + res = DynamicResource(path=pattern) + assert res.canonical == canonical + + +def test_static_resource_canonical(): + prefix = '/prefix' + directory = str(os.path.dirname(aiohttp.__file__)) + canonical = prefix + directory + res = StaticResource(prefix=prefix, directory=directory) + assert res.canonical == canonical + + +def test_prefixed_subapp_resource_canonical(app, loop): + canonical = '/prefix' + subapp = web.Application() + res = subapp.add_subapp(canonical, subapp) + assert res.canonical == canonical + + +def test_resource_route_canonical(router): + canonical = '/plain' + route = router.add_route('GET', canonical, make_handler()) + assert route.canonical == canonical + + canonical = '/variable/{name}' + route = router.add_route('GET', canonical, make_handler()) + assert route.canonical == canonical + + prefix = '/prefix' + directory = str(os.path.dirname(aiohttp.__file__)) + canonical = prefix + directory + route = router.add_static(prefix, directory) + assert route.canonical == canonical + + +def test_system_route_canonical(): + route = SystemRoute(BaseException()) + assert route.canonical is None