Skip to content

Commit

Permalink
New-style middleware by default (#3710)
Browse files Browse the repository at this point in the history
  • Loading branch information
Artem Yushkovskiy authored and asvetlov committed May 10, 2019
1 parent 4b9adf7 commit c8cbbcc
Show file tree
Hide file tree
Showing 10 changed files with 62 additions and 167 deletions.
2 changes: 2 additions & 0 deletions CHANGES/3569.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Make new style middleware default, deprecate the @middleware decorator and
remove support for old-style middleware.
1 change: 1 addition & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Andrii Soldatenko
Antoine Pietri
Anton Kasyanov
Anton Zhdan-Pushkin
Artem Yushkovskiy
Arthur Darcet
Ben Bader
Benedikt Reinartz
Expand Down
33 changes: 10 additions & 23 deletions aiohttp/web_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,9 @@
_RespPrepareSignal = Signal[Callable[[Request, StreamResponse],
Awaitable[None]]]
_Handler = Callable[[Request], Awaitable[StreamResponse]]
_Middleware = Union[Callable[[Request, _Handler],
Awaitable[StreamResponse]],
Callable[['Application', _Handler], # old-style
Awaitable[_Handler]]]
_Middleware = Callable[[Request, _Handler], Awaitable[StreamResponse]]
_Middlewares = FrozenList[_Middleware]
_MiddlewaresHandlers = Optional[Sequence[Tuple[_Middleware, bool]]]
_MiddlewaresHandlers = Sequence[_Middleware]
_Subapps = List['Application']
else:
# No type checker mode, skip types
Expand All @@ -67,7 +64,7 @@
_Handler = Callable
_Middleware = Callable
_Middlewares = FrozenList
_MiddlewaresHandlers = Optional[Sequence]
_MiddlewaresHandlers = Sequence
_Subapps = List


Expand Down Expand Up @@ -104,7 +101,7 @@ def __init__(self, *,
self._middlewares = FrozenList(middlewares) # type: _Middlewares

# initialized on freezing
self._middlewares_handlers = None # type: _MiddlewaresHandlers
self._middlewares_handlers = tuple() # type: _MiddlewaresHandlers
# initialized on freezing
self._run_middlewares = None # type: Optional[bool]

Expand Down Expand Up @@ -390,17 +387,9 @@ def _make_request(self, message: RawRequestMessage,
self._loop,
client_max_size=self._client_max_size)

def _prepare_middleware(self) -> Iterator[Tuple[_Middleware, bool]]:
for m in reversed(self._middlewares):
if getattr(m, '__middleware_version__', None) == 1:
yield m, True
else:
warnings.warn('old-style middleware "{!r}" deprecated, '
'see #2252'.format(m),
DeprecationWarning, stacklevel=2)
yield m, False

yield _fix_request_current_app(self), True
def _prepare_middleware(self) -> Iterator[_Middleware]:
yield from reversed(self._middlewares)
yield _fix_request_current_app(self)

async def _handle(self, request: Request) -> StreamResponse:
loop = asyncio.get_event_loop()
Expand All @@ -426,11 +415,9 @@ async def _handle(self, request: Request) -> StreamResponse:

if self._run_middlewares:
for app in match_info.apps[::-1]:
for m, new_style in app._middlewares_handlers: # type: ignore # noqa
if new_style:
handler = partial(m, handler=handler)
else:
handler = await m(app, handler) # type: ignore
assert app.pre_frozen, "middleware handlers are not ready"
for m in app._middlewares_handlers: # noqa
handler = partial(m, handler=handler)

resp = await handler(request)

Expand Down
7 changes: 4 additions & 3 deletions aiohttp/web_middlewares.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import re
import warnings
from typing import TYPE_CHECKING, Awaitable, Callable, Tuple, Type, TypeVar

from .web_exceptions import HTTPMove, HTTPMovedPermanently
Expand Down Expand Up @@ -31,7 +32,9 @@ async def _check_request_resolves(request: Request,


def middleware(f: _Func) -> _Func:
f.__middleware_version__ = 1 # type: ignore
warnings.warn(
'Middleware decorator is deprecated and its behaviour is default, '
'you can simply remove this decorator.', DeprecationWarning)
return f


Expand Down Expand Up @@ -76,7 +79,6 @@ def normalize_path_middleware(
correct_configuration = not (append_slash and remove_slash)
assert correct_configuration, "Cannot both remove and append slash"

@middleware
async def impl(request: Request, handler: _Handler) -> StreamResponse:
if isinstance(request.match_info.route, SystemRoute):
paths_to_check = []
Expand Down Expand Up @@ -113,7 +115,6 @@ async def impl(request: Request, handler: _Handler) -> StreamResponse:

def _fix_request_current_app(app: 'Application') -> _Middleware:

@middleware
async def impl(request: Request, handler: _Handler) -> StreamResponse:
with request.match_info.set_current_app(app):
return await handler(request)
Expand Down
1 change: 0 additions & 1 deletion docs/faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,6 @@ response::
# don't do this!
cached = web.Response(status=200, text='Hi, I am cached!')

@web.middleware
async def middleware(request, handler):
# ignoring response for the sake of this example
_res = handler(request)
Expand Down
19 changes: 12 additions & 7 deletions docs/web_advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -483,12 +483,18 @@ response. For example, here's a simple *middleware* which appends

from aiohttp.web import middleware

@middleware
async def middleware(request, handler):
resp = await handler(request)
resp.text = resp.text + ' wink'
return resp

.. warning::

As of version ``4.0.0`` "new-style" middleware is default and the
``@middleware`` decorator is not required (and is deprecated), you can
simply remove the decorator. "Old-style" middleware (a coroutine which
returned a coroutine) is no longer supported.

.. note::

The example won't work with streamed responses or websockets
Expand Down Expand Up @@ -531,14 +537,12 @@ The following code demonstrates middlewares execution order::
print('Handler function called')
return web.Response(text="Hello")

@web.middleware
async def middleware1(request, handler):
print('Middleware 1 called')
response = await handler(request)
print('Middleware 1 finished')
return response

@web.middleware
async def middleware2(request, handler):
print('Middleware 2 called')
response = await handler(request)
Expand Down Expand Up @@ -567,7 +571,6 @@ a JSON REST service::

from aiohttp import web

@web.middleware
async def error_middleware(request, handler):
try:
response = await handler(request)
Expand All @@ -586,17 +589,19 @@ a JSON REST service::
Middleware Factory
^^^^^^^^^^^^^^^^^^

A *middleware factory* is a function that creates a middleware with passed arguments. For example, here's a trivial *middleware factory*::
A *middleware factory* is a function that creates a middleware with passed
arguments. For example, here's a trivial *middleware factory*::

def middleware_factory(text):
@middleware
async def sample_middleware(request, handler):
resp = await handler(request)
resp.text = resp.text + text
return resp
return sample_middleware

Remember that contrary to regular middlewares you need the result of a middleware factory not the function itself. So when passing a middleware factory to an app you actually need to call it::
Note that in contrast to regular middlewares, a middleware factory should
return the function, not the value. So when passing a middleware factory
to the app you actually need to call it::

app = web.Application(middlewares=[middleware_factory(' wink')])

Expand Down
1 change: 0 additions & 1 deletion examples/web_rewrite_headers_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ async def handler(request):
return web.Response(text="Everything is fine")


@web.middleware
async def middleware(request, handler):
try:
response = await handler(request)
Expand Down
1 change: 0 additions & 1 deletion tests/test_web_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,6 @@ def test_app_run_middlewares() -> None:
root.freeze()
assert root._run_middlewares is False

@web.middleware
async def middleware(request, handler):
return await handler(request)

Expand Down
37 changes: 20 additions & 17 deletions tests/test_web_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -1211,35 +1211,39 @@ async def handler(request):
assert resp.status == 500


async def test_subapp_middlewares(aiohttp_client) -> None:
async def test_old_style_subapp_middlewares(aiohttp_client) -> None:
order = []

async def handler(request):
return web.Response(text='OK')

async def middleware_factory(app, handler):

async def middleware(request):
order.append((1, app))
with pytest.warns(
DeprecationWarning, match='Middleware decorator is deprecated'
):
@web.middleware
async def middleware(request, handler):
order.append((1, request.app['name']))
resp = await handler(request)
assert 200 == resp.status
order.append((2, app))
order.append((2, request.app['name']))
return resp
return middleware

app = web.Application(middlewares=[middleware_factory])
subapp1 = web.Application(middlewares=[middleware_factory])
subapp2 = web.Application(middlewares=[middleware_factory])
app = web.Application(middlewares=[middleware])
subapp1 = web.Application(middlewares=[middleware])
subapp2 = web.Application(middlewares=[middleware])
app['name'] = 'app'
subapp1['name'] = 'subapp1'
subapp2['name'] = 'subapp2'

subapp2.router.add_get('/to', handler)
with pytest.warns(DeprecationWarning):
subapp1.add_subapp('/b/', subapp2)
app.add_subapp('/a/', subapp1)
client = await aiohttp_client(app)
subapp1.add_subapp('/b/', subapp2)
app.add_subapp('/a/', subapp1)
client = await aiohttp_client(app)

resp = await client.get('/a/b/to')
assert resp.status == 200
assert [(1, app), (1, subapp1), (1, subapp2),
(2, subapp2), (2, subapp1), (2, app)] == order
assert [(1, 'app'), (1, 'subapp1'), (1, 'subapp2'),
(2, 'subapp2'), (2, 'subapp1'), (2, 'app')] == order


async def test_subapp_on_response_prepare(aiohttp_client) -> None:
Expand Down Expand Up @@ -1348,7 +1352,6 @@ async def test_subapp_middleware_context(aiohttp_client,
values = []

def show_app_context(appname):
@web.middleware
async def middleware(request, handler):
values.append('{}: {}'.format(
appname, request.app['my_value']))
Expand Down
Loading

0 comments on commit c8cbbcc

Please sign in to comment.