From c8ccce084c6f21eb6efa5b3c84989513d4b47cd1 Mon Sep 17 00:00:00 2001 From: Jan Buchar Date: Tue, 20 Oct 2020 10:08:14 +0200 Subject: [PATCH 01/14] Add keepalive_timeout parameter to web.run_app --- CHANGES/5094.feature | 1 + aiohttp/web.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 CHANGES/5094.feature diff --git a/CHANGES/5094.feature b/CHANGES/5094.feature new file mode 100644 index 00000000000..726408ffc25 --- /dev/null +++ b/CHANGES/5094.feature @@ -0,0 +1 @@ +Add keepalive_timeout parameter to web.run_app diff --git a/aiohttp/web.py b/aiohttp/web.py index a5de6fcee54..e655b3f1085 100644 --- a/aiohttp/web.py +++ b/aiohttp/web.py @@ -292,6 +292,7 @@ async def _run_app(app: Union[Application, Awaitable[Application]], *, path: Optional[str]=None, sock: Optional[socket.socket]=None, shutdown_timeout: float=60.0, + keepalive_timeout: float=75.0, ssl_context: Optional[SSLContext]=None, print: Optional[Callable[..., None]]=print, backlog: int=128, @@ -310,7 +311,8 @@ async def _run_app(app: Union[Application, Awaitable[Application]], *, runner = AppRunner(app, handle_signals=handle_signals, access_log_class=access_log_class, access_log_format=access_log_format, - access_log=access_log) + access_log=access_log, + keepalive_timeout=keepalive_timeout) await runner.setup() @@ -407,6 +409,7 @@ def run_app(app: Union[Application, Awaitable[Application]], *, path: Optional[str]=None, sock: Optional[socket.socket]=None, shutdown_timeout: float=60.0, + keepalive_timeout: float=75.0, ssl_context: Optional[SSLContext]=None, print: Optional[Callable[..., None]]=print, backlog: int=128, @@ -435,6 +438,7 @@ def run_app(app: Union[Application, Awaitable[Application]], *, path=path, sock=sock, shutdown_timeout=shutdown_timeout, + keepalive_timeout=keepalive_timeout, ssl_context=ssl_context, print=print, backlog=backlog, From fcc2253b9052f767f5311a3100d2e2d3f232e7c3 Mon Sep 17 00:00:00 2001 From: Jan Buchar Date: Tue, 20 Oct 2020 11:10:26 +0200 Subject: [PATCH 02/14] Add test for setting keepalive_timeout --- tests/test_run_app.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_run_app.py b/tests/test_run_app.py index efc79cfc2c5..ce6e3449912 100644 --- a/tests/test_run_app.py +++ b/tests/test_run_app.py @@ -16,6 +16,7 @@ from aiohttp import web from aiohttp.helpers import PY_37 from aiohttp.test_utils import make_mocked_coro +from aiohttp.web_runner import BaseRunner # Test for features of OS' socket support _has_unix_domain_socks = hasattr(socket, 'AF_UNIX') @@ -685,6 +686,17 @@ async def on_startup(app): exc_handler.assert_called_with(patched_loop, msg) +def test_run_app_keepalive_timeout(patched_loop): + new_timeout = 1234 + mock_runner_init = mock.Mock(BaseRunner.__init__) + + with mock.patch.object(BaseRunner, "__init__", mock_runner_init): + app = web.Application() + web.run_app(app, keepalive_timeout=new_timeout) + + assert mock_runner_init.call_args.kwargs["keepalive_timeout"] == new_timeout + + @pytest.mark.skipif(not PY_37, reason="contextvars support is required") def test_run_app_context_vars(patched_loop): From 827375faaeb6b9400e53cddb0594c4edc9a586a2 Mon Sep 17 00:00:00 2001 From: Jan Buchar Date: Tue, 20 Oct 2020 11:29:41 +0200 Subject: [PATCH 03/14] Code format --- tests/test_run_app.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_run_app.py b/tests/test_run_app.py index ce6e3449912..d288954ec10 100644 --- a/tests/test_run_app.py +++ b/tests/test_run_app.py @@ -694,7 +694,8 @@ def test_run_app_keepalive_timeout(patched_loop): app = web.Application() web.run_app(app, keepalive_timeout=new_timeout) - assert mock_runner_init.call_args.kwargs["keepalive_timeout"] == new_timeout + assert (mock_runner_init.call_args.kwargs["keepalive_timeout"] == + new_timeout) @pytest.mark.skipif(not PY_37, From 2a99b5c89768682a42a7cf60ee8ce2196e38f005 Mon Sep 17 00:00:00 2001 From: Jan Buchar Date: Tue, 20 Oct 2020 13:08:34 +0200 Subject: [PATCH 04/14] A more complex way to spy on BaseRunner.__init__ --- tests/test_run_app.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/test_run_app.py b/tests/test_run_app.py index d288954ec10..4334f223ffa 100644 --- a/tests/test_run_app.py +++ b/tests/test_run_app.py @@ -688,13 +688,19 @@ async def on_startup(app): def test_run_app_keepalive_timeout(patched_loop): new_timeout = 1234 - mock_runner_init = mock.Mock(BaseRunner.__init__) + base_runner_init_mock = mock.MagicMock() + base_runner_init_orig = BaseRunner.__init__ - with mock.patch.object(BaseRunner, "__init__", mock_runner_init): + def base_runner_init_spy(self, *args, **kwargs): + base_runner_init_mock(*args, **kwargs) + base_runner_init_orig(self, *args, **kwargs) + + with mock.patch.object(BaseRunner, "__init__", base_runner_init_spy): app = web.Application() - web.run_app(app, keepalive_timeout=new_timeout) + web.run_app(app, keepalive_timeout=new_timeout, + print=stopper(patched_loop)) - assert (mock_runner_init.call_args.kwargs["keepalive_timeout"] == + assert (base_runner_init_mock.call_args.kwargs["keepalive_timeout"] == new_timeout) From df611020bdf7d1184bed498f1e074f1146828318 Mon Sep 17 00:00:00 2001 From: Jan Buchar Date: Tue, 20 Oct 2020 13:18:51 +0200 Subject: [PATCH 05/14] Update doc --- docs/web_reference.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/web_reference.rst b/docs/web_reference.rst index 297a3a10d4e..2cf485c4123 100644 --- a/docs/web_reference.rst +++ b/docs/web_reference.rst @@ -2737,7 +2737,8 @@ Utilities .. function:: run_app(app, *, debug=False, host=None, port=None, \ path=None, sock=None, shutdown_timeout=60.0, \ - ssl_context=None, print=print, backlog=128, \ + keepalive_timeout=75.0, ssl_context=None, \ + print=print, backlog=128, \ access_log_class=aiohttp.helpers.AccessLogger, \ access_log_format=aiohttp.helpers.AccessLogger.LOG_FORMAT, \ access_log=aiohttp.log.access_logger, \ @@ -2793,6 +2794,10 @@ Utilities timeout but closes a server in a few milliseconds. + :param float keepalive_timeout: a delay before a TCP connection is + closed after a HTTP request. The delay + allows for reuse of a TCP connection. + :param ssl_context: :class:`ssl.SSLContext` for HTTPS server, ``None`` for HTTP connection. From dfa36393cf4ff96f794c7e6425f72f9eb7b97fa4 Mon Sep 17 00:00:00 2001 From: Jan Buchar Date: Tue, 20 Oct 2020 13:23:16 +0200 Subject: [PATCH 06/14] Update CONTRIBUTORS.txt --- CONTRIBUTORS.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 93d4ab5b1ed..35e2ae2e074 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -141,6 +141,7 @@ Jaesung Lee Jake Davis Jakob Ackermann Jakub Wilk +Jan Buchar Jashandeep Sohi Jens Steinhauser Jeonghun Lee From 427489cb2b10c0a10b13318055decfa92f4242da Mon Sep 17 00:00:00 2001 From: Jan Buchar Date: Tue, 20 Oct 2020 13:24:32 +0200 Subject: [PATCH 07/14] Add missing full stop --- CHANGES/5094.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES/5094.feature b/CHANGES/5094.feature index 726408ffc25..2d140f175e9 100644 --- a/CHANGES/5094.feature +++ b/CHANGES/5094.feature @@ -1 +1 @@ -Add keepalive_timeout parameter to web.run_app +Add keepalive_timeout parameter to web.run_app. From fcc6de6123e8d999f58515202d1bbf6176e0e443 Mon Sep 17 00:00:00 2001 From: Jan Buchar Date: Tue, 20 Oct 2020 14:07:17 +0200 Subject: [PATCH 08/14] Fix call_args usage for Python < 3.8 --- 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 4334f223ffa..92bba9a9f01 100644 --- a/tests/test_run_app.py +++ b/tests/test_run_app.py @@ -700,8 +700,8 @@ def base_runner_init_spy(self, *args, **kwargs): web.run_app(app, keepalive_timeout=new_timeout, print=stopper(patched_loop)) - assert (base_runner_init_mock.call_args.kwargs["keepalive_timeout"] == - new_timeout) + base_runner_init_kwargs = base_runner_init_mock.call_args[1] + assert (base_runner_init_kwargs["keepalive_timeout"] == new_timeout) @pytest.mark.skipif(not PY_37, From 998fb711b776e34bc7735dca5b3e8e40338f424f Mon Sep 17 00:00:00 2001 From: Jan Buchar Date: Tue, 20 Oct 2020 16:37:45 +0200 Subject: [PATCH 09/14] Update tests/test_run_app.py Co-authored-by: Sviatoslav Sydorenko --- tests/test_run_app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_run_app.py b/tests/test_run_app.py index 92bba9a9f01..ad6d82e946c 100644 --- a/tests/test_run_app.py +++ b/tests/test_run_app.py @@ -701,7 +701,7 @@ def base_runner_init_spy(self, *args, **kwargs): print=stopper(patched_loop)) base_runner_init_kwargs = base_runner_init_mock.call_args[1] - assert (base_runner_init_kwargs["keepalive_timeout"] == new_timeout) + assert base_runner_init_kwargs["keepalive_timeout"] == new_timeout @pytest.mark.skipif(not PY_37, From f136cd9af0da112f5e935eba7eca0d3c4458b670 Mon Sep 17 00:00:00 2001 From: Jan Buchar Date: Mon, 26 Oct 2020 14:54:35 +0100 Subject: [PATCH 10/14] Simplify test --- tests/test_run_app.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/tests/test_run_app.py b/tests/test_run_app.py index ad6d82e946c..f2e00cf9d0d 100644 --- a/tests/test_run_app.py +++ b/tests/test_run_app.py @@ -686,23 +686,19 @@ async def on_startup(app): exc_handler.assert_called_with(patched_loop, msg) -def test_run_app_keepalive_timeout(patched_loop): +def test_run_app_keepalive_timeout(patched_loop, mocker): new_timeout = 1234 - base_runner_init_mock = mock.MagicMock() base_runner_init_orig = BaseRunner.__init__ def base_runner_init_spy(self, *args, **kwargs): - base_runner_init_mock(*args, **kwargs) + assert kwargs["keepalive_timeout"] == new_timeout base_runner_init_orig(self, *args, **kwargs) + app = web.Application() with mock.patch.object(BaseRunner, "__init__", base_runner_init_spy): - app = web.Application() web.run_app(app, keepalive_timeout=new_timeout, print=stopper(patched_loop)) - base_runner_init_kwargs = base_runner_init_mock.call_args[1] - assert base_runner_init_kwargs["keepalive_timeout"] == new_timeout - @pytest.mark.skipif(not PY_37, reason="contextvars support is required") From 2a33550f5b3fecd7aa62319d552db58b271ee802 Mon Sep 17 00:00:00 2001 From: Jan Buchar Date: Mon, 26 Oct 2020 14:55:00 +0100 Subject: [PATCH 11/14] Update docs/web_reference.rst Co-authored-by: Andrew Svetlov --- docs/web_reference.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/web_reference.rst b/docs/web_reference.rst index 2cf485c4123..4fc62114fe7 100644 --- a/docs/web_reference.rst +++ b/docs/web_reference.rst @@ -2798,6 +2798,8 @@ Utilities closed after a HTTP request. The delay allows for reuse of a TCP connection. + .. versionadded:: 3.7 + :param ssl_context: :class:`ssl.SSLContext` for HTTPS server, ``None`` for HTTP connection. From 4ecfaf8ee9afe5a6a8fdd81da804cebe06b672a6 Mon Sep 17 00:00:00 2001 From: Jan Buchar Date: Tue, 27 Oct 2020 08:47:25 +0100 Subject: [PATCH 12/14] Change versionadded for the keepalive_timeout parameter --- docs/web_reference.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/web_reference.rst b/docs/web_reference.rst index 4fc62114fe7..4769a89749e 100644 --- a/docs/web_reference.rst +++ b/docs/web_reference.rst @@ -2798,7 +2798,7 @@ Utilities closed after a HTTP request. The delay allows for reuse of a TCP connection. - .. versionadded:: 3.7 + .. versionadded:: 3.8 :param ssl_context: :class:`ssl.SSLContext` for HTTPS server, ``None`` for HTTP connection. From ccefa6592f50be387e24f03499b2937802d2a925 Mon Sep 17 00:00:00 2001 From: Jan Buchar Date: Tue, 27 Oct 2020 19:35:01 +0100 Subject: [PATCH 13/14] Fix code style --- aiohttp/web.py | 515 +++++++++++++++++++++++------------------ tests/test_run_app.py | 520 ++++++++++++++++++++++++++---------------- 2 files changed, 605 insertions(+), 430 deletions(-) diff --git a/aiohttp/web.py b/aiohttp/web.py index e655b3f1085..4cdfc1d557d 100644 --- a/aiohttp/web.py +++ b/aiohttp/web.py @@ -155,128 +155,128 @@ __all__ = ( # web_app - 'Application', - 'CleanupError', + "Application", + "CleanupError", # web_exceptions - 'HTTPAccepted', - 'HTTPBadGateway', - 'HTTPBadRequest', - 'HTTPClientError', - 'HTTPConflict', - 'HTTPCreated', - 'HTTPError', - 'HTTPException', - 'HTTPExpectationFailed', - 'HTTPFailedDependency', - 'HTTPForbidden', - 'HTTPFound', - 'HTTPGatewayTimeout', - 'HTTPGone', - 'HTTPInsufficientStorage', - 'HTTPInternalServerError', - 'HTTPLengthRequired', - 'HTTPMethodNotAllowed', - 'HTTPMisdirectedRequest', - 'HTTPMovedPermanently', - 'HTTPMultipleChoices', - 'HTTPNetworkAuthenticationRequired', - 'HTTPNoContent', - 'HTTPNonAuthoritativeInformation', - 'HTTPNotAcceptable', - 'HTTPNotExtended', - 'HTTPNotFound', - 'HTTPNotImplemented', - 'HTTPNotModified', - 'HTTPOk', - 'HTTPPartialContent', - 'HTTPPaymentRequired', - 'HTTPPermanentRedirect', - 'HTTPPreconditionFailed', - 'HTTPPreconditionRequired', - 'HTTPProxyAuthenticationRequired', - 'HTTPRedirection', - 'HTTPRequestEntityTooLarge', - 'HTTPRequestHeaderFieldsTooLarge', - 'HTTPRequestRangeNotSatisfiable', - 'HTTPRequestTimeout', - 'HTTPRequestURITooLong', - 'HTTPResetContent', - 'HTTPSeeOther', - 'HTTPServerError', - 'HTTPServiceUnavailable', - 'HTTPSuccessful', - 'HTTPTemporaryRedirect', - 'HTTPTooManyRequests', - 'HTTPUnauthorized', - 'HTTPUnavailableForLegalReasons', - 'HTTPUnprocessableEntity', - 'HTTPUnsupportedMediaType', - 'HTTPUpgradeRequired', - 'HTTPUseProxy', - 'HTTPVariantAlsoNegotiates', - 'HTTPVersionNotSupported', + "HTTPAccepted", + "HTTPBadGateway", + "HTTPBadRequest", + "HTTPClientError", + "HTTPConflict", + "HTTPCreated", + "HTTPError", + "HTTPException", + "HTTPExpectationFailed", + "HTTPFailedDependency", + "HTTPForbidden", + "HTTPFound", + "HTTPGatewayTimeout", + "HTTPGone", + "HTTPInsufficientStorage", + "HTTPInternalServerError", + "HTTPLengthRequired", + "HTTPMethodNotAllowed", + "HTTPMisdirectedRequest", + "HTTPMovedPermanently", + "HTTPMultipleChoices", + "HTTPNetworkAuthenticationRequired", + "HTTPNoContent", + "HTTPNonAuthoritativeInformation", + "HTTPNotAcceptable", + "HTTPNotExtended", + "HTTPNotFound", + "HTTPNotImplemented", + "HTTPNotModified", + "HTTPOk", + "HTTPPartialContent", + "HTTPPaymentRequired", + "HTTPPermanentRedirect", + "HTTPPreconditionFailed", + "HTTPPreconditionRequired", + "HTTPProxyAuthenticationRequired", + "HTTPRedirection", + "HTTPRequestEntityTooLarge", + "HTTPRequestHeaderFieldsTooLarge", + "HTTPRequestRangeNotSatisfiable", + "HTTPRequestTimeout", + "HTTPRequestURITooLong", + "HTTPResetContent", + "HTTPSeeOther", + "HTTPServerError", + "HTTPServiceUnavailable", + "HTTPSuccessful", + "HTTPTemporaryRedirect", + "HTTPTooManyRequests", + "HTTPUnauthorized", + "HTTPUnavailableForLegalReasons", + "HTTPUnprocessableEntity", + "HTTPUnsupportedMediaType", + "HTTPUpgradeRequired", + "HTTPUseProxy", + "HTTPVariantAlsoNegotiates", + "HTTPVersionNotSupported", # web_fileresponse - 'FileResponse', + "FileResponse", # web_middlewares - 'middleware', - 'normalize_path_middleware', + "middleware", + "normalize_path_middleware", # web_protocol - 'PayloadAccessError', - 'RequestHandler', - 'RequestPayloadError', + "PayloadAccessError", + "RequestHandler", + "RequestPayloadError", # web_request - 'BaseRequest', - 'FileField', - 'Request', + "BaseRequest", + "FileField", + "Request", # web_response - 'ContentCoding', - 'Response', - 'StreamResponse', - 'json_response', + "ContentCoding", + "Response", + "StreamResponse", + "json_response", # web_routedef - 'AbstractRouteDef', - 'RouteDef', - 'RouteTableDef', - 'StaticDef', - 'delete', - 'get', - 'head', - 'options', - 'patch', - 'post', - 'put', - 'route', - 'static', - 'view', + "AbstractRouteDef", + "RouteDef", + "RouteTableDef", + "StaticDef", + "delete", + "get", + "head", + "options", + "patch", + "post", + "put", + "route", + "static", + "view", # web_runner - 'AppRunner', - 'BaseRunner', - 'BaseSite', - 'GracefulExit', - 'ServerRunner', - 'SockSite', - 'TCPSite', - 'UnixSite', - 'NamedPipeSite', + "AppRunner", + "BaseRunner", + "BaseSite", + "GracefulExit", + "ServerRunner", + "SockSite", + "TCPSite", + "UnixSite", + "NamedPipeSite", # web_server - 'Server', + "Server", # web_urldispatcher - 'AbstractResource', - 'AbstractRoute', - 'DynamicResource', - 'PlainResource', - 'Resource', - 'ResourceRoute', - 'StaticResource', - 'UrlDispatcher', - 'UrlMappingMatchInfo', - 'View', + "AbstractResource", + "AbstractRoute", + "DynamicResource", + "PlainResource", + "Resource", + "ResourceRoute", + "StaticResource", + "UrlDispatcher", + "UrlMappingMatchInfo", + "View", # web_ws - 'WebSocketReady', - 'WebSocketResponse', - 'WSMsgType', + "WebSocketReady", + "WebSocketResponse", + "WSMsgType", # web - 'run_app', + "run_app", ) @@ -286,33 +286,39 @@ SSLContext = Any # type: ignore -async def _run_app(app: Union[Application, Awaitable[Application]], *, - host: Optional[str]=None, - port: Optional[int]=None, - path: Optional[str]=None, - sock: Optional[socket.socket]=None, - shutdown_timeout: float=60.0, - keepalive_timeout: float=75.0, - ssl_context: Optional[SSLContext]=None, - print: Optional[Callable[..., None]]=print, - backlog: int=128, - access_log_class: Type[AbstractAccessLogger]=AccessLogger, - access_log_format: str=AccessLogger.LOG_FORMAT, - access_log: Optional[logging.Logger]=access_logger, - handle_signals: bool=True, - reuse_address: Optional[bool]=None, - reuse_port: Optional[bool]=None) -> None: +async def _run_app( + app: Union[Application, Awaitable[Application]], + *, + host: Optional[str] = None, + port: Optional[int] = None, + path: Optional[str] = None, + sock: Optional[socket.socket] = None, + shutdown_timeout: float = 60.0, + keepalive_timeout: float = 75.0, + ssl_context: Optional[SSLContext] = None, + print: Optional[Callable[..., None]] = print, + backlog: int = 128, + access_log_class: Type[AbstractAccessLogger] = AccessLogger, + access_log_format: str = AccessLogger.LOG_FORMAT, + access_log: Optional[logging.Logger] = access_logger, + handle_signals: bool = True, + reuse_address: Optional[bool] = None, + reuse_port: Optional[bool] = None, +) -> None: # An internal function to actually do all dirty job for application running if asyncio.iscoroutine(app): app = await app # type: ignore app = cast(Application, app) - runner = AppRunner(app, handle_signals=handle_signals, - access_log_class=access_log_class, - access_log_format=access_log_format, - access_log=access_log, - keepalive_timeout=keepalive_timeout) + runner = AppRunner( + app, + handle_signals=handle_signals, + access_log_class=access_log_class, + access_log_format=access_log_format, + access_log=access_log, + keepalive_timeout=keepalive_timeout, + ) await runner.setup() @@ -321,67 +327,108 @@ async def _run_app(app: Union[Application, Awaitable[Application]], *, try: if host is not None: if isinstance(host, (str, bytes, bytearray, memoryview)): - sites.append(TCPSite(runner, host, port, - shutdown_timeout=shutdown_timeout, - ssl_context=ssl_context, - backlog=backlog, - reuse_address=reuse_address, - reuse_port=reuse_port)) + sites.append( + TCPSite( + runner, + host, + port, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + reuse_address=reuse_address, + reuse_port=reuse_port, + ) + ) else: for h in host: - sites.append(TCPSite(runner, h, port, - shutdown_timeout=shutdown_timeout, - ssl_context=ssl_context, - backlog=backlog, - reuse_address=reuse_address, - reuse_port=reuse_port)) + sites.append( + TCPSite( + runner, + h, + port, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + reuse_address=reuse_address, + reuse_port=reuse_port, + ) + ) elif path is None and sock is None or port is not None: - sites.append(TCPSite(runner, port=port, - shutdown_timeout=shutdown_timeout, - ssl_context=ssl_context, backlog=backlog, - reuse_address=reuse_address, - reuse_port=reuse_port)) + sites.append( + TCPSite( + runner, + port=port, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + reuse_address=reuse_address, + reuse_port=reuse_port, + ) + ) if path is not None: if isinstance(path, (str, bytes, bytearray, memoryview)): - sites.append(UnixSite(runner, path, - shutdown_timeout=shutdown_timeout, - ssl_context=ssl_context, - backlog=backlog)) + sites.append( + UnixSite( + runner, + path, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + ) + ) else: for p in path: - sites.append(UnixSite(runner, p, - shutdown_timeout=shutdown_timeout, - ssl_context=ssl_context, - backlog=backlog)) + sites.append( + UnixSite( + runner, + p, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + ) + ) if sock is not None: if not isinstance(sock, Iterable): - sites.append(SockSite(runner, sock, - shutdown_timeout=shutdown_timeout, - ssl_context=ssl_context, - backlog=backlog)) + sites.append( + SockSite( + runner, + sock, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + ) + ) else: for s in sock: - sites.append(SockSite(runner, s, - shutdown_timeout=shutdown_timeout, - ssl_context=ssl_context, - backlog=backlog)) + sites.append( + SockSite( + runner, + s, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + ) + ) for site in sites: await site.start() if print: # pragma: no branch names = sorted(str(s.name) for s in runner.sites) - print("======== Running on {} ========\n" - "(Press CTRL+C to quit)".format(', '.join(names))) + print( + "======== Running on {} ========\n" + "(Press CTRL+C to quit)".format(", ".join(names)) + ) while True: await asyncio.sleep(3600) # sleep forever by 1 hour intervals finally: await runner.cleanup() -def _cancel_tasks(to_cancel: Set['asyncio.Task[Any]'], - loop: asyncio.AbstractEventLoop) -> None: +def _cancel_tasks( + to_cancel: Set["asyncio.Task[Any]"], loop: asyncio.AbstractEventLoop +) -> None: if not to_cancel: return @@ -389,65 +436,74 @@ def _cancel_tasks(to_cancel: Set['asyncio.Task[Any]'], task.cancel() loop.run_until_complete( - asyncio.gather(*to_cancel, loop=loop, return_exceptions=True)) + asyncio.gather(*to_cancel, loop=loop, return_exceptions=True) + ) for task in to_cancel: if task.cancelled(): continue if task.exception() is not None: - loop.call_exception_handler({ - 'message': 'unhandled exception during asyncio.run() shutdown', - 'exception': task.exception(), - 'task': task, - }) + loop.call_exception_handler( + { + "message": "unhandled exception during asyncio.run() shutdown", + "exception": task.exception(), + "task": task, + } + ) -def run_app(app: Union[Application, Awaitable[Application]], *, - debug: bool=False, - host: Optional[str]=None, - port: Optional[int]=None, - path: Optional[str]=None, - sock: Optional[socket.socket]=None, - shutdown_timeout: float=60.0, - keepalive_timeout: float=75.0, - ssl_context: Optional[SSLContext]=None, - print: Optional[Callable[..., None]]=print, - backlog: int=128, - access_log_class: Type[AbstractAccessLogger]=AccessLogger, - access_log_format: str=AccessLogger.LOG_FORMAT, - access_log: Optional[logging.Logger]=access_logger, - handle_signals: bool=True, - reuse_address: Optional[bool]=None, - reuse_port: Optional[bool]=None) -> None: +def run_app( + app: Union[Application, Awaitable[Application]], + *, + debug: bool = False, + host: Optional[str] = None, + port: Optional[int] = None, + path: Optional[str] = None, + sock: Optional[socket.socket] = None, + shutdown_timeout: float = 60.0, + keepalive_timeout: float = 75.0, + ssl_context: Optional[SSLContext] = None, + print: Optional[Callable[..., None]] = print, + backlog: int = 128, + access_log_class: Type[AbstractAccessLogger] = AccessLogger, + access_log_format: str = AccessLogger.LOG_FORMAT, + access_log: Optional[logging.Logger] = access_logger, + handle_signals: bool = True, + reuse_address: Optional[bool] = None, + reuse_port: Optional[bool] = None, +) -> None: """Run an app locally""" loop = asyncio.get_event_loop() loop.set_debug(debug) # Configure if and only if in debugging mode and using the default logger - if loop.get_debug() and access_log and access_log.name == 'aiohttp.access': + if loop.get_debug() and access_log and access_log.name == "aiohttp.access": if access_log.level == logging.NOTSET: access_log.setLevel(logging.DEBUG) if not access_log.hasHandlers(): access_log.addHandler(logging.StreamHandler()) try: - main_task = loop.create_task(_run_app( - app, - host=host, - port=port, - path=path, - sock=sock, - shutdown_timeout=shutdown_timeout, - keepalive_timeout=keepalive_timeout, - ssl_context=ssl_context, - print=print, - backlog=backlog, - access_log_class=access_log_class, - access_log_format=access_log_format, - access_log=access_log, - handle_signals=handle_signals, - reuse_address=reuse_address, - reuse_port=reuse_port)) + main_task = loop.create_task( + _run_app( + app, + host=host, + port=port, + path=path, + sock=sock, + shutdown_timeout=shutdown_timeout, + keepalive_timeout=keepalive_timeout, + ssl_context=ssl_context, + print=print, + backlog=backlog, + access_log_class=access_log_class, + access_log_format=access_log_format, + access_log=access_log, + handle_signals=handle_signals, + reuse_address=reuse_address, + reuse_port=reuse_port, + ) + ) loop.run_until_complete(main_task) except (GracefulExit, KeyboardInterrupt): # pragma: no cover pass @@ -461,54 +517,57 @@ def run_app(app: Union[Application, Awaitable[Application]], *, def main(argv: List[str]) -> None: arg_parser = ArgumentParser( - description="aiohttp.web Application server", - prog="aiohttp.web" + description="aiohttp.web Application server", prog="aiohttp.web" ) arg_parser.add_argument( "entry_func", - help=("Callable returning the `aiohttp.web.Application` instance to " - "run. Should be specified in the 'module:function' syntax."), - metavar="entry-func" + help=( + "Callable returning the `aiohttp.web.Application` instance to " + "run. Should be specified in the 'module:function' syntax." + ), + metavar="entry-func", ) arg_parser.add_argument( - "-H", "--hostname", + "-H", + "--hostname", help="TCP/IP hostname to serve on (default: %(default)r)", - default="localhost" + default="localhost", ) arg_parser.add_argument( - "-P", "--port", + "-P", + "--port", help="TCP/IP port to serve on (default: %(default)r)", type=int, - default="8080" + default="8080", ) arg_parser.add_argument( - "-U", "--path", + "-U", + "--path", help="Unix file system path to serve on. Specifying a path will cause " - "hostname and port arguments to be ignored.", + "hostname and port arguments to be ignored.", ) args, extra_argv = arg_parser.parse_known_args(argv) # Import logic mod_str, _, func_str = args.entry_func.partition(":") if not func_str or not mod_str: - arg_parser.error( - "'entry-func' not in 'module:function' syntax" - ) + arg_parser.error("'entry-func' not in 'module:function' syntax") if mod_str.startswith("."): arg_parser.error("relative module names not supported") try: module = import_module(mod_str) except ImportError as ex: - arg_parser.error("unable to import %s: %s" % (mod_str, ex)) + arg_parser.error(f"unable to import {mod_str}: {ex}") try: func = getattr(module, func_str) except AttributeError: - arg_parser.error("module %r has no attribute %r" % (mod_str, func_str)) + arg_parser.error(f"module {mod_str!r} has no attribute {func_str!r}") # Compatibility logic - if args.path is not None and not hasattr(socket, 'AF_UNIX'): - arg_parser.error("file system paths not supported by your operating" - " environment") + if args.path is not None and not hasattr(socket, "AF_UNIX"): + arg_parser.error( + "file system paths not supported by your operating" " environment" + ) logging.basicConfig(level=logging.DEBUG) diff --git a/tests/test_run_app.py b/tests/test_run_app.py index f2e00cf9d0d..694021e66d8 100644 --- a/tests/test_run_app.py +++ b/tests/test_run_app.py @@ -19,11 +19,11 @@ from aiohttp.web_runner import BaseRunner # Test for features of OS' socket support -_has_unix_domain_socks = hasattr(socket, 'AF_UNIX') +_has_unix_domain_socks = hasattr(socket, "AF_UNIX") if _has_unix_domain_socks: _abstract_path_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) try: - _abstract_path_sock.bind(b"\x00" + uuid4().hex.encode('ascii')) # type: ignore # noqa + _abstract_path_sock.bind(b"\x00" + uuid4().hex.encode("ascii")) # type: ignore # noqa except FileNotFoundError: _abstract_path_failed = True else: @@ -35,12 +35,10 @@ _abstract_path_failed = True skip_if_no_abstract_paths = pytest.mark.skipif( - _abstract_path_failed, - reason="Linux-style abstract paths are not supported." + _abstract_path_failed, reason="Linux-style abstract paths are not supported." ) skip_if_no_unix_socks = pytest.mark.skipif( - not _has_unix_domain_socks, - reason="Unix domain sockets are not supported" + not _has_unix_domain_socks, reason="Unix domain sockets are not supported" ) del _has_unix_domain_socks, _abstract_path_failed @@ -57,7 +55,7 @@ # tokio event loop does not allow to override attributes def skip_if_no_dict(loop): - if not hasattr(loop, '__dict__'): + if not hasattr(loop, "__dict__"): pytest.skip("can not override loop attributes") @@ -85,6 +83,7 @@ def raiser(): def f(*args): loop.call_soon(raiser) + return f @@ -97,10 +96,9 @@ def test_run_app_http(patched_loop) -> None: web.run_app(app, print=stopper(patched_loop)) - patched_loop.create_server.assert_called_with(mock.ANY, None, 8080, - ssl=None, backlog=128, - reuse_address=None, - reuse_port=None) + patched_loop.create_server.assert_called_with( + mock.ANY, None, 8080, ssl=None, backlog=128, reuse_address=None, reuse_port=None + ) startup_handler.assert_called_once_with(app) cleanup_handler.assert_called_once_with(app) @@ -109,102 +107,139 @@ def test_run_app_close_loop(patched_loop) -> None: app = web.Application() web.run_app(app, print=stopper(patched_loop)) - patched_loop.create_server.assert_called_with(mock.ANY, None, 8080, - ssl=None, backlog=128, - reuse_address=None, - reuse_port=None) + patched_loop.create_server.assert_called_with( + mock.ANY, None, 8080, ssl=None, backlog=128, reuse_address=None, reuse_port=None + ) assert patched_loop.is_closed() mock_unix_server_single = [ - mock.call(mock.ANY, '/tmp/testsock1.sock', ssl=None, backlog=128), + mock.call(mock.ANY, "/tmp/testsock1.sock", ssl=None, backlog=128), ] mock_unix_server_multi = [ - mock.call(mock.ANY, '/tmp/testsock1.sock', ssl=None, backlog=128), - mock.call(mock.ANY, '/tmp/testsock2.sock', ssl=None, backlog=128), + mock.call(mock.ANY, "/tmp/testsock1.sock", ssl=None, backlog=128), + mock.call(mock.ANY, "/tmp/testsock2.sock", ssl=None, backlog=128), ] mock_server_single = [ - mock.call(mock.ANY, '127.0.0.1', 8080, ssl=None, backlog=128, - reuse_address=None, reuse_port=None), + mock.call( + mock.ANY, + "127.0.0.1", + 8080, + ssl=None, + backlog=128, + reuse_address=None, + reuse_port=None, + ), ] mock_server_multi = [ - mock.call(mock.ANY, '127.0.0.1', 8080, ssl=None, - backlog=128, reuse_address=None, reuse_port=None), - mock.call(mock.ANY, '192.168.1.1', 8080, ssl=None, - backlog=128, reuse_address=None, reuse_port=None), + mock.call( + mock.ANY, + "127.0.0.1", + 8080, + ssl=None, + backlog=128, + reuse_address=None, + reuse_port=None, + ), + mock.call( + mock.ANY, + "192.168.1.1", + 8080, + ssl=None, + backlog=128, + reuse_address=None, + reuse_port=None, + ), ] mock_server_default_8989 = [ - mock.call(mock.ANY, None, 8989, ssl=None, backlog=128, - reuse_address=None, reuse_port=None) + mock.call( + mock.ANY, None, 8989, ssl=None, backlog=128, reuse_address=None, reuse_port=None + ) ] -mock_socket = mock.Mock(getsockname=lambda: ('mock-socket', 123)) +mock_socket = mock.Mock(getsockname=lambda: ("mock-socket", 123)) mixed_bindings_tests = ( ( # type: ignore "Nothing Specified", {}, - [mock.call(mock.ANY, None, 8080, ssl=None, backlog=128, - reuse_address=None, reuse_port=None)], - [] - ), - ( - "Port Only", - {'port': 8989}, - mock_server_default_8989, - [] - ), - ( - "Multiple Hosts", - {'host': ('127.0.0.1', '192.168.1.1')}, - mock_server_multi, - [] + [ + mock.call( + mock.ANY, + None, + 8080, + ssl=None, + backlog=128, + reuse_address=None, + reuse_port=None, + ) + ], + [], ), + ("Port Only", {"port": 8989}, mock_server_default_8989, []), + ("Multiple Hosts", {"host": ("127.0.0.1", "192.168.1.1")}, mock_server_multi, []), ( "Multiple Paths", - {'path': ('/tmp/testsock1.sock', '/tmp/testsock2.sock')}, + {"path": ("/tmp/testsock1.sock", "/tmp/testsock2.sock")}, [], - mock_unix_server_multi + mock_unix_server_multi, ), ( "Multiple Paths, Port", - {'path': ('/tmp/testsock1.sock', '/tmp/testsock2.sock'), - 'port': 8989}, + {"path": ("/tmp/testsock1.sock", "/tmp/testsock2.sock"), "port": 8989}, mock_server_default_8989, mock_unix_server_multi, ), ( "Multiple Paths, Single Host", - {'path': ('/tmp/testsock1.sock', '/tmp/testsock2.sock'), - 'host': '127.0.0.1'}, + {"path": ("/tmp/testsock1.sock", "/tmp/testsock2.sock"), "host": "127.0.0.1"}, mock_server_single, - mock_unix_server_multi + mock_unix_server_multi, ), ( "Single Path, Single Host", - {'path': '/tmp/testsock1.sock', 'host': '127.0.0.1'}, + {"path": "/tmp/testsock1.sock", "host": "127.0.0.1"}, mock_server_single, - mock_unix_server_single + mock_unix_server_single, ), ( "Single Path, Multiple Hosts", - {'path': '/tmp/testsock1.sock', 'host': ('127.0.0.1', '192.168.1.1')}, + {"path": "/tmp/testsock1.sock", "host": ("127.0.0.1", "192.168.1.1")}, mock_server_multi, - mock_unix_server_single + mock_unix_server_single, ), ( "Single Path, Port", - {'path': '/tmp/testsock1.sock', 'port': 8989}, + {"path": "/tmp/testsock1.sock", "port": 8989}, mock_server_default_8989, - mock_unix_server_single + mock_unix_server_single, ), ( "Multiple Paths, Multiple Hosts, Port", - {'path': ('/tmp/testsock1.sock', '/tmp/testsock2.sock'), - 'host': ('127.0.0.1', '192.168.1.1'), 'port': 8000}, - [mock.call(mock.ANY, '127.0.0.1', 8000, ssl=None, backlog=128, - reuse_address=None, reuse_port=None), - mock.call(mock.ANY, '192.168.1.1', 8000, ssl=None, backlog=128, - reuse_address=None, reuse_port=None)], - mock_unix_server_multi + { + "path": ("/tmp/testsock1.sock", "/tmp/testsock2.sock"), + "host": ("127.0.0.1", "192.168.1.1"), + "port": 8000, + }, + [ + mock.call( + mock.ANY, + "127.0.0.1", + 8000, + ssl=None, + backlog=128, + reuse_address=None, + reuse_port=None, + ), + mock.call( + mock.ANY, + "192.168.1.1", + 8000, + ssl=None, + backlog=128, + reuse_address=None, + reuse_port=None, + ), + ], + mock_unix_server_multi, ), ( "Only socket", @@ -215,78 +250,166 @@ def test_run_app_close_loop(patched_loop) -> None: ( "Socket, port", {"sock": [mock_socket], "port": 8765}, - [mock.call(mock.ANY, None, 8765, ssl=None, backlog=128, - reuse_address=None, reuse_port=None), - mock.call(mock.ANY, sock=mock_socket, ssl=None, backlog=128)], + [ + mock.call( + mock.ANY, + None, + 8765, + ssl=None, + backlog=128, + reuse_address=None, + reuse_port=None, + ), + mock.call(mock.ANY, sock=mock_socket, ssl=None, backlog=128), + ], [], ), ( "Socket, Host, No port", - {"sock": [mock_socket], "host": 'localhost'}, - [mock.call(mock.ANY, 'localhost', 8080, ssl=None, backlog=128, - reuse_address=None, reuse_port=None), - mock.call(mock.ANY, sock=mock_socket, ssl=None, backlog=128)], + {"sock": [mock_socket], "host": "localhost"}, + [ + mock.call( + mock.ANY, + "localhost", + 8080, + ssl=None, + backlog=128, + reuse_address=None, + reuse_port=None, + ), + mock.call(mock.ANY, sock=mock_socket, ssl=None, backlog=128), + ], [], ), ( "reuse_port", {"reuse_port": True}, - [mock.call(mock.ANY, None, 8080, ssl=None, backlog=128, - reuse_address=None, reuse_port=True)], - [] + [ + mock.call( + mock.ANY, + None, + 8080, + ssl=None, + backlog=128, + reuse_address=None, + reuse_port=True, + ) + ], + [], ), ( "reuse_address", {"reuse_address": False}, - [mock.call(mock.ANY, None, 8080, ssl=None, backlog=128, - reuse_address=False, reuse_port=None)], - [] + [ + mock.call( + mock.ANY, + None, + 8080, + ssl=None, + backlog=128, + reuse_address=False, + reuse_port=None, + ) + ], + [], ), ( "reuse_port, reuse_address", {"reuse_address": True, "reuse_port": True}, - [mock.call(mock.ANY, None, 8080, ssl=None, backlog=128, - reuse_address=True, reuse_port=True)], - [] + [ + mock.call( + mock.ANY, + None, + 8080, + ssl=None, + backlog=128, + reuse_address=True, + reuse_port=True, + ) + ], + [], ), ( "Port, reuse_port", - {'port': 8989, "reuse_port": True}, - [mock.call(mock.ANY, None, 8989, ssl=None, backlog=128, - reuse_address=None, reuse_port=True)], - [] + {"port": 8989, "reuse_port": True}, + [ + mock.call( + mock.ANY, + None, + 8989, + ssl=None, + backlog=128, + reuse_address=None, + reuse_port=True, + ) + ], + [], ), ( "Multiple Hosts, reuse_port", - {'host': ('127.0.0.1', '192.168.1.1'), "reuse_port": True}, + {"host": ("127.0.0.1", "192.168.1.1"), "reuse_port": True}, [ - mock.call(mock.ANY, '127.0.0.1', 8080, ssl=None, - backlog=128, reuse_address=None, reuse_port=True), - mock.call(mock.ANY, '192.168.1.1', 8080, ssl=None, - backlog=128, reuse_address=None, reuse_port=True), + mock.call( + mock.ANY, + "127.0.0.1", + 8080, + ssl=None, + backlog=128, + reuse_address=None, + reuse_port=True, + ), + mock.call( + mock.ANY, + "192.168.1.1", + 8080, + ssl=None, + backlog=128, + reuse_address=None, + reuse_port=True, + ), ], - [] + [], ), ( "Multiple Paths, Port, reuse_address", - {'path': ('/tmp/testsock1.sock', '/tmp/testsock2.sock'), - 'port': 8989, - 'reuse_address': False}, - [mock.call(mock.ANY, None, 8989, ssl=None, backlog=128, - reuse_address=False, reuse_port=None)], + { + "path": ("/tmp/testsock1.sock", "/tmp/testsock2.sock"), + "port": 8989, + "reuse_address": False, + }, + [ + mock.call( + mock.ANY, + None, + 8989, + ssl=None, + backlog=128, + reuse_address=False, + reuse_port=None, + ) + ], mock_unix_server_multi, ), ( "Multiple Paths, Single Host, reuse_address, reuse_port", - {'path': ('/tmp/testsock1.sock', '/tmp/testsock2.sock'), - 'host': '127.0.0.1', - 'reuse_address': True, - 'reuse_port': True}, + { + "path": ("/tmp/testsock1.sock", "/tmp/testsock2.sock"), + "host": "127.0.0.1", + "reuse_address": True, + "reuse_port": True, + }, [ - mock.call(mock.ANY, '127.0.0.1', 8080, ssl=None, backlog=128, - reuse_address=True, reuse_port=True), + mock.call( + mock.ANY, + "127.0.0.1", + 8080, + ssl=None, + backlog=128, + reuse_address=True, + reuse_port=True, + ), ], - mock_unix_server_multi + mock_unix_server_multi, ), ) mixed_bindings_test_ids = [test[0] for test in mixed_bindings_tests] @@ -294,20 +417,18 @@ def test_run_app_close_loop(patched_loop) -> None: @pytest.mark.parametrize( - 'run_app_kwargs, expected_server_calls, expected_unix_server_calls', + "run_app_kwargs, expected_server_calls, expected_unix_server_calls", mixed_bindings_test_params, - ids=mixed_bindings_test_ids + ids=mixed_bindings_test_ids, ) -def test_run_app_mixed_bindings(run_app_kwargs, expected_server_calls, - expected_unix_server_calls, - patched_loop): +def test_run_app_mixed_bindings( + run_app_kwargs, expected_server_calls, expected_unix_server_calls, patched_loop +): app = web.Application() web.run_app(app, print=stopper(patched_loop), **run_app_kwargs) - assert (patched_loop.create_unix_server.mock_calls == - expected_unix_server_calls) - assert (patched_loop.create_server.mock_calls == - expected_server_calls) + assert patched_loop.create_unix_server.mock_calls == expected_unix_server_calls + assert patched_loop.create_server.mock_calls == expected_server_calls def test_run_app_https(patched_loop) -> None: @@ -317,22 +438,26 @@ def test_run_app_https(patched_loop) -> None: web.run_app(app, ssl_context=ssl_context, print=stopper(patched_loop)) patched_loop.create_server.assert_called_with( - mock.ANY, None, 8443, ssl=ssl_context, backlog=128, - reuse_address=None, reuse_port=None) + mock.ANY, + None, + 8443, + ssl=ssl_context, + backlog=128, + reuse_address=None, + reuse_port=None, + ) -def test_run_app_nondefault_host_port(patched_loop, - aiohttp_unused_port) -> None: +def test_run_app_nondefault_host_port(patched_loop, aiohttp_unused_port) -> None: port = aiohttp_unused_port() - host = '127.0.0.1' + host = "127.0.0.1" app = web.Application() web.run_app(app, host=host, port=port, print=stopper(patched_loop)) - patched_loop.create_server.assert_called_with(mock.ANY, host, port, - ssl=None, backlog=128, - reuse_address=None, - reuse_port=None) + patched_loop.create_server.assert_called_with( + mock.ANY, host, port, ssl=None, backlog=128, reuse_address=None, reuse_port=None + ) def test_run_app_custom_backlog(patched_loop) -> None: @@ -340,60 +465,59 @@ def test_run_app_custom_backlog(patched_loop) -> None: web.run_app(app, backlog=10, print=stopper(patched_loop)) patched_loop.create_server.assert_called_with( - mock.ANY, None, 8080, ssl=None, backlog=10, - reuse_address=None, reuse_port=None) + mock.ANY, None, 8080, ssl=None, backlog=10, reuse_address=None, reuse_port=None + ) def test_run_app_custom_backlog_unix(patched_loop) -> None: app = web.Application() - web.run_app(app, path='/tmp/tmpsock.sock', - backlog=10, print=stopper(patched_loop)) + web.run_app(app, path="/tmp/tmpsock.sock", backlog=10, print=stopper(patched_loop)) patched_loop.create_unix_server.assert_called_with( - mock.ANY, '/tmp/tmpsock.sock', ssl=None, backlog=10) + mock.ANY, "/tmp/tmpsock.sock", ssl=None, backlog=10 + ) @skip_if_no_unix_socks def test_run_app_http_unix_socket(patched_loop, tmp_path) -> None: app = web.Application() - sock_path = str(tmp_path / 'socket.sock') + sock_path = str(tmp_path / "socket.sock") printer = mock.Mock(wraps=stopper(patched_loop)) web.run_app(app, path=sock_path, print=printer) - patched_loop.create_unix_server.assert_called_with(mock.ANY, sock_path, - ssl=None, backlog=128) - assert "http://unix:{}:".format(sock_path) in printer.call_args[0][0] + patched_loop.create_unix_server.assert_called_with( + mock.ANY, sock_path, ssl=None, backlog=128 + ) + assert f"http://unix:{sock_path}:" in printer.call_args[0][0] @skip_if_no_unix_socks def test_run_app_https_unix_socket(patched_loop, tmp_path) -> None: app = web.Application() - sock_path = str(tmp_path / 'socket.sock') + sock_path = str(tmp_path / "socket.sock") ssl_context = ssl.create_default_context() printer = mock.Mock(wraps=stopper(patched_loop)) web.run_app(app, path=sock_path, ssl_context=ssl_context, print=printer) patched_loop.create_unix_server.assert_called_with( - mock.ANY, sock_path, ssl=ssl_context, backlog=128) - assert "https://unix:{}:".format(sock_path) in printer.call_args[0][0] + mock.ANY, sock_path, ssl=ssl_context, backlog=128 + ) + assert f"https://unix:{sock_path}:" in printer.call_args[0][0] @skip_if_no_unix_socks @skip_if_no_abstract_paths def test_run_app_abstract_linux_socket(patched_loop) -> None: - sock_path = b"\x00" + uuid4().hex.encode('ascii') + sock_path = b"\x00" + uuid4().hex.encode("ascii") app = web.Application() web.run_app( - app, path=sock_path.decode('ascii', 'ignore'), - print=stopper(patched_loop)) + app, path=sock_path.decode("ascii", "ignore"), print=stopper(patched_loop) + ) patched_loop.create_unix_server.assert_called_with( - mock.ANY, - sock_path.decode('ascii'), - ssl=None, - backlog=128 + mock.ANY, sock_path.decode("ascii"), ssl=None, backlog=128 ) @@ -402,7 +526,7 @@ def test_run_app_preexisting_inet_socket(patched_loop, mocker) -> None: sock = socket.socket() with contextlib.closing(sock): - sock.bind(('0.0.0.0', 0)) + sock.bind(("0.0.0.0", 0)) _, port = sock.getsockname() printer = mock.Mock(wraps=stopper(patched_loop)) @@ -411,7 +535,7 @@ def test_run_app_preexisting_inet_socket(patched_loop, mocker) -> None: patched_loop.create_server.assert_called_with( mock.ANY, sock=sock, backlog=128, ssl=None ) - assert "http://0.0.0.0:{}".format(port) in printer.call_args[0][0] + assert f"http://0.0.0.0:{port}" in printer.call_args[0][0] @pytest.mark.skipif(not HAS_IPV6, reason="IPv6 is not available") @@ -420,7 +544,7 @@ def test_run_app_preexisting_inet6_socket(patched_loop) -> None: sock = socket.socket(socket.AF_INET6) with contextlib.closing(sock): - sock.bind(('::', 0)) + sock.bind(("::", 0)) port = sock.getsockname()[1] printer = mock.Mock(wraps=stopper(patched_loop)) @@ -429,14 +553,14 @@ def test_run_app_preexisting_inet6_socket(patched_loop) -> None: patched_loop.create_server.assert_called_with( mock.ANY, sock=sock, backlog=128, ssl=None ) - assert "http://[::]:{}".format(port) in printer.call_args[0][0] + assert f"http://[::]:{port}" in printer.call_args[0][0] @skip_if_no_unix_socks def test_run_app_preexisting_unix_socket(patched_loop, mocker) -> None: app = web.Application() - sock_path = '/tmp/test_preexisting_sock1' + sock_path = "/tmp/test_preexisting_sock1" sock = socket.socket(socket.AF_UNIX) with contextlib.closing(sock): sock.bind(sock_path) @@ -448,7 +572,7 @@ def test_run_app_preexisting_unix_socket(patched_loop, mocker) -> None: patched_loop.create_server.assert_called_with( mock.ANY, sock=sock, backlog=128, ssl=None ) - assert "http://unix:{}:".format(sock_path) in printer.call_args[0][0] + assert f"http://unix:{sock_path}:" in printer.call_args[0][0] def test_run_app_multiple_preexisting_sockets(patched_loop) -> None: @@ -457,20 +581,22 @@ def test_run_app_multiple_preexisting_sockets(patched_loop) -> None: sock1 = socket.socket() sock2 = socket.socket() with contextlib.closing(sock1), contextlib.closing(sock2): - sock1.bind(('0.0.0.0', 0)) + sock1.bind(("0.0.0.0", 0)) _, port1 = sock1.getsockname() - sock2.bind(('0.0.0.0', 0)) + sock2.bind(("0.0.0.0", 0)) _, port2 = sock2.getsockname() printer = mock.Mock(wraps=stopper(patched_loop)) web.run_app(app, sock=(sock1, sock2), print=printer) - patched_loop.create_server.assert_has_calls([ - mock.call(mock.ANY, sock=sock1, backlog=128, ssl=None), - mock.call(mock.ANY, sock=sock2, backlog=128, ssl=None) - ]) - assert "http://0.0.0.0:{}".format(port1) in printer.call_args[0][0] - assert "http://0.0.0.0:{}".format(port2) in printer.call_args[0][0] + patched_loop.create_server.assert_has_calls( + [ + mock.call(mock.ANY, sock=sock1, backlog=128, ssl=None), + mock.call(mock.ANY, sock=sock2, backlog=128, ssl=None), + ] + ) + assert f"http://0.0.0.0:{port1}" in printer.call_args[0][0] + assert f"http://0.0.0.0:{port2}" in printer.call_args[0][0] _script_test_signal = """ @@ -484,8 +610,9 @@ def test_run_app_multiple_preexisting_sockets(patched_loop) -> None: def test_sigint() -> None: skip_if_on_windows() - proc = subprocess.Popen([sys.executable, "-u", "-c", _script_test_signal], - stdout=subprocess.PIPE) + proc = subprocess.Popen( + [sys.executable, "-u", "-c", _script_test_signal], stdout=subprocess.PIPE + ) for line in proc.stdout: if line.startswith(b"======== Running on"): break @@ -496,8 +623,9 @@ def test_sigint() -> None: def test_sigterm() -> None: skip_if_on_windows() - proc = subprocess.Popen([sys.executable, "-u", "-c", _script_test_signal], - stdout=subprocess.PIPE) + proc = subprocess.Popen( + [sys.executable, "-u", "-c", _script_test_signal], stdout=subprocess.PIPE + ) for line in proc.stdout: if line.startswith(b"======== Running on"): break @@ -535,10 +663,9 @@ async def make_app(): web.run_app(make_app(), print=stopper(patched_loop)) - patched_loop.create_server.assert_called_with(mock.ANY, None, 8080, - ssl=None, backlog=128, - reuse_address=None, - reuse_port=None) + patched_loop.create_server.assert_called_with( + mock.ANY, None, 8080, ssl=None, backlog=128, reuse_address=None, reuse_port=None + ) startup_handler.assert_called_once_with(mock.ANY) cleanup_handler.assert_called_once_with(mock.ANY) @@ -546,37 +673,32 @@ async def make_app(): def test_run_app_default_logger(monkeypatch, patched_loop): logger = web.access_logger attrs = { - 'hasHandlers.return_value': False, - 'level': logging.NOTSET, - 'name': 'aiohttp.access', + "hasHandlers.return_value": False, + "level": logging.NOTSET, + "name": "aiohttp.access", } - mock_logger = mock.create_autospec(logger, name='mock_access_logger') + mock_logger = mock.create_autospec(logger, name="mock_access_logger") mock_logger.configure_mock(**attrs) app = web.Application() - web.run_app(app, debug=True, - print=stopper(patched_loop), - access_log=mock_logger) + web.run_app(app, debug=True, print=stopper(patched_loop), access_log=mock_logger) mock_logger.setLevel.assert_any_call(logging.DEBUG) mock_logger.hasHandlers.assert_called_with() - assert isinstance(mock_logger.addHandler.call_args[0][0], - logging.StreamHandler) + assert isinstance(mock_logger.addHandler.call_args[0][0], logging.StreamHandler) def test_run_app_default_logger_setup_requires_debug(patched_loop): logger = web.access_logger attrs = { - 'hasHandlers.return_value': False, - 'level': logging.NOTSET, - 'name': 'aiohttp.access', + "hasHandlers.return_value": False, + "level": logging.NOTSET, + "name": "aiohttp.access", } - mock_logger = mock.create_autospec(logger, name='mock_access_logger') + mock_logger = mock.create_autospec(logger, name="mock_access_logger") mock_logger.configure_mock(**attrs) app = web.Application() - web.run_app(app, debug=False, - print=stopper(patched_loop), - access_log=mock_logger) + web.run_app(app, debug=False, print=stopper(patched_loop), access_log=mock_logger) mock_logger.setLevel.assert_not_called() mock_logger.hasHandlers.assert_not_called() mock_logger.addHandler.assert_not_called() @@ -585,17 +707,15 @@ def test_run_app_default_logger_setup_requires_debug(patched_loop): def test_run_app_default_logger_setup_requires_default_logger(patched_loop): logger = web.access_logger attrs = { - 'hasHandlers.return_value': False, - 'level': logging.NOTSET, - 'name': None, + "hasHandlers.return_value": False, + "level": logging.NOTSET, + "name": None, } - mock_logger = mock.create_autospec(logger, name='mock_access_logger') + mock_logger = mock.create_autospec(logger, name="mock_access_logger") mock_logger.configure_mock(**attrs) app = web.Application() - web.run_app(app, debug=True, - print=stopper(patched_loop), - access_log=mock_logger) + web.run_app(app, debug=True, print=stopper(patched_loop), access_log=mock_logger) mock_logger.setLevel.assert_not_called() mock_logger.hasHandlers.assert_not_called() mock_logger.addHandler.assert_not_called() @@ -604,17 +724,15 @@ def test_run_app_default_logger_setup_requires_default_logger(patched_loop): def test_run_app_default_logger_setup_only_if_unconfigured(patched_loop): logger = web.access_logger attrs = { - 'hasHandlers.return_value': True, - 'level': None, - 'name': 'aiohttp.access', + "hasHandlers.return_value": True, + "level": None, + "name": "aiohttp.access", } - mock_logger = mock.create_autospec(logger, name='mock_access_logger') + mock_logger = mock.create_autospec(logger, name="mock_access_logger") mock_logger.configure_mock(**attrs) app = web.Application() - web.run_app(app, debug=True, - print=stopper(patched_loop), - access_log=mock_logger) + web.run_app(app, debug=True, print=stopper(patched_loop), access_log=mock_logger) mock_logger.setLevel.assert_not_called() mock_logger.hasHandlers.assert_called_with() mock_logger.addHandler.assert_not_called() @@ -679,9 +797,9 @@ async def on_startup(app): assert task.done() msg = { - 'message': 'unhandled exception during asyncio.run() shutdown', - 'exception': exc, - 'task': task, + "message": "unhandled exception during asyncio.run() shutdown", + "exception": exc, + "task": task, } exc_handler.assert_called_with(patched_loop, msg) @@ -696,33 +814,31 @@ def base_runner_init_spy(self, *args, **kwargs): app = web.Application() with mock.patch.object(BaseRunner, "__init__", base_runner_init_spy): - web.run_app(app, keepalive_timeout=new_timeout, - print=stopper(patched_loop)) + web.run_app(app, keepalive_timeout=new_timeout, print=stopper(patched_loop)) -@pytest.mark.skipif(not PY_37, - reason="contextvars support is required") +@pytest.mark.skipif(not PY_37, reason="contextvars support is required") def test_run_app_context_vars(patched_loop): from contextvars import ContextVar count = 0 - VAR = ContextVar('VAR', default='default') + VAR = ContextVar("VAR", default="default") async def on_startup(app): nonlocal count - assert 'init' == VAR.get() - VAR.set('on_startup') + assert "init" == VAR.get() + VAR.set("on_startup") count += 1 async def on_cleanup(app): nonlocal count - assert 'on_startup' == VAR.get() + assert "on_startup" == VAR.get() count += 1 async def init(): nonlocal count - assert 'default' == VAR.get() - VAR.set('init') + assert "default" == VAR.get() + VAR.set("init") app = web.Application() app.on_startup.append(on_startup) From 4dda2788ca23804b41e5289212a28a9d02de4499 Mon Sep 17 00:00:00 2001 From: Jan Buchar Date: Tue, 27 Oct 2020 19:46:01 +0100 Subject: [PATCH 14/14] Apply suggestions from code review Co-authored-by: Sviatoslav Sydorenko --- tests/test_run_app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_run_app.py b/tests/test_run_app.py index b91b8fea139..3c60807f2cc 100644 --- a/tests/test_run_app.py +++ b/tests/test_run_app.py @@ -825,7 +825,7 @@ async def on_startup(app): exc_handler.assert_called_with(patched_loop, msg) -def test_run_app_keepalive_timeout(patched_loop, mocker): +def test_run_app_keepalive_timeout(patched_loop, mocker, monkeypatch): new_timeout = 1234 base_runner_init_orig = BaseRunner.__init__ @@ -834,8 +834,8 @@ def base_runner_init_spy(self, *args, **kwargs): base_runner_init_orig(self, *args, **kwargs) app = web.Application() - with mock.patch.object(BaseRunner, "__init__", base_runner_init_spy): - web.run_app(app, keepalive_timeout=new_timeout, print=stopper(patched_loop)) + monkeypatch.setattr(BaseRunner, "__init__", base_runner_init_spy) + web.run_app(app, keepalive_timeout=new_timeout, print=stopper(patched_loop)) @pytest.mark.skipif(not PY_37, reason="contextvars support is required")