Skip to content

Commit 0e11b36

Browse files
pposcaasvetlov
authored andcommitted
Add canonical property to resources (#2993)
The canonical is the path used to add a new route. For example, /foo/bar/{name}. For DynamicResource, canonical exposes the formatter. Closes #2968
1 parent 858e926 commit 0e11b36

File tree

5 files changed

+94
-1
lines changed

5 files changed

+94
-1
lines changed

CHANGES/2968.feature

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add canonical property to resources

CONTRIBUTORS.txt

+1
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ Paul Colomiets
154154
Paulus Schoutsen
155155
Pavel Kamaev
156156
Pawel Miech
157+
Pepe Osca
157158
Philipp A.
158159
Pieter van Beek
159160
Rafael Viotti

aiohttp/web_urldispatcher.py

+21
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,15 @@ def __init__(self, *, name=None):
4545
def name(self):
4646
return self._name
4747

48+
@property
49+
@abc.abstractmethod
50+
def canonical(self):
51+
"""Exposes the resource's canonical path.
52+
53+
For example '/foo/bar/{name}'
54+
55+
"""
56+
4857
@abc.abstractmethod # pragma: no branch
4958
def url_for(self, **kwargs):
5059
"""Construct url for resource with additional params."""
@@ -300,6 +309,10 @@ def __init__(self, path, *, name=None):
300309
assert not path or path.startswith('/')
301310
self._path = path
302311

312+
@property
313+
def canonical(self):
314+
return self._path
315+
303316
def freeze(self):
304317
if not self._path:
305318
self._path = '/'
@@ -373,6 +386,10 @@ def __init__(self, path, *, name=None):
373386
self._pattern = compiled
374387
self._formatter = formatter
375388

389+
@property
390+
def canonical(self):
391+
return self._formatter
392+
376393
def add_prefix(self, prefix):
377394
assert prefix.startswith('/')
378395
assert not prefix.endswith('/')
@@ -414,6 +431,10 @@ def __init__(self, prefix, *, name=None):
414431
super().__init__(name=name)
415432
self._prefix = URL.build(path=prefix).raw_path
416433

434+
@property
435+
def canonical(self):
436+
return self._prefix
437+
417438
def add_prefix(self, prefix):
418439
assert prefix.startswith('/')
419440
assert not prefix.endswith('/')

docs/web_reference.rst

+35
Original file line numberDiff line numberDiff line change
@@ -1826,6 +1826,13 @@ Resource classes hierarchy::
18261826

18271827
Read-only *name* of resource or ``None``.
18281828

1829+
.. attribute:: canonical
1830+
1831+
Read-only *canonical path* associate with the resource. For example
1832+
``/path/to`` or ``/path/{to}``
1833+
1834+
.. versionadded:: 3.3
1835+
18291836
.. comethod:: resolve(request)
18301837

18311838
Resolve resource by finding appropriate :term:`web-handler` for
@@ -1889,6 +1896,12 @@ Resource classes hierarchy::
18891896
The class corresponds to resources with plain-text matching,
18901897
``'/path/to'`` for example.
18911898

1899+
.. attribute:: canonical
1900+
1901+
Read-only *canonical path* associate with the resource. Returns the path
1902+
used to create the PlainResource. For example ``/path/to``
1903+
1904+
.. versionadded:: 3.3
18921905

18931906
.. method:: url_for()
18941907

@@ -1903,6 +1916,13 @@ Resource classes hierarchy::
19031916
:ref:`variable <aiohttp-web-variable-handler>` matching,
19041917
e.g. ``'/path/{to}/{param}'`` etc.
19051918

1919+
.. attribute:: canonical
1920+
1921+
Read-only *canonical path* associate with the resource. Returns the
1922+
formatter obtained from the path used to create the DynamicResource.
1923+
For example, from a path ``/get/{num:^\d+}``, it returns ``/get/{num}``
1924+
1925+
.. versionadded:: 3.3
19061926

19071927
.. method:: url_for(**params)
19081928

@@ -1921,6 +1941,13 @@ Resource classes hierarchy::
19211941
The class corresponds to resources for :ref:`static file serving
19221942
<aiohttp-web-static-file-handling>`.
19231943

1944+
.. attribute:: canonical
1945+
1946+
Read-only *canonical path* associate with the resource. Returns the prefix
1947+
used to create the StaticResource. For example ``/prefix``
1948+
1949+
.. versionadded:: 3.3
1950+
19241951
.. method:: url_for(filename, append_version=None)
19251952

19261953
Returns a :class:`~yarl.URL` for file path under resource prefix.
@@ -1948,6 +1975,14 @@ Resource classes hierarchy::
19481975
A resource for serving nested applications. The class instance is
19491976
returned by :class:`~aiohttp.web.Application.add_subapp` call.
19501977

1978+
.. attribute:: canonical
1979+
1980+
Read-only *canonical path* associate with the resource. Returns the
1981+
prefix used to create the PrefixedSubAppResource.
1982+
For example ``/prefix``
1983+
1984+
.. versionadded:: 3.3
1985+
19511986
.. method:: url_for(**kwargs)
19521987

19531988
The call is not allowed, it raises :exc:`RuntimeError`.

tests/test_urldispatch.py

+36-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
from aiohttp.test_utils import make_mocked_request
1313
from aiohttp.web import HTTPMethodNotAllowed, HTTPNotFound, Response
1414
from aiohttp.web_urldispatcher import (PATH_SEP, AbstractResource,
15-
ResourceRoute, SystemRoute, View,
15+
DynamicResource, PlainResource,
16+
ResourceRoute, StaticResource,
17+
SystemRoute, View,
1618
_default_expect_handler)
1719

1820

@@ -1112,3 +1114,36 @@ def handler(request):
11121114

11131115
with pytest.warns(DeprecationWarning):
11141116
router.add_route('GET', '/handler', handler)
1117+
1118+
1119+
def test_plain_resource_canonical():
1120+
canonical = '/plain/path'
1121+
res = PlainResource(path=canonical)
1122+
assert res.canonical == canonical
1123+
1124+
1125+
def test_dynamic_resource_canonical():
1126+
canonicals = {
1127+
'/get/{name}': '/get/{name}',
1128+
'/get/{num:^\d+}': '/get/{num}',
1129+
r'/handler/{to:\d+}': r'/handler/{to}',
1130+
r'/{one}/{two:.+}': r'/{one}/{two}',
1131+
}
1132+
for pattern, canonical in canonicals.items():
1133+
res = DynamicResource(path=pattern)
1134+
assert res.canonical == canonical
1135+
1136+
1137+
def test_static_resource_canonical():
1138+
prefix = '/prefix'
1139+
directory = str(os.path.dirname(aiohttp.__file__))
1140+
canonical = prefix
1141+
res = StaticResource(prefix=prefix, directory=directory)
1142+
assert res.canonical == canonical
1143+
1144+
1145+
def test_prefixed_subapp_resource_canonical(app, loop):
1146+
canonical = '/prefix'
1147+
subapp = web.Application()
1148+
res = subapp.add_subapp(canonical, subapp)
1149+
assert res.canonical == canonical

0 commit comments

Comments
 (0)