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

Allow route registration with decorator #1735

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
22 changes: 22 additions & 0 deletions aiohttp/web.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import asyncio
import inspect
import os
import socket
import stat
Expand Down Expand Up @@ -58,6 +59,7 @@ def __init__(self, *, logger=web_logger, router=None, middlewares=(),
self._secure_proxy_ssl_header = None
self._loop = loop
self._handler_args = handler_args
self._module = None
self.logger = logger

self._middlewares = FrozenList(middlewares)
Expand Down Expand Up @@ -215,6 +217,26 @@ def on_cleanup(self):
def router(self):
return self._router

def route(self, path, method=hdrs.METH_GET,
*, name=None, expect_handler=None):
""" Add route to path """

def wrapper(handler):
module = inspect.getmodule(handler)
if self._module is None:
self._module = module
else:
if module is not self._module:
raise TypeError(
'Application.route decorator could be used only '
'in one python module')

resource = self._router.add_resource(path, name=name)
resource.add_route(method, handler, expect_handler=None)
return handler

return wrapper

@property
def middlewares(self):
return self._middlewares
Expand Down
43 changes: 41 additions & 2 deletions tests/test_web_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

import pytest

from aiohttp import helpers, log, web
from aiohttp.abc import AbstractRouter
from aiohttp import hdrs, helpers, log, web
from aiohttp.abc import AbstractRouter, abstractmethod


def test_app_ctor(loop):
Expand Down Expand Up @@ -111,6 +111,45 @@ def test_non_default_router():
assert router is app.router


class MockAbstractRouter(AbstractRouter):

@abstractmethod
def add_resource(self, oath, name=None):
pass


def test_add_route(loop):
router = mock.Mock(spec=MockAbstractRouter)
app = web.Application(router=router)

@app.route('/index.html')
Copy link
Contributor

Choose a reason for hiding this comment

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

in my own impplementation of such decorator, I add reference to decorated method and all decorator's kwargs and args to some global array. And also I have a function that adds this saved information into application's routes.

In short (pseudocode):

global_array = []
def handler(*args, **kwargs):
    def decorator(function):
        global_array.append((function, args, kwargs))
        return function
    return decorator

@handler(....)
async def myhandler(request):
    ....

class MyApplication(web.Application):
    def add_saved_routes(self):
        for (method, args, kwargs):
            self.add_route(....)

def main():
    app = MyApplication()
    app.add_saved_routes()
    ...

This allows me to use decorators BEFORE creating application (during imports). This is especially important since I have many handlers which are placed in many files.

So, could you provide complete example showing usage of your decorators for the case when there are many files with handlers ?

def get(req):
pass

router.add_resource.assert_called_with(
'/index.html', name=None)
router.add_resource.return_value.add_route(
hdrs.METH_GET, get, expect_handler=None)

@app.route('/index.html', hdrs.METH_POST, name='test')
def post(req):
pass

router.add_resource.assert_called_with(
'/index.html', name='test')
router.add_resource.return_value.add_route(
hdrs.METH_POST, post, expect_handler=None)


def test_add_route_different_module(loop):
from test_web_application_helpers import app_test

with pytest.raises(TypeError):
@app_test.route('/404')
def get(req):
pass


def test_logging():
logger = mock.Mock()
app = web.Application()
Expand Down
9 changes: 9 additions & 0 deletions tests/test_web_application_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from aiohttp import web


app_test = web.Application()


@app_test.route('/index.html')
def get(req):
pass