From 636b07512edea1f1d9957f2c7da604d1116cded6 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Tue, 5 Jan 2016 22:48:37 +0200 Subject: [PATCH 01/18] Add run_app() --- aiohttp/web.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/aiohttp/web.py b/aiohttp/web.py index 61ea36ea43c..e2bb915f908 100644 --- a/aiohttp/web.py +++ b/aiohttp/web.py @@ -270,3 +270,31 @@ def __call__(self): def __repr__(self): return "" + + +def run_app(app, host='0.0.0.0', port=None): + """Run an app locally""" + if port is None: + # allow to use 8443 if future ssl=True will be added + port = 8080 + + loop = asyncio.get_event_loop() + handler = app.make_handler() + f = loop.create_server(handler, host, port) + srv = loop.run_until_complete(f) + + prompt = '127.0.0.1' if host == '0.0.0.0' else host + print(" * Running on http://{prompt}:{port}/ \n" + "(Press CTRL+C to quit)".format( + host=prompt, port=port)) + + try: + loop.run_forever() + except KeyboardInterrupt: + pass + finally: + srv.close() + loop.run_until_complete(srv.wait_closed()) + loop.run_until_complete(handler.finish_connections(1.0)) + loop.run_until_complete(app.finish()) + loop.close() From 0b8e831fe5f0ff372826d4fc993195285f2aefde Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Wed, 6 Jan 2016 18:09:41 +0200 Subject: [PATCH 02/18] Improve run_app code --- aiohttp/web.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/aiohttp/web.py b/aiohttp/web.py index e2bb915f908..23d2c412714 100644 --- a/aiohttp/web.py +++ b/aiohttp/web.py @@ -272,16 +272,17 @@ def __repr__(self): return "" -def run_app(app, host='0.0.0.0', port=None): +def run_app(app, *, host='0.0.0.0', port=None, loop=None): """Run an app locally""" if port is None: # allow to use 8443 if future ssl=True will be added port = 8080 - loop = asyncio.get_event_loop() + if loop is None: + loop = asyncio.get_event_loop() + handler = app.make_handler() - f = loop.create_server(handler, host, port) - srv = loop.run_until_complete(f) + srv = loop.run_until_complete(loop.create_server(handler, host, port)) prompt = '127.0.0.1' if host == '0.0.0.0' else host print(" * Running on http://{prompt}:{port}/ \n" @@ -295,6 +296,7 @@ def run_app(app, host='0.0.0.0', port=None): finally: srv.close() loop.run_until_complete(srv.wait_closed()) + loop.run_until_complete(app.shutdown()) loop.run_until_complete(handler.finish_connections(1.0)) loop.run_until_complete(app.finish()) loop.close() From 918d8200304985a2b0ede076520def5a8c67ba37 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Wed, 6 Jan 2016 18:33:11 +0200 Subject: [PATCH 03/18] Increase max timeout for connection shutdown --- aiohttp/web.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiohttp/web.py b/aiohttp/web.py index 23d2c412714..e32068472e9 100644 --- a/aiohttp/web.py +++ b/aiohttp/web.py @@ -297,6 +297,6 @@ def run_app(app, *, host='0.0.0.0', port=None, loop=None): srv.close() loop.run_until_complete(srv.wait_closed()) loop.run_until_complete(app.shutdown()) - loop.run_until_complete(handler.finish_connections(1.0)) + loop.run_until_complete(handler.finish_connections(60.0)) loop.run_until_complete(app.finish()) loop.close() From 7670eee61cdfa98f4203a4fa5fa16b054f8e12a6 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Wed, 6 Jan 2016 18:34:39 +0200 Subject: [PATCH 04/18] Add shutdown_timeout parameter --- aiohttp/web.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/aiohttp/web.py b/aiohttp/web.py index e32068472e9..dc176b51c42 100644 --- a/aiohttp/web.py +++ b/aiohttp/web.py @@ -272,7 +272,8 @@ def __repr__(self): return "" -def run_app(app, *, host='0.0.0.0', port=None, loop=None): +def run_app(app, *, host='0.0.0.0', port=None, loop=None, + shutdown_timeout=60.0): """Run an app locally""" if port is None: # allow to use 8443 if future ssl=True will be added @@ -297,6 +298,6 @@ def run_app(app, *, host='0.0.0.0', port=None, loop=None): srv.close() loop.run_until_complete(srv.wait_closed()) loop.run_until_complete(app.shutdown()) - loop.run_until_complete(handler.finish_connections(60.0)) + loop.run_until_complete(handler.finish_connections(shutdown_timeout)) loop.run_until_complete(app.finish()) loop.close() From 2e4e4bb6e0857fe6fbb267a197e020b1be4a402e Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Wed, 6 Jan 2016 21:39:06 +0200 Subject: [PATCH 05/18] Support ssl scheme --- aiohttp/web.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/aiohttp/web.py b/aiohttp/web.py index dc176b51c42..16c3800144c 100644 --- a/aiohttp/web.py +++ b/aiohttp/web.py @@ -273,22 +273,26 @@ def __repr__(self): def run_app(app, *, host='0.0.0.0', port=None, loop=None, - shutdown_timeout=60.0): + shutdown_timeout=60.0, ssl_context=None): """Run an app locally""" if port is None: - # allow to use 8443 if future ssl=True will be added - port = 8080 + if not ssl_context: + port = 8080 + else: + port = 8443 if loop is None: loop = asyncio.get_event_loop() handler = app.make_handler() - srv = loop.run_until_complete(loop.create_server(handler, host, port)) + srv = loop.run_until_complete(loop.create_server(handler, host, port, + ssl=ssl_context)) + scheme = 'https' if ssl_context else 'http' prompt = '127.0.0.1' if host == '0.0.0.0' else host - print(" * Running on http://{prompt}:{port}/ \n" + print("======== Running on {scheme}://{prompt}:{port}/ ========\n" "(Press CTRL+C to quit)".format( - host=prompt, port=port)) + scheme=scheme, host=prompt, port=port)) try: loop.run_forever() From 6f35b4d333af6f0f08c1ed4396e90bbade69b87a Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Tue, 12 Jan 2016 19:18:11 +0200 Subject: [PATCH 06/18] Add tests for run_app --- aiohttp/web.py | 2 +- tests/test_run_app.py | 91 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 tests/test_run_app.py diff --git a/aiohttp/web.py b/aiohttp/web.py index 122a1a1e659..824793a705b 100644 --- a/aiohttp/web.py +++ b/aiohttp/web.py @@ -309,7 +309,7 @@ def run_app(app, *, host='0.0.0.0', port=None, loop=None, prompt = '127.0.0.1' if host == '0.0.0.0' else host print("======== Running on {scheme}://{prompt}:{port}/ ========\n" "(Press CTRL+C to quit)".format( - scheme=scheme, host=prompt, port=port)) + scheme=scheme, prompt=prompt, port=port)) try: loop.run_forever() diff --git a/tests/test_run_app.py b/tests/test_run_app.py new file mode 100644 index 00000000000..323cc017d7c --- /dev/null +++ b/tests/test_run_app.py @@ -0,0 +1,91 @@ +import asyncio +import pytest +import ssl + +from unittest import mock +from aiohttp import web + + +def test_run_app_http(loop): + loop = mock.Mock(spec=asyncio.AbstractEventLoop, wrap=loop) + loop.run_forever.return_value = None # to disable wrapping + loop.run_forever.side_effect = KeyboardInterrupt() + + app = mock.Mock(wrap=web.Application(loop=loop)) + + web.run_app(app, loop=loop) + + app.make_handler.assert_called_with() + + loop.close.assert_called_with() + app.finish.assert_called_with() + app.shutdown.assert_called_with() + + loop.create_server.assert_called_with(mock.ANY, '0.0.0.0', 8080, ssl=None) + + +def test_run_app_https(loop): + loop = mock.Mock(spec=asyncio.AbstractEventLoop, wrap=loop) + loop.run_forever.return_value = None # to disable wrapping + loop.run_forever.side_effect = KeyboardInterrupt() + + app = mock.Mock(wrap=web.Application(loop=loop)) + + ssl_context = ssl.create_default_context() + + web.run_app(app, ssl_context=ssl_context, loop=loop) + + app.make_handler.assert_called_with() + + loop.close.assert_called_with() + app.finish.assert_called_with() + app.shutdown.assert_called_with() + + loop.create_server.assert_called_with(mock.ANY, '0.0.0.0', 8443, + ssl=ssl_context) + + +def test_run_app_nondefault_host_port(loop, unused_port): + port = unused_port() + host = 'localhost' + + loop = mock.Mock(spec=asyncio.AbstractEventLoop, wrap=loop) + loop.run_forever.return_value = None # to disable wrapping + loop.run_forever.side_effect = KeyboardInterrupt() + + app = mock.Mock(wrap=web.Application(loop=loop)) + + web.run_app(app, host=host, port=port, loop=loop) + + loop.create_server.assert_called_with(mock.ANY, host, port, ssl=None) + + +def test_run_app_default_eventloop(loop, unused_port): + loop = mock.Mock(spec=asyncio.AbstractEventLoop, wrap=loop) + loop.run_forever.return_value = None # to disable wrapping + loop.run_forever.side_effect = KeyboardInterrupt() + + asyncio.set_event_loop(loop) + + app = mock.Mock(wrap=web.Application()) + + web.run_app(app) + + loop.create_server.assert_called_with(mock.ANY, '0.0.0.0', 8080, ssl=None) + + +def test_run_app_exit_with_exception(loop): + loop = mock.Mock(spec=asyncio.AbstractEventLoop, wrap=loop) + loop.run_forever.return_value = None # to disable wrapping + loop.run_forever.side_effect = exc = RuntimeError() + + app = mock.Mock(wrap=web.Application(loop=loop)) + + with pytest.raises(RuntimeError) as ctx: + web.run_app(app, loop=loop) + + assert ctx.value is exc + + assert not loop.close.called + app.finish.assert_called_with() + app.shutdown.assert_called_with() From c9b74c87f506c18af2fb65707b0e75d744d82d5b Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Tue, 12 Jan 2016 19:34:49 +0200 Subject: [PATCH 07/18] Add reference doc for run_app --- docs/web_reference.rst | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/docs/web_reference.rst b/docs/web_reference.rst index 02fbd5a2790..a9326983bb4 100644 --- a/docs/web_reference.rst +++ b/docs/web_reference.rst @@ -1511,6 +1511,43 @@ Utilities .. seealso:: :ref:`aiohttp-web-file-upload` +.. function:: run_app(app, *, host='0.0.0.0', port=None, loop=None, \ + shutdown_timeout=60.0, ssl_context=None) + + An utility function for running an application, serving it until + keyboard iterrupt and performing graceful shutdown. + + Suitable as handy tool for scaffolding aiohttp based projects. + Perhaps production config will use more sophisticated runner but it + good enough at least at very beginning stage. + + :param app: :class:`Application` instance to run + + :param str host: host for HTTP server, ``'0.0.0.0'`` by default + + :param int port: port for HTTP server. By default is ``8080`` for + plain text HTTP and ``8443`` for HTTP via SSL + (when *ssl_context* parameter is specified). + + :param int shutdown_timeout: a delay to wait for graceful server + shutdown before diconnecting all + opened sockets hard way. + + A system with + :ref:`aiohttp-web-graceful-shutdown` + implemented never waits for this + timeout. + + :param ssl_context: :class:`ssl.SSLContext` for HTTPS server, + ``None`` for HTTP connection. + + :param loop: :ref:`event loop` used + for processing HTTP requests. + + If param is ``None`` :func:`asyncio.get_event_loop` + used for getting default event loop. + + Constants --------- From 6b5ed6200e434f9ca29534681db2c0dfb2e4db6b Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Tue, 12 Jan 2016 20:31:04 +0200 Subject: [PATCH 08/18] Use delayed call to stop loop --- tests/test_run_app.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/tests/test_run_app.py b/tests/test_run_app.py index 323cc017d7c..7fd6f259805 100644 --- a/tests/test_run_app.py +++ b/tests/test_run_app.py @@ -8,11 +8,10 @@ def test_run_app_http(loop): loop = mock.Mock(spec=asyncio.AbstractEventLoop, wrap=loop) - loop.run_forever.return_value = None # to disable wrapping - loop.run_forever.side_effect = KeyboardInterrupt() - + loop.call_later(0.01, loop.stop) app = mock.Mock(wrap=web.Application(loop=loop)) + web.run_app(app, loop=loop) app.make_handler.assert_called_with() @@ -26,8 +25,7 @@ def test_run_app_http(loop): def test_run_app_https(loop): loop = mock.Mock(spec=asyncio.AbstractEventLoop, wrap=loop) - loop.run_forever.return_value = None # to disable wrapping - loop.run_forever.side_effect = KeyboardInterrupt() + loop.call_later(0.01, loop.stop) app = mock.Mock(wrap=web.Application(loop=loop)) @@ -50,8 +48,7 @@ def test_run_app_nondefault_host_port(loop, unused_port): host = 'localhost' loop = mock.Mock(spec=asyncio.AbstractEventLoop, wrap=loop) - loop.run_forever.return_value = None # to disable wrapping - loop.run_forever.side_effect = KeyboardInterrupt() + loop.call_later(0.01, loop.stop) app = mock.Mock(wrap=web.Application(loop=loop)) @@ -62,8 +59,7 @@ def test_run_app_nondefault_host_port(loop, unused_port): def test_run_app_default_eventloop(loop, unused_port): loop = mock.Mock(spec=asyncio.AbstractEventLoop, wrap=loop) - loop.run_forever.return_value = None # to disable wrapping - loop.run_forever.side_effect = KeyboardInterrupt() + loop.call_later(0.01, loop.stop) asyncio.set_event_loop(loop) @@ -76,6 +72,7 @@ def test_run_app_default_eventloop(loop, unused_port): def test_run_app_exit_with_exception(loop): loop = mock.Mock(spec=asyncio.AbstractEventLoop, wrap=loop) + loop.run_forever.return_value = None # to disable wrapping loop.run_forever.side_effect = exc = RuntimeError() From 51267fb3995b9734915c972d750dff003caeef80 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Tue, 12 Jan 2016 21:01:57 +0200 Subject: [PATCH 09/18] Finish docs update for run_app --- docs/web.rst | 41 ++++++++++++++++++++++------------------- docs/web_reference.rst | 12 +++++++----- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/docs/web.rst b/docs/web.rst index 8343968ec92..0b4fc22b35e 100644 --- a/docs/web.rst +++ b/docs/web.rst @@ -32,27 +32,16 @@ particular *HTTP method* and *path*:: app = web.Application() app.router.add_route('GET', '/', hello) -After that, create a server and run the *asyncio loop* as usual:: +After that, run the application by :func:`run_app` call:: - loop = asyncio.get_event_loop() - handler = app.make_handler() - f = loop.create_server(handler, '0.0.0.0', 8080) - srv = loop.run_until_complete(f) - print('serving on', srv.sockets[0].getsockname()) - try: - loop.run_forever() - except KeyboardInterrupt: - pass - finally: - srv.close() - loop.run_until_complete(srv.wait_closed()) - loop.run_until_complete(app.on_shutdown.send()) - loop.run_until_complete(handler.finish_connections(1.0)) - loop.run_until_complete(app.finish()) - loop.close() + run_app(app) That's it. Now, head over to ``http://localhost:8080/`` to see the results. +.. seealso:: :ref:`aiohttp-web-graceful-shutdown` section + explains what :func:`run_app` does and how implement + complex server initialization/finalization from scratch. + .. _aiohttp-web-handler: @@ -834,9 +823,22 @@ Signal handler may looks like: app.on_shutdown.append(on_shutdown) +Proper finalization procedure has three steps: -Server finalizer should raise shutdown signal by -:meth:`Application.shutdown` call:: + 1. Stop accepting new client connections by + :meth:`asyncio.Server.close` and + :meth:`asyncio.Server.wait_closed` calls. + + 2. Fire :meth:`Application.shutdown` event. + + 3. Close accepted connections from clients by + :meth:`RequestHandlerFactory.finish_connections` call with + reasonable small delay. + + 4. Call registered application finalizers by :meth:`Application.finish`. + +The following code snippet performs proper application start, run and +finalizing. It's pretty close to :func:`run_app` utility function:: loop = asyncio.get_event_loop() handler = app.make_handler() @@ -857,6 +859,7 @@ Server finalizer should raise shutdown signal by + CORS support ------------ diff --git a/docs/web_reference.rst b/docs/web_reference.rst index a9326983bb4..e22b23c8a4e 100644 --- a/docs/web_reference.rst +++ b/docs/web_reference.rst @@ -1515,7 +1515,8 @@ Utilities shutdown_timeout=60.0, ssl_context=None) An utility function for running an application, serving it until - keyboard iterrupt and performing graceful shutdown. + keyboard interrupt and performing a + :ref:`aiohttp-web-graceful-shutdown`. Suitable as handy tool for scaffolding aiohttp based projects. Perhaps production config will use more sophisticated runner but it @@ -1530,13 +1531,14 @@ Utilities (when *ssl_context* parameter is specified). :param int shutdown_timeout: a delay to wait for graceful server - shutdown before diconnecting all - opened sockets hard way. + shutdown before disconnecting all + open client sockets hard way. - A system with + A system with properly :ref:`aiohttp-web-graceful-shutdown` implemented never waits for this - timeout. + timeout but closes a server in a few + milliseconds. :param ssl_context: :class:`ssl.SSLContext` for HTTPS server, ``None`` for HTTP connection. From a2b39a0e615a9de21325299dc0ac0d0e326fcc18 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Tue, 12 Jan 2016 21:02:49 +0200 Subject: [PATCH 10/18] Fix flake error --- tests/test_run_app.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_run_app.py b/tests/test_run_app.py index 7fd6f259805..b816512aff9 100644 --- a/tests/test_run_app.py +++ b/tests/test_run_app.py @@ -11,7 +11,6 @@ def test_run_app_http(loop): loop.call_later(0.01, loop.stop) app = mock.Mock(wrap=web.Application(loop=loop)) - web.run_app(app, loop=loop) app.make_handler.assert_called_with() From 1b7f94cf354ca0f9f846d6c76b114a97c1f14db5 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Tue, 12 Jan 2016 21:27:10 +0200 Subject: [PATCH 11/18] Try to fix a test --- tests/test_run_app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_run_app.py b/tests/test_run_app.py index b816512aff9..40ea1550922 100644 --- a/tests/test_run_app.py +++ b/tests/test_run_app.py @@ -57,11 +57,11 @@ def test_run_app_nondefault_host_port(loop, unused_port): def test_run_app_default_eventloop(loop, unused_port): + asyncio.set_event_loop(loop) + loop = mock.Mock(spec=asyncio.AbstractEventLoop, wrap=loop) loop.call_later(0.01, loop.stop) - asyncio.set_event_loop(loop) - app = mock.Mock(wrap=web.Application()) web.run_app(app) From 0f17dca03893909b51acdfb8bf98b37c505772c6 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Tue, 12 Jan 2016 21:39:21 +0200 Subject: [PATCH 12/18] Try to fix a test again --- tests/test_run_app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_run_app.py b/tests/test_run_app.py index 40ea1550922..091f7c90a00 100644 --- a/tests/test_run_app.py +++ b/tests/test_run_app.py @@ -57,9 +57,9 @@ def test_run_app_nondefault_host_port(loop, unused_port): def test_run_app_default_eventloop(loop, unused_port): - asyncio.set_event_loop(loop) - loop = mock.Mock(spec=asyncio.AbstractEventLoop, wrap=loop) + + asyncio.set_event_loop(loop) loop.call_later(0.01, loop.stop) app = mock.Mock(wrap=web.Application()) From 635599c28c0e5f3e6655ced4462cd17275210b6d Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Tue, 12 Jan 2016 21:44:14 +0200 Subject: [PATCH 13/18] Skip failing test on Python 3.4 --- tests/test_run_app.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_run_app.py b/tests/test_run_app.py index 091f7c90a00..7be3cd7ac8b 100644 --- a/tests/test_run_app.py +++ b/tests/test_run_app.py @@ -1,6 +1,7 @@ import asyncio import pytest import ssl +import sys from unittest import mock from aiohttp import web @@ -56,7 +57,10 @@ def test_run_app_nondefault_host_port(loop, unused_port): loop.create_server.assert_called_with(mock.ANY, host, port, ssl=None) +@pytest.mark.skipIf(sys.version_info < (3, 5)) def test_run_app_default_eventloop(loop, unused_port): + # Python 3.4 fails on running in debug mode if loop is wrapped by mock + loop = mock.Mock(spec=asyncio.AbstractEventLoop, wrap=loop) asyncio.set_event_loop(loop) From 727393b34da8751b473b5f3afca6b00ec006bb95 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Tue, 12 Jan 2016 21:53:46 +0200 Subject: [PATCH 14/18] Fighting with failed test --- tests/conftest.py | 2 +- tests/test_run_app.py | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 9d57383dcd7..7e2e01bad01 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -164,7 +164,7 @@ def loop(request): yield loop if not loop._closed: - loop.stop() + loop.call_soon(loop.stop) loop.run_forever() loop.close() gc.collect() diff --git a/tests/test_run_app.py b/tests/test_run_app.py index 7be3cd7ac8b..ac567316cba 100644 --- a/tests/test_run_app.py +++ b/tests/test_run_app.py @@ -57,10 +57,7 @@ def test_run_app_nondefault_host_port(loop, unused_port): loop.create_server.assert_called_with(mock.ANY, host, port, ssl=None) -@pytest.mark.skipIf(sys.version_info < (3, 5)) def test_run_app_default_eventloop(loop, unused_port): - # Python 3.4 fails on running in debug mode if loop is wrapped by mock - loop = mock.Mock(spec=asyncio.AbstractEventLoop, wrap=loop) asyncio.set_event_loop(loop) From 69d30c0b4b0943a033f921f996ac501367403aa8 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Tue, 12 Jan 2016 22:03:06 +0200 Subject: [PATCH 15/18] Drop unused sys import --- tests/test_run_app.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_run_app.py b/tests/test_run_app.py index ac567316cba..091f7c90a00 100644 --- a/tests/test_run_app.py +++ b/tests/test_run_app.py @@ -1,7 +1,6 @@ import asyncio import pytest import ssl -import sys from unittest import mock from aiohttp import web From 8460828984ecd55b11b8a841dbb07c9c797abb79 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Tue, 12 Jan 2016 22:10:49 +0200 Subject: [PATCH 16/18] Work on failing test --- tests/test_run_app.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/tests/test_run_app.py b/tests/test_run_app.py index 091f7c90a00..3363430eb15 100644 --- a/tests/test_run_app.py +++ b/tests/test_run_app.py @@ -56,17 +56,12 @@ def test_run_app_nondefault_host_port(loop, unused_port): loop.create_server.assert_called_with(mock.ANY, host, port, ssl=None) -def test_run_app_default_eventloop(loop, unused_port): - loop = mock.Mock(spec=asyncio.AbstractEventLoop, wrap=loop) - +def test_run_app_default_eventloop(loop): asyncio.set_event_loop(loop) loop.call_later(0.01, loop.stop) - app = mock.Mock(wrap=web.Application()) - - web.run_app(app) - - loop.create_server.assert_called_with(mock.ANY, '0.0.0.0', 8080, ssl=None) + web.run_app(web.Application()) + # don't analise a return value, jut make sure the call was successful def test_run_app_exit_with_exception(loop): From f418dca8f03aab6b18d469490f33b057da471d7a Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Tue, 12 Jan 2016 22:15:28 +0200 Subject: [PATCH 17/18] Final commit with explanation why it was failed on Python 3.4 --- tests/test_run_app.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_run_app.py b/tests/test_run_app.py index 3363430eb15..ec8660c6846 100644 --- a/tests/test_run_app.py +++ b/tests/test_run_app.py @@ -57,11 +57,14 @@ def test_run_app_nondefault_host_port(loop, unused_port): def test_run_app_default_eventloop(loop): + # don't perform any assert checks just make sure the run_app was successful + # mocking a default loop produces a failure on Python 3.4 + # with PYTHONASYNCIODEBUG enabled + asyncio.set_event_loop(loop) loop.call_later(0.01, loop.stop) web.run_app(web.Application()) - # don't analise a return value, jut make sure the call was successful def test_run_app_exit_with_exception(loop): From 0733de2555c2659dbc0555237a75829f16337a7f Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Wed, 13 Jan 2016 13:02:11 +0200 Subject: [PATCH 18/18] Use Application.loop in run_app --- aiohttp/web.py | 7 +++--- docs/web_reference.rst | 8 ++----- tests/test_run_app.py | 51 +++++------------------------------------- 3 files changed, 11 insertions(+), 55 deletions(-) diff --git a/aiohttp/web.py b/aiohttp/web.py index 824793a705b..fa4d67e48b3 100644 --- a/aiohttp/web.py +++ b/aiohttp/web.py @@ -289,7 +289,7 @@ def __repr__(self): return "" -def run_app(app, *, host='0.0.0.0', port=None, loop=None, +def run_app(app, *, host='0.0.0.0', port=None, shutdown_timeout=60.0, ssl_context=None): """Run an app locally""" if port is None: @@ -298,8 +298,7 @@ def run_app(app, *, host='0.0.0.0', port=None, loop=None, else: port = 8443 - if loop is None: - loop = asyncio.get_event_loop() + loop = app.loop handler = app.make_handler() srv = loop.run_until_complete(loop.create_server(handler, host, port, @@ -313,7 +312,7 @@ def run_app(app, *, host='0.0.0.0', port=None, loop=None, try: loop.run_forever() - except KeyboardInterrupt: + except KeyboardInterrupt: # pragma: no branch pass finally: srv.close() diff --git a/docs/web_reference.rst b/docs/web_reference.rst index e22b23c8a4e..bb123c95891 100644 --- a/docs/web_reference.rst +++ b/docs/web_reference.rst @@ -1522,6 +1522,8 @@ Utilities Perhaps production config will use more sophisticated runner but it good enough at least at very beginning stage. + The function uses *app.loop* as event loop to run. + :param app: :class:`Application` instance to run :param str host: host for HTTP server, ``'0.0.0.0'`` by default @@ -1543,12 +1545,6 @@ Utilities :param ssl_context: :class:`ssl.SSLContext` for HTTPS server, ``None`` for HTTP connection. - :param loop: :ref:`event loop` used - for processing HTTP requests. - - If param is ``None`` :func:`asyncio.get_event_loop` - used for getting default event loop. - Constants --------- diff --git a/tests/test_run_app.py b/tests/test_run_app.py index ec8660c6846..d6b4bfb8a1c 100644 --- a/tests/test_run_app.py +++ b/tests/test_run_app.py @@ -1,5 +1,4 @@ import asyncio -import pytest import ssl from unittest import mock @@ -9,16 +8,12 @@ def test_run_app_http(loop): loop = mock.Mock(spec=asyncio.AbstractEventLoop, wrap=loop) loop.call_later(0.01, loop.stop) - app = mock.Mock(wrap=web.Application(loop=loop)) - web.run_app(app, loop=loop) + app = web.Application(loop=loop) - app.make_handler.assert_called_with() + web.run_app(app) loop.close.assert_called_with() - app.finish.assert_called_with() - app.shutdown.assert_called_with() - loop.create_server.assert_called_with(mock.ANY, '0.0.0.0', 8080, ssl=None) @@ -26,18 +21,13 @@ def test_run_app_https(loop): loop = mock.Mock(spec=asyncio.AbstractEventLoop, wrap=loop) loop.call_later(0.01, loop.stop) - app = mock.Mock(wrap=web.Application(loop=loop)) + app = web.Application(loop=loop) ssl_context = ssl.create_default_context() - web.run_app(app, ssl_context=ssl_context, loop=loop) - - app.make_handler.assert_called_with() + web.run_app(app, ssl_context=ssl_context) loop.close.assert_called_with() - app.finish.assert_called_with() - app.shutdown.assert_called_with() - loop.create_server.assert_called_with(mock.ANY, '0.0.0.0', 8443, ssl=ssl_context) @@ -49,37 +39,8 @@ def test_run_app_nondefault_host_port(loop, unused_port): loop = mock.Mock(spec=asyncio.AbstractEventLoop, wrap=loop) loop.call_later(0.01, loop.stop) - app = mock.Mock(wrap=web.Application(loop=loop)) + app = web.Application(loop=loop) - web.run_app(app, host=host, port=port, loop=loop) + web.run_app(app, host=host, port=port) loop.create_server.assert_called_with(mock.ANY, host, port, ssl=None) - - -def test_run_app_default_eventloop(loop): - # don't perform any assert checks just make sure the run_app was successful - # mocking a default loop produces a failure on Python 3.4 - # with PYTHONASYNCIODEBUG enabled - - asyncio.set_event_loop(loop) - loop.call_later(0.01, loop.stop) - - web.run_app(web.Application()) - - -def test_run_app_exit_with_exception(loop): - loop = mock.Mock(spec=asyncio.AbstractEventLoop, wrap=loop) - - loop.run_forever.return_value = None # to disable wrapping - loop.run_forever.side_effect = exc = RuntimeError() - - app = mock.Mock(wrap=web.Application(loop=loop)) - - with pytest.raises(RuntimeError) as ctx: - web.run_app(app, loop=loop) - - assert ctx.value is exc - - assert not loop.close.called - app.finish.assert_called_with() - app.shutdown.assert_called_with()