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

Classbasedview #684

Merged
merged 8 commits into from
Dec 23, 2015
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
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
16 changes: 16 additions & 0 deletions aiohttp/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,19 @@ def handler(self):
@abstractmethod
def route(self):
"""Return route for match info"""


class AbstractView(metaclass=ABCMeta):

def __init__(self, request):
self._request = request

@property
def request(self):
return self._request

@asyncio.coroutine
@abstractmethod
def __iter__(self):
while False: # pragma: no cover
yield None
4 changes: 4 additions & 0 deletions aiohttp/hdrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
METH_PUT = upstr('PUT')
METH_TRACE = upstr('TRACE')

METH_ALL = {METH_CONNECT, METH_HEAD, METH_GET, METH_DELETE,
METH_OPTIONS, METH_PATCH, METH_POST, METH_PUT, METH_TRACE}


ACCEPT = upstr('ACCEPT')
ACCEPT_CHARSET = upstr('ACCEPT-CHARSET')
ACCEPT_ENCODING = upstr('ACCEPT-ENCODING')
Expand Down
30 changes: 26 additions & 4 deletions aiohttp/web_urldispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@
from types import MappingProxyType

from . import hdrs
from .abc import AbstractRouter, AbstractMatchInfo
from .abc import AbstractRouter, AbstractMatchInfo, AbstractView
from .protocol import HttpVersion11
from .web_exceptions import HTTPMethodNotAllowed, HTTPNotFound, HTTPNotModified
from .web_reqrep import StreamResponse
from .multidict import upstr


__all__ = ('UrlDispatcher', 'UrlMappingMatchInfo',
'Route', 'PlainRoute', 'DynamicRoute', 'StaticRoute')
'Route', 'PlainRoute', 'DynamicRoute', 'StaticRoute', 'View')


class UrlMappingMatchInfo(dict, AbstractMatchInfo):
Expand Down Expand Up @@ -372,6 +372,23 @@ def __repr__(self):
', '.join(sorted(self._allowed_methods))))


class View(AbstractView):

@asyncio.coroutine
def __iter__(self):
if self.request.method not in hdrs.METH_ALL:
self._raise_allowed_methods()
method = getattr(self, self.request.method.lower(), None)
if method is None:
self._raise_allowed_methods()
resp = yield from method()
return resp

def _raise_allowed_methods(self):
allowed_methods = {m for m in hdrs.METH_ALL if hasattr(self, m)}
raise HTTPMethodNotAllowed(self.request.method, allowed_methods)


class RoutesView(Sized, Iterable, Container):

__slots__ = '_urls'
Expand Down Expand Up @@ -477,8 +494,13 @@ def add_route(self, method, path, handler,
raise ValueError("path should be started with /")

assert callable(handler), handler
if (not asyncio.iscoroutinefunction(handler) and
not inspect.isgeneratorfunction(handler)):
if asyncio.iscoroutinefunction(handler):
pass
elif inspect.isgeneratorfunction(handler):
pass
elif isinstance(handler, type) and issubclass(handler, AbstractView):
pass
else:
handler = asyncio.coroutine(handler)

method = upstr(method)
Expand Down
67 changes: 67 additions & 0 deletions examples/web_classview1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env python3
"""Example for aiohttp.web class based views
"""


import asyncio
import functools
import json
from aiohttp.web import json_response, Application, Response, View


class MyView(View):

async def get(self):
return json_response({
'method': 'get',
'args': dict(self.request.GET),
'headers': dict(self.request.headers),
}, dumps=functools.partial(json.dumps, indent=4))

async def post(self):
data = await self.request.post()
return json_response({
'method': 'post',
'args': dict(self.request.GET),
'data': dict(data),
'headers': dict(self.request.headers),
}, dumps=functools.partial(json.dumps, indent=4))


async def index(request):
txt = """
<html>
<head>
<title>Class based view example</title>
</head>
<body>
<h1>Class based view example</h1>
<ul>
<li><a href="/">/</a> This page
<li><a href="/get">/get</a> Returns GET data.
<li><a href="/post">/post</a> Returns POST data.
</ul>
</body>
</html>
"""
return Response(text=txt, content_type='text/html')


async def init(loop):
app = Application(loop=loop)
app.router.add_route('GET', '/', index)
app.router.add_route('GET', '/get', MyView)
app.router.add_route('POST', '/post', MyView)
Copy link
Member

Choose a reason for hiding this comment

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

So what's the profit?

  1. You still need to register routes by hand (not auto-discovered by router).
  2. The MyView class is used not instance of MyView, so no custom initialization could be done. This would lead to some "view factories" like:
def my_view_factory(*args, **kw):
    return lambda request: MyView(request, *args, **kw)

Copy link
Member Author

Choose a reason for hiding this comment

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

People asked for "class based views like Django" many times.
Personally I prefer another style of views organizing like http://aiohttp.readthedocs.org/en/stable/web.html#using-plain-coroutines-and-classes-for-web-handlers you know.

  1. Views should not be autodiscovered (but you may use '*' as method name).
  2. Yes, in non-trivial cases user should use custom factories (just like for asyncio protocols). I don't want to make a mess adding clumsy initializers like django's View.as_view() etc.

Copy link
Member

Choose a reason for hiding this comment

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

ok, agree to 1.
2. looks like a next request after this one.

Anyway, I not sure which is worse -- to have "class based views like Django" in aiohttp.web or to let people reinvent the wheel themselfs...

Copy link
Member

Choose a reason for hiding this comment

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

If people asked me the question I would try to explain why they don't need it

Copy link
Member Author

Choose a reason for hiding this comment

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

The problem is in this line: https://github.com/KeepSafe/aiohttp/pull/684/files#diff-b41dc5c66bc19f22fe7cc77c002db02cR501

Without it people may do it themselves (while most of them not experienced enough to override __iter__ or __await__ properly).


handler = app.make_handler()
srv = await loop.create_server(handler, '127.0.0.1', 8080)
print("Server started at http://127.0.0.1:8080")
return srv, handler


loop = asyncio.get_event_loop()
srv, handler = loop.run_until_complete(init(loop))
try:
loop.run_forever()
except KeyboardInterrupt:
loop.run_until_complete(handler.finish_connections())