From a2fbd23d75e3af7b5934c19de2c65fdff3f1afc7 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Tue, 23 May 2017 19:25:25 -0700 Subject: [PATCH 01/12] First scratches --- aiohttp/web_urldispatcher.py | 54 ++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/aiohttp/web_urldispatcher.py b/aiohttp/web_urldispatcher.py index 04c4b117956..5cc12ba4080 100644 --- a/aiohttp/web_urldispatcher.py +++ b/aiohttp/web_urldispatcher.py @@ -1,6 +1,7 @@ import abc import asyncio import collections +import functools import inspect import keyword import os @@ -893,3 +894,56 @@ def freeze(self): super().freeze() for resource in self._resources: resource.freeze() + + +def _reg_http_method(func, method, **kwargs): + if not hasattr(func, '__aiohttp_data__'): + func.__aiohttp_data__ = {} + func.__aiohttp_data__[method] = kwargs + return func + + +def head(**kwargs): + def wrapper(func): + @functools.wraps(func) + def wrapped(**kwargs): + return _reg_http_method(func, hdrs.METH_HEAD, **kwargs) + return wrapped + return wrapper + + +def add_get(self, *args, name=None, allow_head=True, **kwargs): + """ + Shortcut for add_route with method GET, if allow_head is true another + route is added allowing head requests to the same endpoint + """ + if allow_head: + # it name is not None append -head to avoid it conflicting with + # the GET route below + head_name = name and '{}-head'.format(name) + self.add_route(hdrs.METH_HEAD, *args, name=head_name, **kwargs) + return self.add_route(hdrs.METH_GET, *args, name=name, **kwargs) + +def add_post(self, *args, **kwargs): + """ + Shortcut for add_route with method POST + """ + return self.add_route(hdrs.METH_POST, *args, **kwargs) + +def add_put(self, *args, **kwargs): + """ + Shortcut for add_route with method PUT + """ + return self.add_route(hdrs.METH_PUT, *args, **kwargs) + +def add_patch(self, *args, **kwargs): + """ + Shortcut for add_route with method PATCH + """ + return self.add_route(hdrs.METH_PATCH, *args, **kwargs) + +def add_delete(self, *args, **kwargs): + """ + Shortcut for add_route with method DELETE + """ + return self.add_route(hdrs.METH_DELETE, *args, **kwargs) From 62f653a6d8cbe8370d765b5c50038b3545c25734 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Thu, 1 Jun 2017 21:53:24 +0300 Subject: [PATCH 02/12] Work on --- aiohttp/web_urldispatcher.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/aiohttp/web_urldispatcher.py b/aiohttp/web_urldispatcher.py index 5cc12ba4080..25e3505968f 100644 --- a/aiohttp/web_urldispatcher.py +++ b/aiohttp/web_urldispatcher.py @@ -897,9 +897,9 @@ def freeze(self): def _reg_http_method(func, method, **kwargs): - if not hasattr(func, '__aiohttp_data__'): - func.__aiohttp_data__ = {} - func.__aiohttp_data__[method] = kwargs + if not hasattr(func, '__aiohttp_web__'): + func.__aiohttp_web__ = {} + func.__aiohttp_web__[method] = kwargs return func @@ -912,7 +912,7 @@ def wrapped(**kwargs): return wrapper -def add_get(self, *args, name=None, allow_head=True, **kwargs): +def get(self, *args, name=None, allow_head=True, **kwargs): """ Shortcut for add_route with method GET, if allow_head is true another route is added allowing head requests to the same endpoint @@ -924,24 +924,28 @@ def add_get(self, *args, name=None, allow_head=True, **kwargs): self.add_route(hdrs.METH_HEAD, *args, name=head_name, **kwargs) return self.add_route(hdrs.METH_GET, *args, name=name, **kwargs) + def add_post(self, *args, **kwargs): """ Shortcut for add_route with method POST """ return self.add_route(hdrs.METH_POST, *args, **kwargs) + def add_put(self, *args, **kwargs): """ Shortcut for add_route with method PUT """ return self.add_route(hdrs.METH_PUT, *args, **kwargs) + def add_patch(self, *args, **kwargs): """ Shortcut for add_route with method PATCH """ return self.add_route(hdrs.METH_PATCH, *args, **kwargs) + def add_delete(self, *args, **kwargs): """ Shortcut for add_route with method DELETE From 2ce063fec8cf5e3bca23c2e78fd203502b9cddd3 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sun, 4 Jun 2017 20:15:27 +0300 Subject: [PATCH 03/12] Work on decorators --- aiohttp/web_urldispatcher.py | 102 +++++++++++++++++++++-------------- 1 file changed, 61 insertions(+), 41 deletions(-) diff --git a/aiohttp/web_urldispatcher.py b/aiohttp/web_urldispatcher.py index 25e3505968f..f1497d613b1 100644 --- a/aiohttp/web_urldispatcher.py +++ b/aiohttp/web_urldispatcher.py @@ -7,7 +7,7 @@ import os import re import warnings -from collections.abc import Container, Iterable, Sized +from collections.abc import Container, Iterable, Sized, namedtuple from pathlib import Path from types import MappingProxyType @@ -33,6 +33,8 @@ HTTP_METHOD_RE = re.compile(r"^[0-9A-Za-z!#\$%&'\*\+\-\.\^_`\|~]+$") PATH_SEP = re.escape('/') +RouteInfo = namedtuple('RouteInfo', 'method, path, handler, kwargs') + class AbstractResource(Sized, Iterable): @@ -895,59 +897,77 @@ def freeze(self): for resource in self._resources: resource.freeze() + def add_routes(self, routes): + for route in routes: + assert route.method in hdrs.METH_ALL + reg = getattr(self, 'add_'+route.method.lower()) + reg(route.path, route.handler, **route.kwargs) + + def scan(self, package): + pass + -def _reg_http_method(func, method, **kwargs): - if not hasattr(func, '__aiohttp_web__'): - func.__aiohttp_web__ = {} - func.__aiohttp_web__[method] = kwargs - return func +def _make_route(method, path, handler, **kwargs): + return RouteInfo(method, path, handler, kwargs) -def head(**kwargs): - def wrapper(func): - @functools.wraps(func) +def _make_wrapper(method, path, kwargs): + def wrapper(handler): + @functools.wraps(handler) def wrapped(**kwargs): - return _reg_http_method(func, hdrs.METH_HEAD, **kwargs) + if hasattr(handler, '__aiohttp_web__'): + raise ValueError('Handler {handler!r} is registered already ' + 'as [{method}] {path} {kwargs}'.format( + handler=handler, + method=method, + path=path, + kwargs=kwargs)) + handler.__aiohttp_web__ = _make_route(method, path, + handler, kwargs) + return handler return wrapped return wrapper -def get(self, *args, name=None, allow_head=True, **kwargs): - """ - Shortcut for add_route with method GET, if allow_head is true another - route is added allowing head requests to the same endpoint - """ - if allow_head: - # it name is not None append -head to avoid it conflicting with - # the GET route below - head_name = name and '{}-head'.format(name) - self.add_route(hdrs.METH_HEAD, *args, name=head_name, **kwargs) - return self.add_route(hdrs.METH_GET, *args, name=name, **kwargs) +def head(path, handler=None, **kwargs): + if handler is None: + return _make_wrapper(hdrs.METH_HEAD, path, **kwargs) + else: + return _make_route(hdrs.METH_HEAD, path, handler, **kwargs) -def add_post(self, *args, **kwargs): - """ - Shortcut for add_route with method POST - """ - return self.add_route(hdrs.METH_POST, *args, **kwargs) +def get(path, handler=None, *, name=None, allow_head=True, **kwargs): + if handler is None: + return _make_wrapper(hdrs.METH_GET, path, name=name, + allow_head=allow_head, **kwargs) + else: + return _make_route(path, handler, name=name, + allow_head=allow_head, **kwargs) -def add_put(self, *args, **kwargs): - """ - Shortcut for add_route with method PUT - """ - return self.add_route(hdrs.METH_PUT, *args, **kwargs) +def post(path, handler=None, **kwargs): + if handler is None: + return _make_wrapper(hdrs.METH_POST, path, **kwargs) + else: + return _make_route(hdrs.METH_POST, path, handler, **kwargs) -def add_patch(self, *args, **kwargs): - """ - Shortcut for add_route with method PATCH - """ - return self.add_route(hdrs.METH_PATCH, *args, **kwargs) +def put(path, handler=None, **kwargs): + if handler is None: + return _make_wrapper(hdrs.METH_PUT, path, **kwargs) + else: + return _make_route(hdrs.METH_PUT, path, handler, **kwargs) -def add_delete(self, *args, **kwargs): - """ - Shortcut for add_route with method DELETE - """ - return self.add_route(hdrs.METH_DELETE, *args, **kwargs) +def patch(path, handler=None, **kwargs): + if handler is None: + return _make_wrapper(hdrs.METH_PATCH, path, **kwargs) + else: + return _make_route(hdrs.METH_PATCH, path, handler, **kwargs) + + +def delete(path, handler=None, **kwargs): + if handler is None: + return _make_wrapper(hdrs.METH_DELETE, path, **kwargs) + else: + return _make_route(hdrs.METH_DELETE, path, handler, **kwargs) From 0c23ebf4933737096f5f4e66889ed80ca49a0dc0 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Tue, 20 Jun 2017 17:20:01 +0300 Subject: [PATCH 04/12] Make examples work --- aiohttp/web_urldispatcher.py | 77 +++++++++++++++------------------ examples/web_srv_route_deco.py | 60 +++++++++++++++++++++++++ examples/web_srv_route_table.py | 62 ++++++++++++++++++++++++++ 3 files changed, 158 insertions(+), 41 deletions(-) create mode 100644 examples/web_srv_route_deco.py create mode 100644 examples/web_srv_route_table.py diff --git a/aiohttp/web_urldispatcher.py b/aiohttp/web_urldispatcher.py index 3848b8e9eff..259c43a4af6 100644 --- a/aiohttp/web_urldispatcher.py +++ b/aiohttp/web_urldispatcher.py @@ -6,6 +6,7 @@ import keyword import os import re +import sys import warnings from collections import namedtuple from collections.abc import Container, Iterable, Sized @@ -30,7 +31,8 @@ __all__ = ('UrlDispatcher', 'UrlMappingMatchInfo', 'AbstractResource', 'Resource', 'PlainResource', 'DynamicResource', 'AbstractRoute', 'ResourceRoute', - 'StaticResource', 'View') + 'StaticResource', 'View', + 'head', 'get', 'post', 'patch', 'put', 'delete') HTTP_METHOD_RE = re.compile(r"^[0-9A-Za-z!#\$%&'\*\+\-\.\^_`\|~]+$") PATH_SEP = re.escape('/') @@ -906,70 +908,63 @@ def add_routes(self, routes): reg(route.path, route.handler, **route.kwargs) def scan(self, package): - pass + prefix = package + '.' + for modname, mod in sys.modules.items(): + if modname == package or modname.startswith(prefix): + for name in dir(mod): + obj = getattr(mod, name) + route = getattr(obj, '__aiohttp_web__', None) + if route is not None: + reg = getattr(self, 'add_'+route.method.lower()) + reg(route.path, route.handler, **route.kwargs) def _make_route(method, path, handler, **kwargs): return RouteInfo(method, path, handler, kwargs) -def _make_wrapper(method, path, kwargs): +def _make_wrapper(method, path, **kwargs): def wrapper(handler): - @functools.wraps(handler) - def wrapped(**kwargs): - if hasattr(handler, '__aiohttp_web__'): - raise ValueError('Handler {handler!r} is registered already ' - 'as [{method}] {path} {kwargs}'.format( - handler=handler, - method=method, - path=path, - kwargs=kwargs)) - handler.__aiohttp_web__ = _make_route(method, path, - handler, kwargs) - return handler - return wrapped + if hasattr(handler, '__aiohttp_web__'): + raise ValueError('Handler {handler!r} is registered already ' + 'as [{method}] {path} {kwargs}'.format( + handler=handler, + method=method, + path=path, + kwargs=kwargs)) + handler.__aiohttp_web__ = _make_route(method, path, + handler, **kwargs) + return handler return wrapper -def head(path, handler=None, **kwargs): +def route(method, path, handler=None, **kwargs): if handler is None: - return _make_wrapper(hdrs.METH_HEAD, path, **kwargs) + return _make_wrapper(method, path, **kwargs) else: - return _make_route(hdrs.METH_HEAD, path, handler, **kwargs) + return _make_route(method, path, handler, **kwargs) + + +def head(path, handler=None, **kwargs): + return route(hdrs.METH_HEAD, path, handler, **kwargs) def get(path, handler=None, *, name=None, allow_head=True, **kwargs): - if handler is None: - return _make_wrapper(hdrs.METH_GET, path, name=name, - allow_head=allow_head, **kwargs) - else: - return _make_route(path, handler, name=name, - allow_head=allow_head, **kwargs) + return route(hdrs.METH_GET, path, handler, + allow_head=allow_head, **kwargs) def post(path, handler=None, **kwargs): - if handler is None: - return _make_wrapper(hdrs.METH_POST, path, **kwargs) - else: - return _make_route(hdrs.METH_POST, path, handler, **kwargs) + return route(hdrs.METH_POST, path, handler, **kwargs) def put(path, handler=None, **kwargs): - if handler is None: - return _make_wrapper(hdrs.METH_PUT, path, **kwargs) - else: - return _make_route(hdrs.METH_PUT, path, handler, **kwargs) + return route(hdrs.METH_PUT, path, handler, **kwargs) def patch(path, handler=None, **kwargs): - if handler is None: - return _make_wrapper(hdrs.METH_PATCH, path, **kwargs) - else: - return _make_route(hdrs.METH_PATCH, path, handler, **kwargs) + return route(hdrs.METH_PATCH, path, handler, **kwargs) def delete(path, handler=None, **kwargs): - if handler is None: - return _make_wrapper(hdrs.METH_DELETE, path, **kwargs) - else: - return _make_route(hdrs.METH_DELETE, path, handler, **kwargs) + return route(hdrs.METH_DELETE, path, handler, **kwargs) diff --git a/examples/web_srv_route_deco.py b/examples/web_srv_route_deco.py new file mode 100644 index 00000000000..b7ccb989141 --- /dev/null +++ b/examples/web_srv_route_deco.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Example for aiohttp.web basic server +with decorator definition for routes +""" + +import asyncio +import textwrap + +from aiohttp import web + + +@web.get('/') +async def intro(request): + txt = textwrap.dedent("""\ + Type {url}/hello/John {url}/simple or {url}/change_body + in browser url bar + """).format(url='127.0.0.1:8080') + binary = txt.encode('utf8') + resp = web.StreamResponse() + resp.content_length = len(binary) + resp.content_type = 'text/plain' + await resp.prepare(request) + resp.write(binary) + return resp + + +@web.get('/simple') +async def simple(request): + return web.Response(text="Simple answer") + + +@web.get('/change_body') +async def change_body(request): + resp = web.Response() + resp.body = b"Body changed" + resp.content_type = 'text/plain' + return resp + + +@web.get('/hello') +async def hello(request): + resp = web.StreamResponse() + name = request.match_info.get('name', 'Anonymous') + answer = ('Hello, ' + name).encode('utf8') + resp.content_length = len(answer) + resp.content_type = 'text/plain' + await resp.prepare(request) + resp.write(answer) + await resp.write_eof() + return resp + + +async def init(): + app = web.Application() + app.router.scan('__main__') + return app + +loop = asyncio.get_event_loop() +app = loop.run_until_complete(init()) +web.run_app(app) diff --git a/examples/web_srv_route_table.py b/examples/web_srv_route_table.py new file mode 100644 index 00000000000..7d8af62a5c2 --- /dev/null +++ b/examples/web_srv_route_table.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +"""Example for aiohttp.web basic server +with table definition for routes +""" + +import asyncio +import textwrap + +from aiohttp import web + + +async def intro(request): + txt = textwrap.dedent("""\ + Type {url}/hello/John {url}/simple or {url}/change_body + in browser url bar + """).format(url='127.0.0.1:8080') + binary = txt.encode('utf8') + resp = web.StreamResponse() + resp.content_length = len(binary) + resp.content_type = 'text/plain' + await resp.prepare(request) + resp.write(binary) + return resp + + +async def simple(request): + return web.Response(text="Simple answer") + + +async def change_body(request): + resp = web.Response() + resp.body = b"Body changed" + resp.content_type = 'text/plain' + return resp + + +async def hello(request): + resp = web.StreamResponse() + name = request.match_info.get('name', 'Anonymous') + answer = ('Hello, ' + name).encode('utf8') + resp.content_length = len(answer) + resp.content_type = 'text/plain' + await resp.prepare(request) + resp.write(answer) + await resp.write_eof() + return resp + + +async def init(): + app = web.Application() + app.router.add_routes([ + web.get('/', intro), + web.get('/simple', simple), + web.get('/change_body', change_body), + web.get('/hello/{name}', hello), + web.get('/hello', hello), + ]) + return app + +loop = asyncio.get_event_loop() +app = loop.run_until_complete(init()) +web.run_app(app) From 6cab8b802106b9058d7c9c532a01ca0834910b96 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Tue, 20 Jun 2017 17:39:22 +0300 Subject: [PATCH 05/12] Refactor --- aiohttp/web_urldispatcher.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/aiohttp/web_urldispatcher.py b/aiohttp/web_urldispatcher.py index 259c43a4af6..a9b44c59464 100644 --- a/aiohttp/web_urldispatcher.py +++ b/aiohttp/web_urldispatcher.py @@ -1,7 +1,6 @@ import abc import asyncio import collections -import functools import inspect import keyword import os @@ -37,7 +36,15 @@ HTTP_METHOD_RE = re.compile(r"^[0-9A-Za-z!#\$%&'\*\+\-\.\^_`\|~]+$") PATH_SEP = re.escape('/') -RouteInfo = namedtuple('RouteInfo', 'method, path, handler, kwargs') + +class RouteInfo(namedtuple('_RouteInfo', 'method, path, handler, kwargs')): + def register(self, router): + if self.method in hdrs.METH_ALL: + reg = getattr(router, 'add_'+self.method.lower()) + reg(self.path, self.handler, **self.kwargs) + else: + router.add_route(self.method, self.path, self.handler, + **self.kwargs) class AbstractResource(Sized, Iterable): @@ -903,9 +910,7 @@ def freeze(self): def add_routes(self, routes): for route in routes: - assert route.method in hdrs.METH_ALL - reg = getattr(self, 'add_'+route.method.lower()) - reg(route.path, route.handler, **route.kwargs) + route.register(self) def scan(self, package): prefix = package + '.' @@ -915,8 +920,7 @@ def scan(self, package): obj = getattr(mod, name) route = getattr(obj, '__aiohttp_web__', None) if route is not None: - reg = getattr(self, 'add_'+route.method.lower()) - reg(route.path, route.handler, **route.kwargs) + route.register(self) def _make_route(method, path, handler, **kwargs): From 8b44cdee38df7520dc672f7919f9e1d85fff0b56 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Tue, 20 Jun 2017 17:55:53 +0300 Subject: [PATCH 06/12] sort modules for scanning --- aiohttp/web_urldispatcher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiohttp/web_urldispatcher.py b/aiohttp/web_urldispatcher.py index a9b44c59464..900f9c1b32f 100644 --- a/aiohttp/web_urldispatcher.py +++ b/aiohttp/web_urldispatcher.py @@ -914,7 +914,7 @@ def add_routes(self, routes): def scan(self, package): prefix = package + '.' - for modname, mod in sys.modules.items(): + for modname, mod in sorted(sys.modules.items()): if modname == package or modname.startswith(prefix): for name in dir(mod): obj = getattr(mod, name) From 1d5492c4d76108991c7c3fb442be2d3650e2644d Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Tue, 20 Jun 2017 18:37:09 +0300 Subject: [PATCH 07/12] Go forward --- aiohttp/web_urldispatcher.py | 3 +- tests/test_route_table.py | 105 +++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 tests/test_route_table.py diff --git a/aiohttp/web_urldispatcher.py b/aiohttp/web_urldispatcher.py index 900f9c1b32f..bd4fd75ae31 100644 --- a/aiohttp/web_urldispatcher.py +++ b/aiohttp/web_urldispatcher.py @@ -31,7 +31,7 @@ 'AbstractResource', 'Resource', 'PlainResource', 'DynamicResource', 'AbstractRoute', 'ResourceRoute', 'StaticResource', 'View', - 'head', 'get', 'post', 'patch', 'put', 'delete') + 'head', 'get', 'post', 'patch', 'put', 'delete', 'route') HTTP_METHOD_RE = re.compile(r"^[0-9A-Za-z!#\$%&'\*\+\-\.\^_`\|~]+$") PATH_SEP = re.escape('/') @@ -909,6 +909,7 @@ def freeze(self): resource.freeze() def add_routes(self, routes): + # TODO: add_table maybe? for route in routes: route.register(self) diff --git a/tests/test_route_table.py b/tests/test_route_table.py new file mode 100644 index 00000000000..33ba97f72a5 --- /dev/null +++ b/tests/test_route_table.py @@ -0,0 +1,105 @@ +import asyncio + +import pytest + +from aiohttp import web +from aiohttp.web_urldispatcher import UrlDispatcher + + +@pytest.fixture +def router(): + return UrlDispatcher() + + +def test_get(router): + @asyncio.coroutine + def handler(request): + pass + + router.add_routes([web.get('/', handler)]) + assert len(router.routes()) == 2 # GET and HEAD + + route = list(router.routes())[0] + assert route.handler is handler + assert route.method == 'GET' + + route2 = list(router.routes())[1] + assert route2.handler is handler + assert route2.method == 'HEAD' + + +def test_head(router): + @asyncio.coroutine + def handler(request): + pass + + router.add_routes([web.head('/', handler)]) + assert len(router.routes()) == 1 + + route = list(router.routes())[0] + assert route.handler is handler + assert route.method == 'HEAD' + + +def test_post(router): + @asyncio.coroutine + def handler(request): + pass + + router.add_routes([web.post('/', handler)]) + + route = list(router.routes())[0] + assert route.handler is handler + assert route.method == 'POST' + + +def test_put(router): + @asyncio.coroutine + def handler(request): + pass + + router.add_routes([web.put('/', handler)]) + assert len(router.routes()) == 1 + + route = list(router.routes())[0] + assert route.handler is handler + assert route.method == 'PUT' + + +def test_patch(router): + @asyncio.coroutine + def handler(request): + pass + + router.add_routes([web.patch('/', handler)]) + assert len(router.routes()) == 1 + + route = list(router.routes())[0] + assert route.handler is handler + assert route.method == 'PATCH' + + +def test_delete(router): + @asyncio.coroutine + def handler(request): + pass + + router.add_routes([web.delete('/', handler)]) + assert len(router.routes()) == 1 + + route = list(router.routes())[0] + assert route.handler is handler + assert route.method == 'DELETE' + + +def test_route(router): + @asyncio.coroutine + def handler(request): + pass + + router.add_routes([web.route('OPTIONS', '/', handler)]) + assert len(router.routes()) == 1 + + route = list(router.routes())[0] + assert route.handler is handler + assert route.method == 'OPTIONS' From 3eb5137cc4f84b0b8590e035add7a8816a09a2cd Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Tue, 20 Jun 2017 19:56:24 +0300 Subject: [PATCH 08/12] Add tests --- tests/test_route_table.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_route_table.py b/tests/test_route_table.py index 33ba97f72a5..3fc1813c873 100644 --- a/tests/test_route_table.py +++ b/tests/test_route_table.py @@ -19,11 +19,11 @@ def handler(request): router.add_routes([web.get('/', handler)]) assert len(router.routes()) == 2 # GET and HEAD - route = list(router.routes())[0] + route = list(router.routes())[1] assert route.handler is handler assert route.method == 'GET' - route2 = list(router.routes())[1] + route2 = list(router.routes())[0] assert route2.handler is handler assert route2.method == 'HEAD' @@ -97,9 +97,9 @@ def test_route(router): def handler(request): pass - router.add_routes([web.route('OPTIONS', '/', handler)]) + router.add_routes([web.route('OTHER', '/', handler)]) assert len(router.routes()) == 1 route = list(router.routes())[0] assert route.handler is handler - assert route.method == 'OPTIONS' + assert route.method == 'OTHER' From a5c24a044669b51db804a0365997004a3f312943 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Wed, 21 Jun 2017 11:00:55 +0300 Subject: [PATCH 09/12] Add test for decoration methods --- tests/test_route_table.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_route_table.py b/tests/test_route_table.py index 3fc1813c873..b1722add64c 100644 --- a/tests/test_route_table.py +++ b/tests/test_route_table.py @@ -22,6 +22,7 @@ def handler(request): route = list(router.routes())[1] assert route.handler is handler assert route.method == 'GET' + assert str(route.url_for()) == '/' route2 = list(router.routes())[0] assert route2.handler is handler @@ -39,6 +40,7 @@ def handler(request): route = list(router.routes())[0] assert route.handler is handler assert route.method == 'HEAD' + assert str(route.url_for()) == '/' def test_post(router): @@ -51,6 +53,7 @@ def handler(request): route = list(router.routes())[0] assert route.handler is handler assert route.method == 'POST' + assert str(route.url_for()) == '/' def test_put(router): @@ -64,6 +67,7 @@ def handler(request): route = list(router.routes())[0] assert route.handler is handler assert route.method == 'PUT' + assert str(route.url_for()) == '/' def test_patch(router): @@ -77,6 +81,7 @@ def handler(request): route = list(router.routes())[0] assert route.handler is handler assert route.method == 'PATCH' + assert str(route.url_for()) == '/' def test_delete(router): @@ -90,6 +95,7 @@ def handler(request): route = list(router.routes())[0] assert route.handler is handler assert route.method == 'DELETE' + assert str(route.url_for()) == '/' def test_route(router): @@ -103,3 +109,4 @@ def handler(request): route = list(router.routes())[0] assert route.handler is handler assert route.method == 'OTHER' + assert str(route.url_for()) == '/' From 910cf1c56760604d9fc738f388c27aca9ea7bfdc Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Wed, 21 Jun 2017 14:47:29 +0300 Subject: [PATCH 10/12] Add missing file --- tests/test_route_deco.py | 64 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 tests/test_route_deco.py diff --git a/tests/test_route_deco.py b/tests/test_route_deco.py new file mode 100644 index 00000000000..48f4f2b27d7 --- /dev/null +++ b/tests/test_route_deco.py @@ -0,0 +1,64 @@ +import asyncio +import sys +from importlib.machinery import ModuleSpec, SourceFileLoader +from importlib.util import module_from_spec +from textwrap import dedent + +import pytest + +from aiohttp import web +from aiohttp.web_urldispatcher import UrlDispatcher + + +@pytest.fixture +def router(): + return UrlDispatcher() + + +def test_add_routeinfo(router): + @web.get('/path') + @asyncio.coroutine + def handler(request): + pass + + assert hasattr(handler, '__aiohttp_web__') + info = handler.__aiohttp_web__ + assert info.method == 'GET' + assert info.path == '/path' + assert info.handler is handler + + +def test_add_routeinfo_twice(router): + with pytest.raises(ValueError): + @web.get('/path') + @web.post('/path') + @asyncio.coroutine + def handler(request): + pass + + +def test_scan(router): + loader = SourceFileLoader('', '') + spec = ModuleSpec('aiohttp.tmp_test', loader, is_package=False) + mod = module_from_spec(spec) + sys.modules[mod.__name__] = mod + content = dedent("""\ + import asyncio + from aiohttp import web + + @web.head('/path') + @asyncio.coroutine + def handler(request): + pass + """) + try: + exec(content, mod.__dict__) + router.scan(mod.__name__) + + assert len(router.routes()) == 1 + + route = list(router.routes())[0] + assert route.method == 'HEAD' + assert str(route.url_for()) == '/path' + finally: + del sys.modules[mod.__name__] From 36d4fc0faec54148baca67fd9dc1d5789fe49198 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Thu, 22 Jun 2017 22:18:16 +0300 Subject: [PATCH 11/12] Fix python 3.4, add test --- tests/test_route_deco.py | 94 +++++++++++++++++++++++++++++++++------- 1 file changed, 78 insertions(+), 16 deletions(-) diff --git a/tests/test_route_deco.py b/tests/test_route_deco.py index 48f4f2b27d7..7c67d5beb38 100644 --- a/tests/test_route_deco.py +++ b/tests/test_route_deco.py @@ -1,7 +1,5 @@ import asyncio import sys -from importlib.machinery import ModuleSpec, SourceFileLoader -from importlib.util import module_from_spec from textwrap import dedent import pytest @@ -10,6 +8,39 @@ from aiohttp.web_urldispatcher import UrlDispatcher +if sys.version_info >= (3, 5): + @pytest.fixture + def create_module(): + from importlib.machinery import ModuleSpec, SourceFileLoader + from importlib.util import module_from_spec + mods = [] + + def maker(name, *, is_package=False): + loader = SourceFileLoader('', '') + spec = ModuleSpec(name, loader, is_package=is_package) + mod = module_from_spec(spec) + sys.modules[mod.__name__] = mod + mods.append(mod) + return mod + yield maker + for mod in mods: + del sys.modules[mod.__name__] +else: + @pytest.fixture + def create_module(name): + from imp import new_module + + mods = [] + + def maker(name, *, is_package=False): + mod = new_module(name) + sys.modules[mod.__name__] = mod + mods.append(mod) + yield maker + for mod in mods: + del sys.modules[mod.__name__] + + @pytest.fixture def router(): return UrlDispatcher() @@ -37,11 +68,8 @@ def handler(request): pass -def test_scan(router): - loader = SourceFileLoader('', '') - spec = ModuleSpec('aiohttp.tmp_test', loader, is_package=False) - mod = module_from_spec(spec) - sys.modules[mod.__name__] = mod +def test_scan_mod(router, create_module): + mod = create_module('aiohttp.tmp.test_mod') content = dedent("""\ import asyncio from aiohttp import web @@ -51,14 +79,48 @@ def test_scan(router): def handler(request): pass """) - try: - exec(content, mod.__dict__) - router.scan(mod.__name__) + exec(content, mod.__dict__) + router.scan(mod.__name__) + + assert len(router.routes()) == 1 + + route = list(router.routes())[0] + assert route.method == 'HEAD' + assert str(route.url_for()) == '/path' + + +def test_scan_package(router, create_module): + mod = create_module('aiohttp.tmp', is_package=True) + mod1 = create_module('aiohttp.tmp.test_mod1') + content1 = dedent("""\ + import asyncio + from aiohttp import web + + @web.head('/path1') + @asyncio.coroutine + def handler(request): + pass + """) + exec(content1, mod1.__dict__) + mod2 = create_module('aiohttp.tmp.test_mod2') + content2 = dedent("""\ + import asyncio + from aiohttp import web + + @web.put('/path2') + @asyncio.coroutine + def handler(request): + pass + """) + exec(content2, mod2.__dict__) + router.scan(mod.__package__) + + assert len(router.routes()) == 2 - assert len(router.routes()) == 1 + route1 = list(router.routes())[0] + assert route1.method == 'HEAD' + assert str(route1.url_for()) == '/path1' - route = list(router.routes())[0] - assert route.method == 'HEAD' - assert str(route.url_for()) == '/path' - finally: - del sys.modules[mod.__name__] + route1 = list(router.routes())[1] + assert route1.method == 'PUT' + assert str(route1.url_for()) == '/path2' From de48fdaae66ad388c236cb1ff3828457f428143e Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Thu, 22 Jun 2017 23:08:13 +0300 Subject: [PATCH 12/12] Fix typo --- tests/test_route_deco.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_route_deco.py b/tests/test_route_deco.py index 7c67d5beb38..4584fb6323b 100644 --- a/tests/test_route_deco.py +++ b/tests/test_route_deco.py @@ -27,7 +27,7 @@ def maker(name, *, is_package=False): del sys.modules[mod.__name__] else: @pytest.fixture - def create_module(name): + def create_module(): from imp import new_module mods = []