Skip to content
81 changes: 80 additions & 1 deletion aiohttp/web_urldispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import keyword
import os
import re
import sys
import warnings
from collections import namedtuple
from collections.abc import Container, Iterable, Sized
from functools import wraps
from pathlib import Path
Expand All @@ -28,12 +30,23 @@
__all__ = ('UrlDispatcher', 'UrlMappingMatchInfo',
'AbstractResource', 'Resource', 'PlainResource', 'DynamicResource',
'AbstractRoute', 'ResourceRoute',
'StaticResource', 'View')
'StaticResource', 'View',
'head', 'get', 'post', 'patch', 'put', 'delete', 'route')

HTTP_METHOD_RE = re.compile(r"^[0-9A-Za-z!#\$%&'\*\+\-\.\^_`\|~]+$")
PATH_SEP = re.escape('/')


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):

def __init__(self, *, name=None):
Expand Down Expand Up @@ -894,3 +907,69 @@ def freeze(self):
super().freeze()
for resource in self._resources:
resource.freeze()

def add_routes(self, routes):
# TODO: add_table maybe?
for route in routes:
route.register(self)

def scan(self, package):
prefix = package + '.'
for modname, mod in sorted(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:
route.register(self)


def _make_route(method, path, handler, **kwargs):
return RouteInfo(method, path, handler, kwargs)


def _make_wrapper(method, path, **kwargs):
def wrapper(handler):
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 route(method, path, handler=None, **kwargs):
if handler is None:
return _make_wrapper(method, path, **kwargs)
else:
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):
return route(hdrs.METH_GET, path, handler,
allow_head=allow_head, **kwargs)


def post(path, handler=None, **kwargs):
return route(hdrs.METH_POST, path, handler, **kwargs)


def put(path, handler=None, **kwargs):
return route(hdrs.METH_PUT, path, handler, **kwargs)


def patch(path, handler=None, **kwargs):
return route(hdrs.METH_PATCH, path, handler, **kwargs)


def delete(path, handler=None, **kwargs):
return route(hdrs.METH_DELETE, path, handler, **kwargs)
60 changes: 60 additions & 0 deletions examples/web_srv_route_deco.py
Original file line number Diff line number Diff line change
@@ -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)
62 changes: 62 additions & 0 deletions examples/web_srv_route_table.py
Original file line number Diff line number Diff line change
@@ -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)
126 changes: 126 additions & 0 deletions tests/test_route_deco.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import asyncio
import sys
from textwrap import dedent

import pytest

from aiohttp import web
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('<fullname>', '<path>')
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():
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()


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_mod(router, create_module):
mod = create_module('aiohttp.tmp.test_mod')
content = dedent("""\
import asyncio
from aiohttp import web

@web.head('/path')
@asyncio.coroutine
def handler(request):
pass
""")
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

route1 = list(router.routes())[0]
assert route1.method == 'HEAD'
assert str(route1.url_for()) == '/path1'

route1 = list(router.routes())[1]
assert route1.method == 'PUT'
assert str(route1.url_for()) == '/path2'
Loading