Skip to content

Commit

Permalink
Add canonical property to resources (#2993)
Browse files Browse the repository at this point in the history
The canonical is the path used to add a new route. For example,
/foo/bar/{name}. For DynamicResource, canonical exposes the
formatter.

Closes #2968
  • Loading branch information
pposca authored and asvetlov committed May 27, 2018
1 parent 858e926 commit 0e11b36
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGES/2968.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add canonical property to resources
1 change: 1 addition & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ Paul Colomiets
Paulus Schoutsen
Pavel Kamaev
Pawel Miech
Pepe Osca
Philipp A.
Pieter van Beek
Rafael Viotti
Expand Down
21 changes: 21 additions & 0 deletions aiohttp/web_urldispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ def __init__(self, *, name=None):
def name(self):
return self._name

@property
@abc.abstractmethod
def canonical(self):
"""Exposes the resource's canonical path.
For example '/foo/bar/{name}'
"""

@abc.abstractmethod # pragma: no branch
def url_for(self, **kwargs):
"""Construct url for resource with additional params."""
Expand Down Expand Up @@ -300,6 +309,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 = '/'
Expand Down Expand Up @@ -373,6 +386,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('/')
Expand Down Expand Up @@ -414,6 +431,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('/')
Expand Down
35 changes: 35 additions & 0 deletions docs/web_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1826,6 +1826,13 @@ Resource classes hierarchy::

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

.. attribute:: canonical

Read-only *canonical path* associate with the resource. For example
``/path/to`` or ``/path/{to}``

.. versionadded:: 3.3

.. comethod:: resolve(request)

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

.. attribute:: canonical

Read-only *canonical path* associate with the resource. Returns the path
used to create the PlainResource. For example ``/path/to``

.. versionadded:: 3.3

.. method:: url_for()

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

.. attribute:: canonical

Read-only *canonical path* associate with the resource. Returns the
formatter obtained from the path used to create the DynamicResource.
For example, from a path ``/get/{num:^\d+}``, it returns ``/get/{num}``

.. versionadded:: 3.3

.. method:: url_for(**params)

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

.. attribute:: canonical

Read-only *canonical path* associate with the resource. Returns the prefix
used to create the StaticResource. For example ``/prefix``

.. versionadded:: 3.3

.. method:: url_for(filename, append_version=None)

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

.. attribute:: canonical

Read-only *canonical path* associate with the resource. Returns the
prefix used to create the PrefixedSubAppResource.
For example ``/prefix``

.. versionadded:: 3.3

.. method:: url_for(**kwargs)

The call is not allowed, it raises :exc:`RuntimeError`.
Expand Down
37 changes: 36 additions & 1 deletion tests/test_urldispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)


Expand Down Expand Up @@ -1112,3 +1114,36 @@ 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
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

0 comments on commit 0e11b36

Please sign in to comment.