Skip to content
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

Route definitions #2004

Merged
merged 31 commits into from
Aug 4, 2017
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a2fbd23
First scratches
asvetlov May 24, 2017
62f653a
Work on
asvetlov Jun 1, 2017
2ce063f
Work on decorators
asvetlov Jun 4, 2017
727ea50
Merge branch 'master' into route_deco
asvetlov Jun 14, 2017
daa31ec
Merge branch 'master' into route_deco
asvetlov Jun 20, 2017
0c23ebf
Make examples work
asvetlov Jun 20, 2017
6cab8b8
Refactor
asvetlov Jun 20, 2017
8b44cde
sort modules for scanning
asvetlov Jun 20, 2017
1d5492c
Go forward
asvetlov Jun 20, 2017
3eb5137
Add tests
asvetlov Jun 20, 2017
a5c24a0
Add test for decoration methods
asvetlov Jun 21, 2017
910cf1c
Add missing file
asvetlov Jun 21, 2017
36d4fc0
Fix python 3.4, add test
asvetlov Jun 22, 2017
de48fda
Fix typo
asvetlov Jun 22, 2017
234ed0e
Implement RouteDef
asvetlov Jun 22, 2017
2f5bb08
Merge branch 'master' into route_deco2
asvetlov Jun 22, 2017
d158513
Test cover
asvetlov Jun 22, 2017
b8ad2f4
RouteDef -> RoutesDef
asvetlov Jun 22, 2017
7391a69
RouteInfo -> RouteDef
asvetlov Jun 22, 2017
341da53
Add couple TODOs, drop RouteDef from exported names
asvetlov Jun 22, 2017
ec0630a
Fix flake8 blame
asvetlov Jun 23, 2017
9c1845c
RoutesDef -> RouteTableDef
asvetlov Aug 2, 2017
a9cbbb4
Merge remote-tracking branch 'origin/master' into route_deco2
asvetlov Aug 2, 2017
e2bc869
Add reprs
asvetlov Aug 2, 2017
58686cb
Add changes record
asvetlov Aug 2, 2017
875ae8e
Test cover missed case
asvetlov Aug 2, 2017
7c54e36
Add documentation for new route definitions API in web reference
asvetlov Aug 2, 2017
48203d3
Fix typo
asvetlov Aug 2, 2017
68f7225
Mention route tables and route decorators in web usage
asvetlov Aug 2, 2017
aaab3ed
Text flow polishing
asvetlov Aug 2, 2017
c10f4e0
Fix typo
asvetlov Aug 3, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 105 additions & 2 deletions aiohttp/web_urldispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
import os
import re
import warnings
from collections.abc import Container, Iterable, Sized
from collections import namedtuple
from collections.abc import Container, Iterable, Sequence, Sized
from functools import wraps
from pathlib import Path
from types import MappingProxyType
Expand All @@ -28,13 +29,32 @@
__all__ = ('UrlDispatcher', 'UrlMappingMatchInfo',
'AbstractResource', 'Resource', 'PlainResource', 'DynamicResource',
'AbstractRoute', 'ResourceRoute',
'StaticResource', 'View')
'StaticResource', 'View', 'RouteDef', 'RouteTableDef',
'head', 'get', 'post', 'patch', 'put', 'delete', 'route')

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


class RouteDef(namedtuple('_RouteDef', 'method, path, handler, kwargs')):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not only Route?
What's the meaning of Def?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we already use Route name for routes added by app.router.add_route() call.
RouteDef is a scratch for non-added-yet route definition

def __repr__(self):
info = []
for name, value in sorted(self.kwargs.items()):
info += ", {}={}".format(name, value)
return ("<RouteDef {method} {path} -> {handler.__name__!r}"
"{info}>".format(method=self.method, path=self.path,
handler=self.handler, info=''.join(info)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think will be better if info will be string
then you can delete ''.join(info)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This style is used by asyncio in many places, I like to keep it in aiohttp too


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 @@ -897,3 +917,86 @@ def freeze(self):
super().freeze()
for resource in self._resources:
resource.freeze()

def add_routes(self, routes):
"""Append routes to route table.

Parameter should be a sequence of RouteDef objects.
"""
# TODO: add_table maybe?
for route in routes:
route.register(self)


def route(method, path, handler, **kwargs):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think after refactoring this function is not required

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is: user might want to provide custom HTTP method in very rare cases

return RouteDef(method, path, handler, kwargs)


def head(path, handler, **kwargs):
return route(hdrs.METH_HEAD, path, handler, **kwargs)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the code gets more readable when we do not use abbreviations. Can I open a PR by renaming hdrs to headers?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good in general but for this particular case it doesn't make sense I think.
hdrs is not a part of our public API anyway.



def get(path, handler, *, name=None, allow_head=True, **kwargs):
return route(hdrs.METH_GET, path, handler,
allow_head=allow_head, **kwargs)


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


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


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


def delete(path, handler, **kwargs):
return route(hdrs.METH_DELETE, path, handler, **kwargs)


class RouteTableDef(Sequence):
"""Route definition table"""
def __init__(self):
self._items = []

def __repr__(self):
return "<RouteTableDef count={}>".format(len(self._items))

def __getitem__(self, index):
return self._items[index]

def __iter__(self):
return iter(self._items)

def __len__(self):
return len(self._items)

def __contains__(self, item):
return item in self._items

def route(self, method, path, **kwargs):
def inner(handler):
self._items.append(RouteDef(method, path, handler, kwargs))
return handler
return inner

def head(self, path, **kwargs):
return self.route(hdrs.METH_HEAD, path, **kwargs)

def get(self, path, **kwargs):
return self.route(hdrs.METH_GET, path, **kwargs)

def post(self, path, **kwargs):
return self.route(hdrs.METH_POST, path, **kwargs)

def put(self, path, **kwargs):
return self.route(hdrs.METH_PUT, path, **kwargs)

def patch(self, path, **kwargs):
return self.route(hdrs.METH_PATCH, path, **kwargs)

def delete(self, path, **kwargs):
return self.route(hdrs.METH_DELETE, path, **kwargs)
63 changes: 63 additions & 0 deletions examples/web_srv_route_deco.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env python3
"""Example for aiohttp.web basic server
with decorator definition for routes
"""

import asyncio
import textwrap

from aiohttp import web


routes = web.RouteTableDef()


@routes.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


@routes.get('/simple')
async def simple(request):
return web.Response(text="Simple answer")


@routes.get('/change_body')
async def change_body(request):
resp = web.Response()
resp.body = b"Body changed"
resp.content_type = 'text/plain'
return resp


@routes.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.add_routes(routes)
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)
Loading