Skip to content

Commit

Permalink
Add canonical property to routes and resources
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.

 - Add canonical property to AbstractRoute and AbstractResource
 - Add canonical implementation to PlainResource, DynamicResource,
 PrefixResource, StaticResource, ResourceRoute and SystemRoute.
 - Add tests

Closes aio-libs#2968
  • Loading branch information
pposca committed May 21, 2018
1 parent 9541960 commit 35b5704
Show file tree
Hide file tree
Showing 4 changed files with 101 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 routes and resources
1 change: 1 addition & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ Paul Colomiets
Paulus Schoutsen
Pavel Kamaev
Pawel Miech
Pepe Osca
Philipp A.
Pieter van Beek
Rafael Viotti
Expand Down
42 changes: 42 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 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."""
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 = '/'
Expand Down Expand Up @@ -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('/')
Expand Down Expand Up @@ -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('/')
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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}

Expand Down
58 changes: 57 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,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

0 comments on commit 35b5704

Please sign in to comment.