Skip to content

[WIP] New route definitions #2003

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 14 commits into from
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