From dd62442190ffdbeaacfd41140b9c9331d1f6b2cd Mon Sep 17 00:00:00 2001 From: taras Date: Sat, 6 Jun 2026 07:36:35 +0200 Subject: [PATCH 01/25] Added aiofastnet to speedups extra --- CHANGES/12744.feature.rst | 3 + CONTRIBUTORS.txt | 1 + aiohttp/connector.py | 34 +++++- aiohttp/web_fileresponse.py | 19 +++- aiohttp/web_runner.py | 24 +++- docs/faq.rst | 46 ++++++++ docs/spelling_wordlist.txt | 4 + pyproject.toml | 1 + requirements/lint.in | 1 + requirements/runtime-deps.in | 1 + tests/conftest.py | 4 + tests/test_connector.py | 63 ++++++----- tests/test_proxy.py | 47 ++++---- tests/test_run_app.py | 155 +++++++++++++++++--------- tests/test_web_runner.py | 17 +-- tests/test_web_sendfile_functional.py | 6 +- 16 files changed, 301 insertions(+), 125 deletions(-) create mode 100644 CHANGES/12744.feature.rst diff --git a/CHANGES/12744.feature.rst b/CHANGES/12744.feature.rst new file mode 100644 index 00000000000..9f2f116f71f --- /dev/null +++ b/CHANGES/12744.feature.rst @@ -0,0 +1,3 @@ +Added ``aiofastnet`` package to ``speedups`` extra. aiofastnet provides faster alternatives to the standard loop functions, which are used to run server or establish connections. If you experience any issues that you think might be related to this change, you can try to disable ``aiofastnet`` by uninstalling aiofastnet package. + +-- by :user:`tarasko`. diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 2aaa0a02403..837b8456f9d 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -367,6 +367,7 @@ Sunit Deshpande Sviatoslav Bulbakha Sviatoslav Sydorenko Taha Jahangir +Taras Kozlov Taras Voinarovskyi Terence Honles Thanos Lefteris diff --git a/aiohttp/connector.py b/aiohttp/connector.py index 6e70b3a28a2..ddad3930eb1 100644 --- a/aiohttp/connector.py +++ b/aiohttp/connector.py @@ -1,5 +1,6 @@ import asyncio import functools +import importlib import random import socket import sys @@ -18,6 +19,13 @@ from aiohappyeyeballs import AddrInfoType, SocketFactoryType from multidict import CIMultiDict +aiofastnet: Any +try: + import aiofastnet +except ImportError: + aiofastnet = None + + from . import hdrs, helpers from .abc import AbstractResolver, ResolveResult from .client_exceptions import ( @@ -96,6 +104,24 @@ from .tracing import Trace +async def create_connection( + loop: asyncio.AbstractEventLoop, *args: Any, **kwargs: Any, +) -> tuple[asyncio.Transport, ResponseHandler]: + if aiofastnet is not None: + return await aiofastnet.create_connection(loop, *args, **kwargs) # type: ignore[no-any-return] + else: + return await loop.create_connection(*args, **kwargs) + + +async def start_tls( + loop: asyncio.AbstractEventLoop, *args: Any, **kwargs: Any +) -> asyncio.BaseTransport | None: + if aiofastnet is not None: + return await aiofastnet.start_tls(loop, *args, **kwargs) # type: ignore[no-any-return] + else: + return await loop.start_tls(*args, **kwargs) + + class Connection: """Represents a single connection.""" @@ -1259,7 +1285,7 @@ async def _wrap_create_connection( and sys.version_info >= (3, 11) ): kwargs["ssl_shutdown_timeout"] = self._ssl_shutdown_timeout - return await self._loop.create_connection(*args, **kwargs, sock=sock) + return await create_connection(self._loop, *args, **kwargs, sock=sock) except cert_errors as exc: raise ClientConnectorCertificateError(req.connection_key, exc) from exc except ssl_errors as exc: @@ -1340,7 +1366,8 @@ async def _start_tls_connection( try: # ssl_shutdown_timeout is only available in Python 3.11+ if sys.version_info >= (3, 11) and self._ssl_shutdown_timeout: - tls_transport = await self._loop.start_tls( + tls_transport = await start_tls( + self._loop, underlying_transport, tls_proto, sslcontext, @@ -1349,7 +1376,8 @@ async def _start_tls_connection( ssl_shutdown_timeout=self._ssl_shutdown_timeout, ) else: - tls_transport = await self._loop.start_tls( + tls_transport = await start_tls( + self._loop, underlying_transport, tls_proto, sslcontext, diff --git a/aiohttp/web_fileresponse.py b/aiohttp/web_fileresponse.py index d45afc0dd7d..655e953bcd7 100644 --- a/aiohttp/web_fileresponse.py +++ b/aiohttp/web_fileresponse.py @@ -1,4 +1,5 @@ import asyncio +import importlib import io import os import pathlib @@ -11,6 +12,13 @@ from types import MappingProxyType from typing import IO, TYPE_CHECKING, Any, Final, Optional +aiofastnet: Any +try: + import aiofastnet +except ImportError: + aiofastnet = None + + from . import hdrs from .abc import AbstractStreamWriter from .helpers import DEFAULT_CHUNK_SIZE, ETAG_ANY, ETag, must_be_empty_body @@ -34,6 +42,15 @@ _T_OnChunkSent = Optional[Callable[[bytes], Awaitable[None]]] +async def sendfile( + loop: asyncio.AbstractEventLoop, *args: Any, **kwargs: Any +) -> int: + if aiofastnet is not None: + return await aiofastnet.sendfile(loop, *args, **kwargs) # type: ignore[no-any-return] + else: + return await loop.sendfile(*args, **kwargs) + + NOSENDFILE: Final[bool] = bool(os.environ.get("AIOHTTP_NOSENDFILE")) CONTENT_TYPES: Final[MimeTypes] = MimeTypes() @@ -132,7 +149,7 @@ async def _sendfile( raise ConnectionResetError("Connection lost") try: - await loop.sendfile(transport, fobj, offset, count) + await sendfile(loop, transport, fobj, offset, count) except NotImplementedError: return await self._sendfile_fallback(writer, fobj, offset, count) diff --git a/aiohttp/web_runner.py b/aiohttp/web_runner.py index 82c3bd277f8..db4dcbca58c 100644 --- a/aiohttp/web_runner.py +++ b/aiohttp/web_runner.py @@ -1,4 +1,5 @@ import asyncio +import importlib import signal import socket from abc import ABC, abstractmethod @@ -6,6 +7,12 @@ from yarl import URL +aiofastnet: Any +try: + import aiofastnet +except ImportError: + aiofastnet = None + from .abc import AbstractAccessLogger, AbstractStreamWriter from .http_parser import RawRequestMessage from .streams import StreamReader @@ -21,6 +28,16 @@ except ImportError: # pragma: no cover SSLContext = object # type: ignore[misc,assignment] + +async def create_server( + loop: asyncio.AbstractEventLoop, *args: Any, **kwargs: Any +) -> asyncio.Server: + if aiofastnet is not None: + return await aiofastnet.create_server(loop, *args, **kwargs) # type: ignore[no-any-return] + else: + return await loop.create_server(*args, **kwargs) + + __all__ = ( "BaseSite", "TCPSite", @@ -130,7 +147,8 @@ async def start(self) -> None: loop = asyncio.get_running_loop() server = self._runner.server assert server is not None - self._server = await loop.create_server( + self._server = await create_server( + loop, server, self._host, self._port, @@ -244,8 +262,8 @@ async def start(self) -> None: loop = asyncio.get_running_loop() server = self._runner.server assert server is not None - self._server = await loop.create_server( - server, sock=self._sock, ssl=self._ssl_context, backlog=self._backlog + self._server = await create_server( + loop, server, sock=self._sock, ssl=self._ssl_context, backlog=self._backlog ) diff --git a/docs/faq.rst b/docs/faq.rst index 3f50b855588..b4276cf1843 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -263,6 +263,52 @@ enable compression in NGINX (you are deploying aiohttp behind reverse proxy, right?). +How do I enable Kernel TLS, and should I do it? +----------------------------------------------- + +Kernel TLS (KTLS) allows aiohttp to move encryption and decryption of +TLS traffic from user space to the kernel. It was added to the Linux kernel in +4.13, but full support for TLS 1.3 and modern ciphers is available only +since 5.19. + +KTLS will be beneficial if you run an HTTPS server that often returns +:class:`~aiohttp.web.FileResponse` objects or you have a high-end NIC that can +offload TLS encryption. For ordinary +dynamic responses, small files, or deployments behind a TLS-terminating reverse +proxy, it is unlikely to help and may actually slightly degrade performance. + +KTLS is supported through the ``aiofastnet`` package, which is installed as +part of the ``speedups`` extra. + +To enable KTLS, you have to do and check the following: + +* Make sure the Linux ``tls`` kernel module is loaded:: + + sudo modprobe tls + +* Make sure the ``ssl.OP_ENABLE_KTLS`` option is enabled in ``SSLContext`` + (available since Python 3.12):: + + sslcontext.options |= ssl.OP_ENABLE_KTLS + +* Make sure Python is using OpenSSL 3.0 or newer. OpenSSL should have been + built on a machine whose Linux headers are new enough. OpenSSL needs Linux + headers at least 4.13.0 to build the transmit path; older headers make it + skip KTLS support. Typically, Python is using the system OpenSSL on Linux, + but some times distributions ship their own OpenSSL. The following commands + will help identify the OpenSSL version and which ``libssl`` and ``libcrypto`` + are being used by the ``ssl`` module:: + + python -c "import ssl; print(ssl.OPENSSL_VERSION)" + ldd "$(python -c 'import _ssl; print(_ssl.__file__)')" + + +If ``ssl.OP_ENABLE_KTLS`` was requested in ``sslcontext``, but ``aiofastnet`` +could not enable KTLS, it will log a warning suggesting the possible reason. + +After enabling it, run your own benchmarks and verify that KTLS actually +speeds things up in your case. + How do I manage a ClientSession within a web server? ---------------------------------------------------- diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index 75d3d0c8323..14b6da20607 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -4,6 +4,7 @@ ABI addons aiodns aioes +aiofastnet aiohttp aiohttpdemo aiohttp’s @@ -183,6 +184,7 @@ keepalive keepalived keepalives keepaliving +KTLS kib KiB kwarg @@ -227,6 +229,7 @@ namedtuple nameservers namespace netrc +NIC nginx Nginx Nikolay @@ -236,6 +239,7 @@ nowait OAuth Online optimizations +OpenSSL orjson os outcoming diff --git a/pyproject.toml b/pyproject.toml index 0c27cc88bb5..1d96cf4206d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,7 @@ dynamic = [ [project.optional-dependencies] speedups = [ "aiodns >= 3.3.0; sys_platform != 'android' and sys_platform != 'ios'", + "aiofastnet >= 0.9.0; platform_python_implementation == 'CPython' and (platform_machine == 'x86_64' or platform_machine == 'AMD64' or platform_machine == 'aarch64')", "Brotli >= 1.2; platform_python_implementation == 'CPython' and sys_platform != 'android' and sys_platform != 'ios'", "brotlicffi >= 1.2; platform_python_implementation != 'CPython'", "backports.zstd; platform_python_implementation == 'CPython' and python_version < '3.14' and sys_platform != 'android' and sys_platform != 'ios'", diff --git a/requirements/lint.in b/requirements/lint.in index c0a86f2435f..e5c3f7f4533 100644 --- a/requirements/lint.in +++ b/requirements/lint.in @@ -1,4 +1,5 @@ aiodns +aiofastnet backports.zstd; implementation_name == "cpython" and python_version < "3.14" blockbuster freezegun diff --git a/requirements/runtime-deps.in b/requirements/runtime-deps.in index d70fc5a9dbc..03974b7b217 100644 --- a/requirements/runtime-deps.in +++ b/requirements/runtime-deps.in @@ -1,6 +1,7 @@ # Extracted from `pyproject.toml` via `make sync-direct-runtime-deps` aiodns >= 3.3.0; sys_platform != 'android' and sys_platform != 'ios' +aiofastnet >= 0.9.0; platform_python_implementation == 'CPython' and (platform_machine == 'x86_64' or platform_machine == 'AMD64' or platform_machine == 'aarch64') aiohappyeyeballs >= 2.5.0 aiosignal >= 1.4.0 async-timeout >= 4.0, < 6.0 ; python_version < '3.11' diff --git a/tests/conftest.py b/tests/conftest.py index 3869d93794e..3e1a6b2e9fc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -109,6 +109,10 @@ def blockbuster(request: pytest.FixtureRequest) -> Iterator[None]: # synchronization in async code. # Allow lock.acquire calls to prevent these false positives bb.functions["threading.Lock.acquire"].deactivate() + + # aiofastnet is using sendfile on a non-blocking socket. + # blockbuster triggers anyway. Seems like a false positive + bb.functions["os.sendfile"].deactivate() yield diff --git a/tests/test_connector.py b/tests/test_connector.py index 0b1cbcff03e..cbc1f561031 100644 --- a/tests/test_connector.py +++ b/tests/test_connector.py @@ -671,7 +671,7 @@ async def test_tcp_connector_certificate_error( conn = aiohttp.TCPConnector() with mock.patch.object( - conn._loop, + connector_module, "create_connection", autospec=True, spec_set=True, @@ -694,7 +694,7 @@ async def test_tcp_connector_server_hostname_default( conn = aiohttp.TCPConnector() with mock.patch.object( - conn._loop, "create_connection", autospec=True, spec_set=True + connector_module, "create_connection", autospec=True, spec_set=True ) as create_connection: create_connection.return_value = mock.Mock(), mock.Mock() @@ -712,7 +712,7 @@ async def test_tcp_connector_server_hostname_override( conn = aiohttp.TCPConnector() with mock.patch.object( - conn._loop, "create_connection", autospec=True, spec_set=True + connector_module, "create_connection", autospec=True, spec_set=True ) as create_connection: create_connection.return_value = mock.Mock(), mock.Mock() @@ -869,7 +869,7 @@ def get_extra_info(param: str) -> object: side_effect=_resolve_host, ), mock.patch.object( - conn._loop, + connector_module, "create_connection", autospec=True, spec_set=True, @@ -970,7 +970,7 @@ async def create_connection( side_effect=sock_connect, ): with mock.patch.object( - conn._loop, + connector_module, "create_connection", autospec=True, spec_set=True, @@ -1063,7 +1063,7 @@ async def create_connection( side_effect=_resolve_host, ), mock.patch.object( - conn._loop, + connector_module, "create_connection", autospec=True, spec_set=True, @@ -1145,7 +1145,7 @@ async def create_connection( side_effect=sock_connect, ): with mock.patch.object( - conn._loop, + connector_module, "create_connection", autospec=True, spec_set=True, @@ -1258,7 +1258,7 @@ async def create_connection( side_effect=_resolve_host, ), mock.patch.object( - conn._loop, + connector_module, "create_connection", autospec=True, spec_set=True, @@ -2219,7 +2219,7 @@ async def test_tcp_connector_ssl_shutdown_timeout_passed_to_create_connection( conn = aiohttp.TCPConnector(ssl_shutdown_timeout=2.5) with mock.patch.object( - conn._loop, "create_connection", autospec=True, spec_set=True + connector_module, "create_connection", autospec=True, spec_set=True ) as create_connection: create_connection.return_value = mock.Mock(), mock.Mock() @@ -2237,7 +2237,7 @@ async def test_tcp_connector_ssl_shutdown_timeout_passed_to_create_connection( conn = aiohttp.TCPConnector(ssl_shutdown_timeout=None) with mock.patch.object( - conn._loop, "create_connection", autospec=True, spec_set=True + connector_module, "create_connection", autospec=True, spec_set=True ) as create_connection: create_connection.return_value = mock.Mock(), mock.Mock() @@ -2256,7 +2256,7 @@ async def test_tcp_connector_ssl_shutdown_timeout_passed_to_create_connection( conn = aiohttp.TCPConnector(ssl_shutdown_timeout=2.5) with mock.patch.object( - conn._loop, "create_connection", autospec=True, spec_set=True + connector_module, "create_connection", autospec=True, spec_set=True ) as create_connection: create_connection.return_value = mock.Mock(), mock.Mock() @@ -2284,7 +2284,7 @@ async def test_tcp_connector_ssl_shutdown_timeout_not_passed_pre_311( assert any(issubclass(warn.category, RuntimeWarning) for warn in w) with mock.patch.object( - conn._loop, "create_connection", autospec=True, spec_set=True + connector_module, "create_connection", autospec=True, spec_set=True ) as create_connection: create_connection.return_value = mock.Mock(), mock.Mock() @@ -2442,7 +2442,7 @@ async def test_tcp_connector_ssl_shutdown_timeout_zero_not_passed( conn = aiohttp.TCPConnector(ssl_shutdown_timeout=0) with mock.patch.object( - conn._loop, "create_connection", autospec=True, spec_set=True + connector_module, "create_connection", autospec=True, spec_set=True ) as create_connection: create_connection.return_value = mock.Mock(), mock.Mock() @@ -2474,7 +2474,7 @@ async def test_tcp_connector_ssl_shutdown_timeout_nonzero_passed( conn = aiohttp.TCPConnector(ssl_shutdown_timeout=5.0) with mock.patch.object( - conn._loop, "create_connection", autospec=True, spec_set=True + connector_module, "create_connection", autospec=True, spec_set=True ) as create_connection: create_connection.return_value = mock.Mock(), mock.Mock() @@ -2543,7 +2543,9 @@ async def test_start_tls_exception_with_ssl_shutdown_timeout_zero() -> None: mock.patch.object( conn, "_get_ssl_context", return_value=ssl.create_default_context() ), - mock.patch.object(conn._loop, "start_tls", side_effect=OSError("TLS failed")), + mock.patch.object( + connector_module, "start_tls", side_effect=OSError("TLS failed") + ), ): with pytest.raises(OSError): await conn._start_tls_connection(underlying_transport, req, ClientTimeout()) @@ -2575,7 +2577,9 @@ async def test_start_tls_exception_with_ssl_shutdown_timeout_nonzero() -> None: mock.patch.object( conn, "_get_ssl_context", return_value=ssl.create_default_context() ), - mock.patch.object(conn._loop, "start_tls", side_effect=OSError("TLS failed")), + mock.patch.object( + connector_module, "start_tls", side_effect=OSError("TLS failed") + ), ): with pytest.raises(OSError): await conn._start_tls_connection(underlying_transport, req, ClientTimeout()) @@ -2610,7 +2614,9 @@ async def test_start_tls_exception_with_ssl_shutdown_timeout_nonzero_pre_311() - mock.patch.object( conn, "_get_ssl_context", return_value=ssl.create_default_context() ), - mock.patch.object(conn._loop, "start_tls", side_effect=OSError("TLS failed")), + mock.patch.object( + connector_module, "start_tls", side_effect=OSError("TLS failed") + ), ): with pytest.raises(OSError): await conn._start_tls_connection(underlying_transport, req, ClientTimeout()) @@ -4072,7 +4078,7 @@ async def _resolve_host( first_conn = next(iter(conn._conns.values()))[0][0] assert first_conn.transport is not None - _sslcontext = first_conn.transport._ssl_protocol._sslcontext # type: ignore[attr-defined] + _sslcontext = first_conn.transport.get_extra_info("sslcontext") assert _sslcontext is client_ssl_ctx r.close() @@ -4510,19 +4516,15 @@ async def allow_connection_and_add_dummy_waiter() -> None: def test_connector_multiple_event_loop(make_client_request: _RequestMaker) -> None: """Test the connector with multiple event loops.""" + async def create_connection(*args: object, **kwargs: object) -> NoReturn: + raise ssl.CertificateError + async def async_connect() -> Literal[True]: conn = aiohttp.TCPConnector() loop = asyncio.get_running_loop() req = make_client_request("GET", URL("https://127.0.0.1"), loop=loop) with suppress(aiohttp.ClientConnectorError): - with mock.patch.object( - conn._loop, - "create_connection", - autospec=True, - spec_set=True, - side_effect=ssl.CertificateError, - ): - await conn.connect(req, [], ClientTimeout()) + await conn.connect(req, [], ClientTimeout()) return True def test_connect() -> Literal[True]: @@ -4532,9 +4534,10 @@ def test_connect() -> Literal[True]: finally: loop.close() - with futures.ThreadPoolExecutor() as executor: - res_list = [executor.submit(test_connect) for _ in range(2)] - raw_response_list = [res.result() for res in futures.as_completed(res_list)] + with mock.patch.object(connector_module, "create_connection", create_connection): + with futures.ThreadPoolExecutor() as executor: + res_list = [executor.submit(test_connect) for _ in range(2)] + raw_response_list = [res.result() for res in futures.as_completed(res_list)] assert raw_response_list == [True, True] @@ -4559,7 +4562,7 @@ async def test_tcp_connector_socket_factory( ) with mock.patch.object( - conn._loop, + connector_module, "create_connection", autospec=True, spec_set=True, diff --git a/tests/test_proxy.py b/tests/test_proxy.py index 9cd8b3f1d6a..ad47a608115 100644 --- a/tests/test_proxy.py +++ b/tests/test_proxy.py @@ -10,7 +10,7 @@ from yarl import URL import aiohttp -from aiohttp import hdrs +from aiohttp import connector as connector_module, hdrs from aiohttp.abc import AbstractStreamWriter from aiohttp.client_reqrep import ( ClientRequest, @@ -70,7 +70,7 @@ async def test_connect( # type: ignore[misc] } ) with mock.patch.object( - event_loop, + connector_module, "create_connection", autospec=True, return_value=(proto.transport, proto), @@ -131,7 +131,7 @@ async def test_proxy_headers( # type: ignore[misc] } ) with mock.patch.object( - event_loop, + connector_module, "create_connection", autospec=True, return_value=(proto.transport, proto), @@ -204,7 +204,7 @@ async def test_proxy_connection_error( # type: ignore[misc] } with mock.patch.object(connector, "_resolve_host", autospec=True, return_value=[r]): with mock.patch.object( - connector._loop, + connector_module, "create_connection", autospec=True, side_effect=OSError("dont take it serious"), @@ -274,13 +274,13 @@ async def test_proxy_server_hostname_default( # type: ignore[misc] ): tr, proto = mock.Mock(), mock.Mock() with mock.patch.object( - event_loop, + connector_module, "create_connection", autospec=True, return_value=(tr, proto), ): with mock.patch.object( - event_loop, + connector_module, "start_tls", autospec=True, return_value=mock.Mock(), @@ -360,13 +360,13 @@ async def test_proxy_server_hostname_override( # type: ignore[misc] ): tr, proto = mock.Mock(), mock.Mock() with mock.patch.object( - event_loop, + connector_module, "create_connection", autospec=True, return_value=(tr, proto), ): with mock.patch.object( - event_loop, + connector_module, "start_tls", autospec=True, return_value=mock.Mock(), @@ -482,14 +482,14 @@ def close(self) -> None: return_value=fingerprint_mock, ), mock.patch.object( # Called on connection to http://proxy.example.com - event_loop, + connector_module, "create_connection", autospec=True, spec_set=True, return_value=(mock.Mock(), mock.Mock()), ), mock.patch.object( # Called on connection to https://www.python.org - event_loop, + connector_module, "start_tls", autospec=True, spec_set=True, @@ -561,13 +561,13 @@ async def test_https_connect( # type: ignore[misc] ): tr, proto = mock.Mock(), mock.Mock() with mock.patch.object( - event_loop, + connector_module, "create_connection", autospec=True, return_value=(tr, proto), ): with mock.patch.object( - event_loop, + connector_module, "start_tls", autospec=True, return_value=mock.Mock(), @@ -647,14 +647,14 @@ async def test_https_connect_certificate_error( # type: ignore[misc] tr, proto = mock.Mock(), mock.Mock() # Called on connection to http://proxy.example.com with mock.patch.object( - event_loop, + connector_module, "create_connection", autospec=True, return_value=(tr, proto), ): # Called on connection to https://www.python.org with mock.patch.object( - event_loop, + connector_module, "start_tls", autospec=True, side_effect=ssl.CertificateError, @@ -728,14 +728,14 @@ async def test_https_connect_ssl_error( # type: ignore[misc] tr, proto = mock.Mock(), mock.Mock() # Called on connection to http://proxy.example.com with mock.patch.object( - event_loop, + connector_module, "create_connection", autospec=True, return_value=(tr, proto), ): # Called on connection to https://www.python.org with mock.patch.object( - event_loop, + connector_module, "start_tls", autospec=True, side_effect=ssl.SSLError, @@ -811,7 +811,7 @@ async def test_https_connect_http_proxy_error( # type: ignore[misc] tr.get_extra_info.return_value = None # Called on connection to http://proxy.example.com with mock.patch.object( - event_loop, + connector_module, "create_connection", autospec=True, return_value=(tr, proto), @@ -891,7 +891,7 @@ async def test_https_connect_resp_start_error( # type: ignore[misc] tr.get_extra_info.return_value = None # Called on connection to http://proxy.example.com with mock.patch.object( - event_loop, + connector_module, "create_connection", autospec=True, return_value=(tr, proto), @@ -940,7 +940,7 @@ async def test_request_port( # type: ignore[misc] tr.get_extra_info.return_value = None # Called on connection to http://proxy.example.com with mock.patch.object( - event_loop, "create_connection", autospec=True, return_value=(tr, proto) + connector_module, "create_connection", autospec=True, return_value=(tr, proto) ): req = make_client_request( "GET", @@ -1008,13 +1008,13 @@ async def test_https_connect_pass_ssl_context( # type: ignore[misc] ): tr, proto = mock.Mock(), mock.Mock() with mock.patch.object( - event_loop, + connector_module, "create_connection", autospec=True, return_value=(tr, proto), ): with mock.patch.object( - event_loop, + connector_module, "start_tls", autospec=True, return_value=mock.Mock(), @@ -1031,6 +1031,7 @@ async def test_https_connect_pass_ssl_context( # type: ignore[misc] # ssl_shutdown_timeout=0 is not passed to start_tls tls_m.assert_called_with( + event_loop, mock.ANY, mock.ANY, _SSL_CONTEXT_VERIFIED, @@ -1103,13 +1104,13 @@ async def test_https_auth( # type: ignore[misc] ) as host_m: tr, proto = mock.Mock(), mock.Mock() with mock.patch.object( - event_loop, + connector_module, "create_connection", autospec=True, return_value=(tr, proto), ): with mock.patch.object( - event_loop, + connector_module, "start_tls", autospec=True, return_value=mock.Mock(), diff --git a/tests/test_run_app.py b/tests/test_run_app.py index a1cf5dd0f92..e8acc2ccae9 100644 --- a/tests/test_run_app.py +++ b/tests/test_run_app.py @@ -26,6 +26,7 @@ WSCloseCode, web, ) +from aiohttp import web_runner as web_runner_module from aiohttp.log import access_logger from aiohttp.web_protocol import RequestHandler from aiohttp.web_runner import BaseRunner @@ -69,6 +70,17 @@ def skip_if_on_windows() -> None: pytest.skip("the test is not valid for Windows") +@pytest.fixture +def create_server_mock() -> Iterator[mock.AsyncMock]: + server = mock.create_autospec(asyncio.Server, spec_set=True, instance=True) + server.wait_closed.return_value = None + server.sockets = [] + create_server_mock = mock.AsyncMock(return_value=server) + + with mock.patch.object(web_runner_module, "create_server", create_server_mock): + yield create_server_mock + + @pytest.fixture def patched_loop( event_loop: asyncio.AbstractEventLoop, @@ -103,7 +115,7 @@ def f(*args: object) -> None: return f -def test_run_app_http(patched_loop: asyncio.AbstractEventLoop) -> None: +def test_run_app_http(patched_loop: asyncio.AbstractEventLoop, create_server_mock: mock.AsyncMock) -> None: app = web.Application() startup_handler = mock.AsyncMock() app.on_startup.append(startup_handler) @@ -112,19 +124,21 @@ def test_run_app_http(patched_loop: asyncio.AbstractEventLoop) -> None: web.run_app(app, print=stopper(patched_loop), loop=patched_loop) - patched_loop.create_server.assert_called_with( # type: ignore[attr-defined] - mock.ANY, None, 8080, ssl=None, backlog=128, reuse_address=None, reuse_port=None + create_server_mock.assert_called_with( + patched_loop, 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) -def test_run_app_close_loop(patched_loop: asyncio.AbstractEventLoop) -> None: +def test_run_app_close_loop( + patched_loop: asyncio.AbstractEventLoop, create_server_mock: mock.AsyncMock +) -> None: app = web.Application() web.run_app(app, print=stopper(patched_loop), loop=patched_loop) - patched_loop.create_server.assert_called_with( # type: ignore[attr-defined] - mock.ANY, None, 8080, ssl=None, backlog=128, reuse_address=None, reuse_port=None + create_server_mock.assert_called_with( + patched_loop, mock.ANY, None, 8080, ssl=None, backlog=128, reuse_address=None, reuse_port=None, ) assert patched_loop.is_closed() @@ -160,6 +174,7 @@ async def failing_ctx(_app: web.Application) -> AsyncIterator[None]: ] mock_server_single = [ mock.call( + mock.ANY, mock.ANY, "127.0.0.1", 8080, @@ -171,6 +186,7 @@ async def failing_ctx(_app: web.Application) -> AsyncIterator[None]: ] mock_server_multi = [ mock.call( + mock.ANY, mock.ANY, "127.0.0.1", 8080, @@ -180,6 +196,7 @@ async def failing_ctx(_app: web.Application) -> AsyncIterator[None]: reuse_port=None, ), mock.call( + mock.ANY, mock.ANY, "192.168.1.1", 8080, @@ -191,7 +208,7 @@ async def failing_ctx(_app: web.Application) -> AsyncIterator[None]: ] mock_server_default_8989 = [ mock.call( - mock.ANY, None, 8989, ssl=None, backlog=128, reuse_address=None, reuse_port=None + mock.ANY, mock.ANY, None, 8989, ssl=None, backlog=128, reuse_address=None, reuse_port=None ) ] mock_socket = mock.Mock(getsockname=lambda: ("mock-socket", 123)) @@ -203,6 +220,7 @@ async def failing_ctx(_app: web.Application) -> AsyncIterator[None]: {}, [ mock.call( + mock.ANY, mock.ANY, None, 8080, @@ -261,6 +279,7 @@ async def failing_ctx(_app: web.Application) -> AsyncIterator[None]: }, [ mock.call( + mock.ANY, mock.ANY, "127.0.0.1", 8000, @@ -270,6 +289,7 @@ async def failing_ctx(_app: web.Application) -> AsyncIterator[None]: reuse_port=None, ), mock.call( + mock.ANY, mock.ANY, "192.168.1.1", 8000, @@ -284,7 +304,7 @@ async def failing_ctx(_app: web.Application) -> AsyncIterator[None]: ( "Only socket", {"sock": [mock_socket]}, - [mock.call(mock.ANY, ssl=None, sock=mock_socket, backlog=128)], + [mock.call(mock.ANY, mock.ANY, ssl=None, sock=mock_socket, backlog=128)], [], ), ( @@ -292,6 +312,7 @@ async def failing_ctx(_app: web.Application) -> AsyncIterator[None]: {"sock": [mock_socket], "port": 8765}, [ mock.call( + mock.ANY, mock.ANY, None, 8765, @@ -300,7 +321,7 @@ async def failing_ctx(_app: web.Application) -> AsyncIterator[None]: reuse_address=None, reuse_port=None, ), - mock.call(mock.ANY, sock=mock_socket, ssl=None, backlog=128), + mock.call(mock.ANY, mock.ANY, sock=mock_socket, ssl=None, backlog=128), ], [], ), @@ -309,6 +330,7 @@ async def failing_ctx(_app: web.Application) -> AsyncIterator[None]: {"sock": [mock_socket], "host": "localhost"}, [ mock.call( + mock.ANY, mock.ANY, "localhost", 8080, @@ -317,7 +339,7 @@ async def failing_ctx(_app: web.Application) -> AsyncIterator[None]: reuse_address=None, reuse_port=None, ), - mock.call(mock.ANY, sock=mock_socket, ssl=None, backlog=128), + mock.call(mock.ANY, mock.ANY, sock=mock_socket, ssl=None, backlog=128), ], [], ), @@ -326,6 +348,7 @@ async def failing_ctx(_app: web.Application) -> AsyncIterator[None]: {"reuse_port": True}, [ mock.call( + mock.ANY, mock.ANY, None, 8080, @@ -342,6 +365,7 @@ async def failing_ctx(_app: web.Application) -> AsyncIterator[None]: {"reuse_address": False}, [ mock.call( + mock.ANY, mock.ANY, None, 8080, @@ -358,6 +382,7 @@ async def failing_ctx(_app: web.Application) -> AsyncIterator[None]: {"reuse_address": True, "reuse_port": True}, [ mock.call( + mock.ANY, mock.ANY, None, 8080, @@ -374,6 +399,7 @@ async def failing_ctx(_app: web.Application) -> AsyncIterator[None]: {"port": 8989, "reuse_port": True}, [ mock.call( + mock.ANY, mock.ANY, None, 8989, @@ -390,6 +416,7 @@ async def failing_ctx(_app: web.Application) -> AsyncIterator[None]: {"host": ("127.0.0.1", "192.168.1.1"), "reuse_port": True}, [ mock.call( + mock.ANY, mock.ANY, "127.0.0.1", 8080, @@ -399,6 +426,7 @@ async def failing_ctx(_app: web.Application) -> AsyncIterator[None]: reuse_port=True, ), mock.call( + mock.ANY, mock.ANY, "192.168.1.1", 8080, @@ -419,6 +447,7 @@ async def failing_ctx(_app: web.Application) -> AsyncIterator[None]: }, [ mock.call( + mock.ANY, mock.ANY, None, 8989, @@ -440,6 +469,7 @@ async def failing_ctx(_app: web.Application) -> AsyncIterator[None]: }, [ mock.call( + mock.ANY, mock.ANY, "127.0.0.1", 8080, @@ -466,15 +496,16 @@ def test_run_app_mixed_bindings( # type: ignore[misc] expected_server_calls: list[mock._Call], expected_unix_server_calls: list[mock._Call], patched_loop: asyncio.AbstractEventLoop, + create_server_mock: mock.AsyncMock, ) -> None: app = web.Application() web.run_app(app, print=stopper(patched_loop), **run_app_kwargs, loop=patched_loop) assert patched_loop.create_unix_server.mock_calls == expected_unix_server_calls # type: ignore[attr-defined] - assert patched_loop.create_server.mock_calls == expected_server_calls # type: ignore[attr-defined] + assert create_server_mock.mock_calls == expected_server_calls -def test_run_app_https(patched_loop: asyncio.AbstractEventLoop) -> None: +def test_run_app_https(patched_loop: asyncio.AbstractEventLoop, create_server_mock: mock.AsyncMock) -> None: app = web.Application() ssl_context = ssl.create_default_context() @@ -482,7 +513,8 @@ def test_run_app_https(patched_loop: asyncio.AbstractEventLoop) -> None: app, ssl_context=ssl_context, print=stopper(patched_loop), loop=patched_loop ) - patched_loop.create_server.assert_called_with( # type: ignore[attr-defined] + create_server_mock.assert_called_with( + patched_loop, mock.ANY, None, 8443, @@ -494,23 +526,32 @@ def test_run_app_https(patched_loop: asyncio.AbstractEventLoop) -> None: def test_run_app_nondefault_host_port( - patched_loop: asyncio.AbstractEventLoop, unused_port_socket: socket.socket + patched_loop: asyncio.AbstractEventLoop, + unused_port_socket: socket.socket, + create_server_mock: mock.AsyncMock, ) -> None: port = unused_port_socket.getsockname()[1] host = "127.0.0.1" app = web.Application() - web.run_app( - app, host=host, port=port, print=stopper(patched_loop), loop=patched_loop - ) + web.run_app(app, host=host, port=port, print=stopper(patched_loop), loop=patched_loop) - patched_loop.create_server.assert_called_with( # type: ignore[attr-defined] - mock.ANY, host, port, ssl=None, backlog=128, reuse_address=None, reuse_port=None + create_server_mock.assert_called_with( + patched_loop, + mock.ANY, + host, + port, + ssl=None, + backlog=128, + reuse_address=None, + reuse_port=None, ) def test_run_app_with_sock( - patched_loop: asyncio.AbstractEventLoop, unused_port_socket: socket.socket + patched_loop: asyncio.AbstractEventLoop, + unused_port_socket: socket.socket, + create_server_mock: mock.AsyncMock, ) -> None: sock = unused_port_socket app = web.Application() @@ -521,12 +562,14 @@ def test_run_app_with_sock( loop=patched_loop, ) - patched_loop.create_server.assert_called_with( # type: ignore[attr-defined] - mock.ANY, sock=sock, ssl=None, backlog=128 + create_server_mock.assert_called_with( + patched_loop, mock.ANY, sock=sock, ssl=None, backlog=128 ) -def test_run_app_multiple_hosts(patched_loop: asyncio.AbstractEventLoop) -> None: +def test_run_app_multiple_hosts( + patched_loop: asyncio.AbstractEventLoop, create_server_mock: mock.AsyncMock +) -> None: hosts = ("127.0.0.1", "127.0.0.2") app = web.Application() @@ -534,6 +577,7 @@ def test_run_app_multiple_hosts(patched_loop: asyncio.AbstractEventLoop) -> None calls = map( lambda h: mock.call( + patched_loop, mock.ANY, h, 8080, @@ -544,15 +588,17 @@ def test_run_app_multiple_hosts(patched_loop: asyncio.AbstractEventLoop) -> None ), hosts, ) - patched_loop.create_server.assert_has_calls(calls) # type: ignore[attr-defined] + create_server_mock.assert_has_calls(list(calls)) -def test_run_app_custom_backlog(patched_loop: asyncio.AbstractEventLoop) -> None: +def test_run_app_custom_backlog( + patched_loop: asyncio.AbstractEventLoop, create_server_mock: mock.AsyncMock +) -> None: app = web.Application() web.run_app(app, backlog=10, print=stopper(patched_loop), loop=patched_loop) - patched_loop.create_server.assert_called_with( # type: ignore[attr-defined] - mock.ANY, None, 8080, ssl=None, backlog=10, reuse_address=None, reuse_port=None + create_server_mock.assert_called_with( + patched_loop, mock.ANY, None, 8080, ssl=None, backlog=10, reuse_address=None, reuse_port=None, ) @@ -630,7 +676,9 @@ def test_run_app_abstract_linux_socket( def test_run_app_preexisting_inet_socket( - patched_loop: asyncio.AbstractEventLoop, mocker: MockerFixture + patched_loop: asyncio.AbstractEventLoop, + mocker: MockerFixture, + create_server_mock: mock.AsyncMock, ) -> None: app = web.Application() @@ -642,15 +690,15 @@ def test_run_app_preexisting_inet_socket( printer = mock.Mock(wraps=stopper(patched_loop)) web.run_app(app, sock=sock, print=printer, loop=patched_loop) - patched_loop.create_server.assert_called_with( # type: ignore[attr-defined] - mock.ANY, sock=sock, backlog=128, ssl=None + create_server_mock.assert_called_with( + patched_loop, mock.ANY, sock=sock, backlog=128, ssl=None ) assert f"http://127.0.0.1:{port}" in printer.call_args[0][0] @pytest.mark.skipif(not HAS_IPV6, reason="IPv6 is not available") def test_run_app_preexisting_inet6_socket( - patched_loop: asyncio.AbstractEventLoop, + patched_loop: asyncio.AbstractEventLoop, create_server_mock: mock.AsyncMock ) -> None: app = web.Application() @@ -662,15 +710,18 @@ def test_run_app_preexisting_inet6_socket( printer = mock.Mock(wraps=stopper(patched_loop)) web.run_app(app, sock=sock, print=printer, loop=patched_loop) - patched_loop.create_server.assert_called_with( # type: ignore[attr-defined] - mock.ANY, sock=sock, backlog=128, ssl=None + create_server_mock.assert_called_with( + patched_loop, mock.ANY, sock=sock, backlog=128, ssl=None ) assert f"http://[::1]:{port}" in printer.call_args[0][0] @skip_if_no_unix_socks def test_run_app_preexisting_unix_socket( - patched_loop: asyncio.AbstractEventLoop, unix_sockname: str, mocker: MockerFixture + patched_loop: asyncio.AbstractEventLoop, + unix_sockname: str, + mocker: MockerFixture, + create_server_mock: mock.AsyncMock, ) -> None: app = web.Application() @@ -682,14 +733,14 @@ def test_run_app_preexisting_unix_socket( printer = mock.Mock(wraps=stopper(patched_loop)) web.run_app(app, sock=sock, print=printer, loop=patched_loop) - patched_loop.create_server.assert_called_with( # type: ignore[attr-defined] - mock.ANY, sock=sock, backlog=128, ssl=None + create_server_mock.assert_called_with( + patched_loop, mock.ANY, sock=sock, backlog=128, ssl=None ) assert f"http://unix:{unix_sockname}:" in printer.call_args[0][0] def test_run_app_multiple_preexisting_sockets( - patched_loop: asyncio.AbstractEventLoop, + patched_loop: asyncio.AbstractEventLoop, create_server_mock: mock.AsyncMock ) -> None: app = web.Application() @@ -704,10 +755,10 @@ def test_run_app_multiple_preexisting_sockets( printer = mock.Mock(wraps=stopper(patched_loop)) web.run_app(app, sock=(sock1, sock2), print=printer, loop=patched_loop) - patched_loop.create_server.assert_has_calls( # type: ignore[attr-defined] + create_server_mock.assert_has_calls( [ - mock.call(mock.ANY, sock=sock1, backlog=128, ssl=None), - mock.call(mock.ANY, sock=sock2, backlog=128, ssl=None), + mock.call(patched_loop, mock.ANY, sock=sock1, backlog=128, ssl=None), + mock.call(patched_loop, mock.ANY, sock=sock2, backlog=128, ssl=None), ] ) assert f"http://127.0.0.1:{port1}" in printer.call_args[0][0] @@ -753,9 +804,9 @@ def test_sigterm() -> None: def test_startup_cleanup_signals_even_on_failure( - patched_loop: asyncio.AbstractEventLoop, + patched_loop: asyncio.AbstractEventLoop, create_server_mock: mock.AsyncMock ) -> None: - patched_loop.create_server.side_effect = RuntimeError() # type: ignore[attr-defined] + create_server_mock.side_effect = RuntimeError() app = web.Application() startup_handler = mock.AsyncMock() @@ -770,7 +821,9 @@ def test_startup_cleanup_signals_even_on_failure( cleanup_handler.assert_called_once_with(app) -def test_run_app_coro(patched_loop: asyncio.AbstractEventLoop) -> None: +def test_run_app_coro( + patched_loop: asyncio.AbstractEventLoop, create_server_mock: mock.AsyncMock +) -> None: startup_handler = cleanup_handler = None async def make_app() -> web.Application: @@ -784,8 +837,8 @@ async def make_app() -> web.Application: web.run_app(make_app(), print=stopper(patched_loop), loop=patched_loop) - patched_loop.create_server.assert_called_with( # type: ignore[attr-defined] - mock.ANY, None, 8080, ssl=None, backlog=128, reuse_address=None, reuse_port=None + create_server_mock.assert_called_with( + patched_loop, mock.ANY, None, 8080, ssl=None, backlog=128, reuse_address=None, reuse_port=None, ) assert startup_handler is not None assert cleanup_handler is not None @@ -911,9 +964,7 @@ async def on_startup(app: web.Application) -> None: assert task.cancelled() -def test_run_app_cancels_done_tasks( - patched_loop: asyncio.AbstractEventLoop, -) -> None: +def test_run_app_cancels_done_tasks(patched_loop: asyncio.AbstractEventLoop) -> None: app = web.Application() task = None @@ -932,9 +983,7 @@ async def on_startup(app: web.Application) -> None: assert task.done() -def test_run_app_cancels_failed_tasks( - patched_loop: asyncio.AbstractEventLoop, -) -> None: +def test_run_app_cancels_failed_tasks(patched_loop: asyncio.AbstractEventLoop) -> None: app = web.Application() task = None @@ -1031,9 +1080,7 @@ async def init() -> web.Application: assert count == 3 -def test_run_app_raises_exception( - patched_loop: asyncio.AbstractEventLoop, -) -> None: +def test_run_app_raises_exception(patched_loop: asyncio.AbstractEventLoop) -> None: async def context(app: web.Application) -> AsyncIterator[None]: raise RuntimeError("foo") yield # type: ignore[unreachable] # pragma: no cover diff --git a/tests/test_web_runner.py b/tests/test_web_runner.py index c4b7b19e8b7..0798b785647 100644 --- a/tests/test_web_runner.py +++ b/tests/test_web_runner.py @@ -9,6 +9,7 @@ import pytest from aiohttp import web +from aiohttp import web_runner as web_runner_module from aiohttp.abc import AbstractAccessLogger from aiohttp.test_utils import REUSE_ADDRESS from aiohttp.web_log import AccessLogger @@ -265,16 +266,16 @@ async def test_tcpsite_default_host(make_runner: _RunnerMaker) -> None: site = web.TCPSite(runner) assert site.name == "http://0.0.0.0:8080" - m = mock.create_autospec(asyncio.AbstractEventLoop, spec_set=True, instance=True) - m.create_server.return_value = mock.create_autospec(asyncio.Server, spec_set=True) - with mock.patch( - "asyncio.get_running_loop", autospec=True, spec_set=True, return_value=m - ): + create_server = mock.AsyncMock( + return_value=mock.create_autospec(asyncio.Server, spec_set=True) + ) + + with mock.patch.object(web_runner_module, "create_server", create_server): await site.start() - m.create_server.assert_called_once() - args, kwargs = m.create_server.call_args - assert args == (runner.server, None, 8080) + create_server.assert_called_once() + args, kwargs = create_server.call_args + assert args == (asyncio.get_running_loop(), runner.server, None, 8080) async def test_tcpsite_empty_str_host(make_runner: _RunnerMaker) -> None: diff --git a/tests/test_web_sendfile_functional.py b/tests/test_web_sendfile_functional.py index e4daf828fcd..7ecb2899c7e 100644 --- a/tests/test_web_sendfile_functional.py +++ b/tests/test_web_sendfile_functional.py @@ -14,6 +14,7 @@ import aiohttp from aiohttp import web +from aiohttp import web_fileresponse as web_fileresponse_module from aiohttp.compression_utils import ZLibBackend from aiohttp.typedefs import PathLike from aiohttp.web_fileresponse import NOSENDFILE @@ -74,14 +75,13 @@ async def sender(request: SubRequest) -> AsyncIterator[_Sender]: def maker(path: PathLike, chunk_size: int = 256 * 1024) -> web.FileResponse: ret = web.FileResponse(path, chunk_size=chunk_size) - rloop = asyncio.get_running_loop() - is_patched = rloop.sendfile is sendfile_mock + is_patched = web_fileresponse_module.sendfile is sendfile_mock assert is_patched if request.param == "no_sendfile" else not is_patched return ret if request.param == "no_sendfile": with mock.patch.object( - asyncio.get_running_loop(), + web_fileresponse_module, "sendfile", autospec=True, spec_set=True, From eaa4be2e2e2b41fd1ae499d7ae0f7aad21155a31 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 6 Jun 2026 05:57:10 +0000 Subject: [PATCH 02/25] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- aiohttp/connector.py | 8 ++-- aiohttp/web_fileresponse.py | 6 +-- aiohttp/web_runner.py | 2 +- tests/test_proxy.py | 5 ++- tests/test_run_app.py | 59 +++++++++++++++++++++++---- tests/test_web_runner.py | 3 +- tests/test_web_sendfile_functional.py | 3 +- 7 files changed, 64 insertions(+), 22 deletions(-) diff --git a/aiohttp/connector.py b/aiohttp/connector.py index ddad3930eb1..79a1dd3dd7f 100644 --- a/aiohttp/connector.py +++ b/aiohttp/connector.py @@ -105,10 +105,12 @@ async def create_connection( - loop: asyncio.AbstractEventLoop, *args: Any, **kwargs: Any, + loop: asyncio.AbstractEventLoop, + *args: Any, + **kwargs: Any, ) -> tuple[asyncio.Transport, ResponseHandler]: if aiofastnet is not None: - return await aiofastnet.create_connection(loop, *args, **kwargs) # type: ignore[no-any-return] + return await aiofastnet.create_connection(loop, *args, **kwargs) # type: ignore[no-any-return] else: return await loop.create_connection(*args, **kwargs) @@ -117,7 +119,7 @@ async def start_tls( loop: asyncio.AbstractEventLoop, *args: Any, **kwargs: Any ) -> asyncio.BaseTransport | None: if aiofastnet is not None: - return await aiofastnet.start_tls(loop, *args, **kwargs) # type: ignore[no-any-return] + return await aiofastnet.start_tls(loop, *args, **kwargs) # type: ignore[no-any-return] else: return await loop.start_tls(*args, **kwargs) diff --git a/aiohttp/web_fileresponse.py b/aiohttp/web_fileresponse.py index 655e953bcd7..6bcdf5f8e2e 100644 --- a/aiohttp/web_fileresponse.py +++ b/aiohttp/web_fileresponse.py @@ -42,11 +42,9 @@ _T_OnChunkSent = Optional[Callable[[bytes], Awaitable[None]]] -async def sendfile( - loop: asyncio.AbstractEventLoop, *args: Any, **kwargs: Any -) -> int: +async def sendfile(loop: asyncio.AbstractEventLoop, *args: Any, **kwargs: Any) -> int: if aiofastnet is not None: - return await aiofastnet.sendfile(loop, *args, **kwargs) # type: ignore[no-any-return] + return await aiofastnet.sendfile(loop, *args, **kwargs) # type: ignore[no-any-return] else: return await loop.sendfile(*args, **kwargs) diff --git a/aiohttp/web_runner.py b/aiohttp/web_runner.py index db4dcbca58c..be6737f730c 100644 --- a/aiohttp/web_runner.py +++ b/aiohttp/web_runner.py @@ -33,7 +33,7 @@ async def create_server( loop: asyncio.AbstractEventLoop, *args: Any, **kwargs: Any ) -> asyncio.Server: if aiofastnet is not None: - return await aiofastnet.create_server(loop, *args, **kwargs) # type: ignore[no-any-return] + return await aiofastnet.create_server(loop, *args, **kwargs) # type: ignore[no-any-return] else: return await loop.create_server(*args, **kwargs) diff --git a/tests/test_proxy.py b/tests/test_proxy.py index ad47a608115..71e25aef48b 100644 --- a/tests/test_proxy.py +++ b/tests/test_proxy.py @@ -940,7 +940,10 @@ async def test_request_port( # type: ignore[misc] tr.get_extra_info.return_value = None # Called on connection to http://proxy.example.com with mock.patch.object( - connector_module, "create_connection", autospec=True, return_value=(tr, proto) + connector_module, + "create_connection", + autospec=True, + return_value=(tr, proto), ): req = make_client_request( "GET", diff --git a/tests/test_run_app.py b/tests/test_run_app.py index e8acc2ccae9..f62f15917c9 100644 --- a/tests/test_run_app.py +++ b/tests/test_run_app.py @@ -25,8 +25,8 @@ ServerDisconnectedError, WSCloseCode, web, + web_runner as web_runner_module, ) -from aiohttp import web_runner as web_runner_module from aiohttp.log import access_logger from aiohttp.web_protocol import RequestHandler from aiohttp.web_runner import BaseRunner @@ -115,7 +115,9 @@ def f(*args: object) -> None: return f -def test_run_app_http(patched_loop: asyncio.AbstractEventLoop, create_server_mock: mock.AsyncMock) -> None: +def test_run_app_http( + patched_loop: asyncio.AbstractEventLoop, create_server_mock: mock.AsyncMock +) -> None: app = web.Application() startup_handler = mock.AsyncMock() app.on_startup.append(startup_handler) @@ -125,7 +127,14 @@ def test_run_app_http(patched_loop: asyncio.AbstractEventLoop, create_server_moc web.run_app(app, print=stopper(patched_loop), loop=patched_loop) create_server_mock.assert_called_with( - patched_loop, mock.ANY, None, 8080, ssl=None, backlog=128, reuse_address=None, reuse_port=None, + patched_loop, + 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) @@ -138,7 +147,14 @@ def test_run_app_close_loop( web.run_app(app, print=stopper(patched_loop), loop=patched_loop) create_server_mock.assert_called_with( - patched_loop, mock.ANY, None, 8080, ssl=None, backlog=128, reuse_address=None, reuse_port=None, + patched_loop, + mock.ANY, + None, + 8080, + ssl=None, + backlog=128, + reuse_address=None, + reuse_port=None, ) assert patched_loop.is_closed() @@ -208,7 +224,14 @@ async def failing_ctx(_app: web.Application) -> AsyncIterator[None]: ] mock_server_default_8989 = [ mock.call( - mock.ANY, mock.ANY, None, 8989, ssl=None, backlog=128, reuse_address=None, reuse_port=None + mock.ANY, + mock.ANY, + None, + 8989, + ssl=None, + backlog=128, + reuse_address=None, + reuse_port=None, ) ] mock_socket = mock.Mock(getsockname=lambda: ("mock-socket", 123)) @@ -505,7 +528,9 @@ def test_run_app_mixed_bindings( # type: ignore[misc] assert create_server_mock.mock_calls == expected_server_calls -def test_run_app_https(patched_loop: asyncio.AbstractEventLoop, create_server_mock: mock.AsyncMock) -> None: +def test_run_app_https( + patched_loop: asyncio.AbstractEventLoop, create_server_mock: mock.AsyncMock +) -> None: app = web.Application() ssl_context = ssl.create_default_context() @@ -534,7 +559,9 @@ def test_run_app_nondefault_host_port( host = "127.0.0.1" app = web.Application() - web.run_app(app, host=host, port=port, print=stopper(patched_loop), loop=patched_loop) + web.run_app( + app, host=host, port=port, print=stopper(patched_loop), loop=patched_loop + ) create_server_mock.assert_called_with( patched_loop, @@ -598,7 +625,14 @@ def test_run_app_custom_backlog( web.run_app(app, backlog=10, print=stopper(patched_loop), loop=patched_loop) create_server_mock.assert_called_with( - patched_loop, mock.ANY, None, 8080, ssl=None, backlog=10, reuse_address=None, reuse_port=None, + patched_loop, + mock.ANY, + None, + 8080, + ssl=None, + backlog=10, + reuse_address=None, + reuse_port=None, ) @@ -838,7 +872,14 @@ async def make_app() -> web.Application: web.run_app(make_app(), print=stopper(patched_loop), loop=patched_loop) create_server_mock.assert_called_with( - patched_loop, mock.ANY, None, 8080, ssl=None, backlog=128, reuse_address=None, reuse_port=None, + patched_loop, + mock.ANY, + None, + 8080, + ssl=None, + backlog=128, + reuse_address=None, + reuse_port=None, ) assert startup_handler is not None assert cleanup_handler is not None diff --git a/tests/test_web_runner.py b/tests/test_web_runner.py index 0798b785647..c191c605890 100644 --- a/tests/test_web_runner.py +++ b/tests/test_web_runner.py @@ -8,8 +8,7 @@ import pytest -from aiohttp import web -from aiohttp import web_runner as web_runner_module +from aiohttp import web, web_runner as web_runner_module from aiohttp.abc import AbstractAccessLogger from aiohttp.test_utils import REUSE_ADDRESS from aiohttp.web_log import AccessLogger diff --git a/tests/test_web_sendfile_functional.py b/tests/test_web_sendfile_functional.py index 7ecb2899c7e..1304eb92b0c 100644 --- a/tests/test_web_sendfile_functional.py +++ b/tests/test_web_sendfile_functional.py @@ -13,8 +13,7 @@ from pytest_aiohttp import AiohttpClient, AiohttpServer import aiohttp -from aiohttp import web -from aiohttp import web_fileresponse as web_fileresponse_module +from aiohttp import web, web_fileresponse as web_fileresponse_module from aiohttp.compression_utils import ZLibBackend from aiohttp.typedefs import PathLike from aiohttp.web_fileresponse import NOSENDFILE From 2f9457eab446332ae3fdaebbe20c543df8d2ee99 Mon Sep 17 00:00:00 2001 From: taras Date: Sat, 6 Jun 2026 08:01:43 +0200 Subject: [PATCH 03/25] Remove unused imports, rename change file --- CHANGES/{12744.feature.rst => 12822.feature.rst} | 0 aiohttp/connector.py | 1 - aiohttp/web_fileresponse.py | 1 - aiohttp/web_runner.py | 1 - 4 files changed, 3 deletions(-) rename CHANGES/{12744.feature.rst => 12822.feature.rst} (100%) diff --git a/CHANGES/12744.feature.rst b/CHANGES/12822.feature.rst similarity index 100% rename from CHANGES/12744.feature.rst rename to CHANGES/12822.feature.rst diff --git a/aiohttp/connector.py b/aiohttp/connector.py index 79a1dd3dd7f..14f8139ca96 100644 --- a/aiohttp/connector.py +++ b/aiohttp/connector.py @@ -1,6 +1,5 @@ import asyncio import functools -import importlib import random import socket import sys diff --git a/aiohttp/web_fileresponse.py b/aiohttp/web_fileresponse.py index 6bcdf5f8e2e..54fcffe822e 100644 --- a/aiohttp/web_fileresponse.py +++ b/aiohttp/web_fileresponse.py @@ -1,5 +1,4 @@ import asyncio -import importlib import io import os import pathlib diff --git a/aiohttp/web_runner.py b/aiohttp/web_runner.py index be6737f730c..87a8abd168b 100644 --- a/aiohttp/web_runner.py +++ b/aiohttp/web_runner.py @@ -1,5 +1,4 @@ import asyncio -import importlib import signal import socket from abc import ABC, abstractmethod From 62e234f07c82d4581c4d6fd06f1468d681efdb7b Mon Sep 17 00:00:00 2001 From: taras Date: Sat, 6 Jun 2026 08:06:56 +0200 Subject: [PATCH 04/25] Fix flake8 complains --- aiohttp/connector.py | 14 +++++++------- aiohttp/web_fileresponse.py | 13 ++++++------- aiohttp/web_runner.py | 12 ++++++------ 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/aiohttp/connector.py b/aiohttp/connector.py index 14f8139ca96..0845f9b87b4 100644 --- a/aiohttp/connector.py +++ b/aiohttp/connector.py @@ -18,13 +18,6 @@ from aiohappyeyeballs import AddrInfoType, SocketFactoryType from multidict import CIMultiDict -aiofastnet: Any -try: - import aiofastnet -except ImportError: - aiofastnet = None - - from . import hdrs, helpers from .abc import AbstractResolver, ResolveResult from .client_exceptions import ( @@ -58,6 +51,13 @@ from .log import client_logger from .resolver import DefaultResolver +aiofastnet: Any +try: + import aiofastnet +except ImportError: + aiofastnet = None + + if sys.version_info >= (3, 12): from collections.abc import Buffer else: diff --git a/aiohttp/web_fileresponse.py b/aiohttp/web_fileresponse.py index 54fcffe822e..c32c67b8643 100644 --- a/aiohttp/web_fileresponse.py +++ b/aiohttp/web_fileresponse.py @@ -11,13 +11,6 @@ from types import MappingProxyType from typing import IO, TYPE_CHECKING, Any, Final, Optional -aiofastnet: Any -try: - import aiofastnet -except ImportError: - aiofastnet = None - - from . import hdrs from .abc import AbstractStreamWriter from .helpers import DEFAULT_CHUNK_SIZE, ETAG_ANY, ETag, must_be_empty_body @@ -37,6 +30,12 @@ if TYPE_CHECKING: from .web_request import BaseRequest +aiofastnet: Any +try: + import aiofastnet +except ImportError: + aiofastnet = None + _T_OnChunkSent = Optional[Callable[[bytes], Awaitable[None]]] diff --git a/aiohttp/web_runner.py b/aiohttp/web_runner.py index 87a8abd168b..09c4bf4a859 100644 --- a/aiohttp/web_runner.py +++ b/aiohttp/web_runner.py @@ -6,12 +6,6 @@ from yarl import URL -aiofastnet: Any -try: - import aiofastnet -except ImportError: - aiofastnet = None - from .abc import AbstractAccessLogger, AbstractStreamWriter from .http_parser import RawRequestMessage from .streams import StreamReader @@ -27,6 +21,12 @@ except ImportError: # pragma: no cover SSLContext = object # type: ignore[misc,assignment] +aiofastnet: Any +try: + import aiofastnet +except ImportError: + aiofastnet = None + async def create_server( loop: asyncio.AbstractEventLoop, *args: Any, **kwargs: Any From 655f953778d12e54cffc0e8f8327a52f73d0e4a3 Mon Sep 17 00:00:00 2001 From: taras Date: Sat, 6 Jun 2026 08:45:24 +0200 Subject: [PATCH 05/25] aiofastnet supports TLS over TLS always --- aiohttp/connector.py | 2 +- tests/test_proxy_functional.py | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/aiohttp/connector.py b/aiohttp/connector.py index 0845f9b87b4..f601d477f39 100644 --- a/aiohttp/connector.py +++ b/aiohttp/connector.py @@ -1321,7 +1321,7 @@ def _warn_about_tls_in_tls( underlying_transport, "_start_tls_compatible", False, - ) + ) or aiofastnet is not None if asyncio_supports_tls_in_tls: return diff --git a/tests/test_proxy_functional.py b/tests/test_proxy_functional.py index 761be8ed1de..4d6178a389c 100644 --- a/tests/test_proxy_functional.py +++ b/tests/test_proxy_functional.py @@ -7,7 +7,7 @@ from collections.abc import Awaitable, Callable, Iterator from contextlib import suppress from re import match as match_regex -from typing import TYPE_CHECKING, TypedDict +from typing import TYPE_CHECKING, TypedDict, Any from unittest import mock from uuid import uuid4 @@ -27,7 +27,14 @@ else: proxy = pytest.importorskip("proxy") -ASYNCIO_SUPPORTS_TLS_IN_TLS = sys.version_info >= (3, 11) +aiofastnet: Any +try: + import aiofastnet +except ImportError: + aiofastnet = None + + +AIOHTTP_SUPPORTS_TLS_IN_TLS = sys.version_info >= (3, 11) or aiofastnet is not None class _ResponseArgs(TypedDict): @@ -130,7 +137,7 @@ async def handler(request: web.Request) -> web.Response: @pytest.mark.skipif( - not ASYNCIO_SUPPORTS_TLS_IN_TLS, + not AIOHTTP_SUPPORTS_TLS_IN_TLS, reason="asyncio on this python does not support TLS in TLS", ) @pytest.mark.parametrize("web_server_endpoint_type", ("http", "https")) @@ -163,7 +170,7 @@ async def test_secure_https_proxy_absolute_path( @pytest.mark.parametrize("web_server_endpoint_type", ("https",)) @pytest.mark.skipif( - ASYNCIO_SUPPORTS_TLS_IN_TLS, reason="asyncio on this python supports TLS in TLS" + AIOHTTP_SUPPORTS_TLS_IN_TLS, reason="asyncio on this python supports TLS in TLS" ) @pytest.mark.filterwarnings(r"ignore:.*ssl.OP_NO_SSL*") # Filter out the warning from From 0a2a8d8edecd1f8a0db5baed020d51583ab3e86a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 6 Jun 2026 06:46:23 +0000 Subject: [PATCH 06/25] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- aiohttp/connector.py | 14 +++++++++----- tests/test_proxy_functional.py | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/aiohttp/connector.py b/aiohttp/connector.py index f601d477f39..6906a15e426 100644 --- a/aiohttp/connector.py +++ b/aiohttp/connector.py @@ -1317,11 +1317,15 @@ def _warn_about_tls_in_tls( return # Support in asyncio was added in Python 3.11 (bpo-44011) - asyncio_supports_tls_in_tls = sys.version_info >= (3, 11) or getattr( - underlying_transport, - "_start_tls_compatible", - False, - ) or aiofastnet is not None + asyncio_supports_tls_in_tls = ( + sys.version_info >= (3, 11) + or getattr( + underlying_transport, + "_start_tls_compatible", + False, + ) + or aiofastnet is not None + ) if asyncio_supports_tls_in_tls: return diff --git a/tests/test_proxy_functional.py b/tests/test_proxy_functional.py index 4d6178a389c..5ebb3c94127 100644 --- a/tests/test_proxy_functional.py +++ b/tests/test_proxy_functional.py @@ -7,7 +7,7 @@ from collections.abc import Awaitable, Callable, Iterator from contextlib import suppress from re import match as match_regex -from typing import TYPE_CHECKING, TypedDict, Any +from typing import TYPE_CHECKING, Any, TypedDict from unittest import mock from uuid import uuid4 From 4bb088736c871ce97a2712097c7fcf5c06f86deb Mon Sep 17 00:00:00 2001 From: taras Date: Sat, 6 Jun 2026 08:47:53 +0200 Subject: [PATCH 07/25] Fix flake errors --- tests/test_proxy_functional.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/test_proxy_functional.py b/tests/test_proxy_functional.py index 5ebb3c94127..8e424c0ae28 100644 --- a/tests/test_proxy_functional.py +++ b/tests/test_proxy_functional.py @@ -7,7 +7,7 @@ from collections.abc import Awaitable, Callable, Iterator from contextlib import suppress from re import match as match_regex -from typing import TYPE_CHECKING, Any, TypedDict +from typing import TYPE_CHECKING, TypedDict from unittest import mock from uuid import uuid4 @@ -27,15 +27,6 @@ else: proxy = pytest.importorskip("proxy") -aiofastnet: Any -try: - import aiofastnet -except ImportError: - aiofastnet = None - - -AIOHTTP_SUPPORTS_TLS_IN_TLS = sys.version_info >= (3, 11) or aiofastnet is not None - class _ResponseArgs(TypedDict): status: int @@ -72,6 +63,15 @@ async def get_request( return resp +aiofastnet: Any +try: + import aiofastnet +except ImportError: + aiofastnet = None + + +AIOHTTP_SUPPORTS_TLS_IN_TLS = sys.version_info >= (3, 11) or aiofastnet is not None + @pytest.fixture def secure_proxy_url(tls_certificate_pem_path: str) -> Iterator[URL]: """Return the URL of an instance of a running secure proxy. From 226ee8f42a68d540606fd61e6b0a12edf7efec84 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 6 Jun 2026 06:49:52 +0000 Subject: [PATCH 08/25] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_proxy_functional.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_proxy_functional.py b/tests/test_proxy_functional.py index 8e424c0ae28..2db7947f3b3 100644 --- a/tests/test_proxy_functional.py +++ b/tests/test_proxy_functional.py @@ -72,6 +72,7 @@ async def get_request( AIOHTTP_SUPPORTS_TLS_IN_TLS = sys.version_info >= (3, 11) or aiofastnet is not None + @pytest.fixture def secure_proxy_url(tls_certificate_pem_path: str) -> Iterator[URL]: """Return the URL of an instance of a running secure proxy. From 0069c223e7d1d75fd42d91fa18cea1f42f643b76 Mon Sep 17 00:00:00 2001 From: taras Date: Sat, 6 Jun 2026 08:55:57 +0200 Subject: [PATCH 09/25] Fix lint and mypy error --- tests/test_proxy_functional.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_proxy_functional.py b/tests/test_proxy_functional.py index 2db7947f3b3..de17306b272 100644 --- a/tests/test_proxy_functional.py +++ b/tests/test_proxy_functional.py @@ -7,7 +7,7 @@ from collections.abc import Awaitable, Callable, Iterator from contextlib import suppress from re import match as match_regex -from typing import TYPE_CHECKING, TypedDict +from typing import TYPE_CHECKING, TypedDict, Any from unittest import mock from uuid import uuid4 @@ -46,8 +46,6 @@ async def get_request( ) -> ClientResponse: ... else: - from typing import Any - async def get_request( method: str = "GET", *, From fb772b6ffc13b30007bee33c9d02305246a5cea8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 6 Jun 2026 06:57:06 +0000 Subject: [PATCH 10/25] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_proxy_functional.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_proxy_functional.py b/tests/test_proxy_functional.py index de17306b272..4086efec746 100644 --- a/tests/test_proxy_functional.py +++ b/tests/test_proxy_functional.py @@ -7,7 +7,7 @@ from collections.abc import Awaitable, Callable, Iterator from contextlib import suppress from re import match as match_regex -from typing import TYPE_CHECKING, TypedDict, Any +from typing import TYPE_CHECKING, Any, TypedDict from unittest import mock from uuid import uuid4 @@ -46,6 +46,7 @@ async def get_request( ) -> ClientResponse: ... else: + async def get_request( method: str = "GET", *, From 32ee277954f35d3a6628f1260c81b111b832a697 Mon Sep 17 00:00:00 2001 From: taras Date: Sat, 6 Jun 2026 09:16:28 +0200 Subject: [PATCH 11/25] Update FAQ --- docs/faq.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/faq.rst b/docs/faq.rst index b4276cf1843..9b28b54b659 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -282,6 +282,21 @@ part of the ``speedups`` extra. To enable KTLS, you have to do and check the following: +* Verify that ``aiofastnet`` is installed and can be imported. + + Currently, ``aiofastnet`` works only with CPython distributions that are + dynamically linked against OpenSSL. This is generally true for system Python + installations, Conda distributions, ``pyenv``, and + ``actions/setup-python`` in GitHub Actions, but not for Python installations + managed by ``uv``. + + .. code-block:: python + + try: + import aiofastnet + except ImportError: + aiofastnet = None + * Make sure the Linux ``tls`` kernel module is loaded:: sudo modprobe tls From 38dba9177bf19fdb436a55289971b51150e5addd Mon Sep 17 00:00:00 2001 From: taras Date: Sat, 6 Jun 2026 09:48:11 +0200 Subject: [PATCH 12/25] Revert changes in test_connector_multiple_event_loop --- tests/test_connector.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/tests/test_connector.py b/tests/test_connector.py index cbc1f561031..ddbfc38719f 100644 --- a/tests/test_connector.py +++ b/tests/test_connector.py @@ -4516,15 +4516,19 @@ async def allow_connection_and_add_dummy_waiter() -> None: def test_connector_multiple_event_loop(make_client_request: _RequestMaker) -> None: """Test the connector with multiple event loops.""" - async def create_connection(*args: object, **kwargs: object) -> NoReturn: - raise ssl.CertificateError - async def async_connect() -> Literal[True]: conn = aiohttp.TCPConnector() loop = asyncio.get_running_loop() req = make_client_request("GET", URL("https://127.0.0.1"), loop=loop) with suppress(aiohttp.ClientConnectorError): - await conn.connect(req, [], ClientTimeout()) + with mock.patch.object( + conn._loop, + "create_connection", + autospec=True, + spec_set=True, + side_effect=ssl.CertificateError, + ): + await conn.connect(req, [], ClientTimeout()) return True def test_connect() -> Literal[True]: @@ -4534,10 +4538,9 @@ def test_connect() -> Literal[True]: finally: loop.close() - with mock.patch.object(connector_module, "create_connection", create_connection): - with futures.ThreadPoolExecutor() as executor: - res_list = [executor.submit(test_connect) for _ in range(2)] - raw_response_list = [res.result() for res in futures.as_completed(res_list)] + with futures.ThreadPoolExecutor() as executor: + res_list = [executor.submit(test_connect) for _ in range(2)] + raw_response_list = [res.result() for res in futures.as_completed(res_list)] assert raw_response_list == [True, True] From b5d3521a178325d72ce3b72eb753c59c985601e9 Mon Sep 17 00:00:00 2001 From: taras Date: Sat, 6 Jun 2026 09:52:27 +0200 Subject: [PATCH 13/25] Add 'Conda' to spelling list --- docs/spelling_wordlist.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index 14b6da20607..66d8edb0f6a 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -81,6 +81,7 @@ codec Codings committer committers +Conda config Config configs From e4c899ea2ad597f765a631214e828cb14a96e373 Mon Sep 17 00:00:00 2001 From: taras Date: Sat, 6 Jun 2026 20:37:01 +0200 Subject: [PATCH 14/25] Revert name change --- tests/test_proxy_functional.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_proxy_functional.py b/tests/test_proxy_functional.py index 4086efec746..ef77ca8b7cf 100644 --- a/tests/test_proxy_functional.py +++ b/tests/test_proxy_functional.py @@ -69,7 +69,7 @@ async def get_request( aiofastnet = None -AIOHTTP_SUPPORTS_TLS_IN_TLS = sys.version_info >= (3, 11) or aiofastnet is not None +ASYNCIO_SUPPORTS_TLS_IN_TLS = sys.version_info >= (3, 11) or aiofastnet is not None @pytest.fixture @@ -137,7 +137,7 @@ async def handler(request: web.Request) -> web.Response: @pytest.mark.skipif( - not AIOHTTP_SUPPORTS_TLS_IN_TLS, + not ASYNCIO_SUPPORTS_TLS_IN_TLS, reason="asyncio on this python does not support TLS in TLS", ) @pytest.mark.parametrize("web_server_endpoint_type", ("http", "https")) @@ -170,7 +170,7 @@ async def test_secure_https_proxy_absolute_path( @pytest.mark.parametrize("web_server_endpoint_type", ("https",)) @pytest.mark.skipif( - AIOHTTP_SUPPORTS_TLS_IN_TLS, reason="asyncio on this python supports TLS in TLS" + ASYNCIO_SUPPORTS_TLS_IN_TLS, reason="asyncio on this python supports TLS in TLS" ) @pytest.mark.filterwarnings(r"ignore:.*ssl.OP_NO_SSL*") # Filter out the warning from From 63fd43f23f508f117c460faf45782faf8055d8cb Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Tue, 9 Jun 2026 00:00:21 +0100 Subject: [PATCH 15/25] Apply suggestions from code review Co-authored-by: Sam Bull --- aiohttp/connector.py | 5 ++--- aiohttp/web_fileresponse.py | 3 +-- aiohttp/web_runner.py | 3 +-- tests/test_proxy_functional.py | 1 - 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/aiohttp/connector.py b/aiohttp/connector.py index 1fd876fd88b..be385237b7b 100644 --- a/aiohttp/connector.py +++ b/aiohttp/connector.py @@ -53,7 +53,6 @@ from .log import client_logger from .resolver import DefaultResolver -aiofastnet: Any try: import aiofastnet except ImportError: @@ -111,7 +110,7 @@ async def create_connection( **kwargs: Any, ) -> tuple[asyncio.Transport, ResponseHandler]: if aiofastnet is not None: - return await aiofastnet.create_connection(loop, *args, **kwargs) # type: ignore[no-any-return] + return await aiofastnet.create_connection(loop, *args, **kwargs) else: return await loop.create_connection(*args, **kwargs) @@ -120,7 +119,7 @@ async def start_tls( loop: asyncio.AbstractEventLoop, *args: Any, **kwargs: Any ) -> asyncio.BaseTransport | None: if aiofastnet is not None: - return await aiofastnet.start_tls(loop, *args, **kwargs) # type: ignore[no-any-return] + return await aiofastnet.start_tls(loop, *args, **kwargs) else: return await loop.start_tls(*args, **kwargs) diff --git a/aiohttp/web_fileresponse.py b/aiohttp/web_fileresponse.py index c32c67b8643..d80bce202ee 100644 --- a/aiohttp/web_fileresponse.py +++ b/aiohttp/web_fileresponse.py @@ -30,7 +30,6 @@ if TYPE_CHECKING: from .web_request import BaseRequest -aiofastnet: Any try: import aiofastnet except ImportError: @@ -42,7 +41,7 @@ async def sendfile(loop: asyncio.AbstractEventLoop, *args: Any, **kwargs: Any) -> int: if aiofastnet is not None: - return await aiofastnet.sendfile(loop, *args, **kwargs) # type: ignore[no-any-return] + return await aiofastnet.sendfile(loop, *args, **kwargs) else: return await loop.sendfile(*args, **kwargs) diff --git a/aiohttp/web_runner.py b/aiohttp/web_runner.py index 09c4bf4a859..ce8f7665d71 100644 --- a/aiohttp/web_runner.py +++ b/aiohttp/web_runner.py @@ -21,7 +21,6 @@ except ImportError: # pragma: no cover SSLContext = object # type: ignore[misc,assignment] -aiofastnet: Any try: import aiofastnet except ImportError: @@ -32,7 +31,7 @@ async def create_server( loop: asyncio.AbstractEventLoop, *args: Any, **kwargs: Any ) -> asyncio.Server: if aiofastnet is not None: - return await aiofastnet.create_server(loop, *args, **kwargs) # type: ignore[no-any-return] + return await aiofastnet.create_server(loop, *args, **kwargs) else: return await loop.create_server(*args, **kwargs) diff --git a/tests/test_proxy_functional.py b/tests/test_proxy_functional.py index 40cf7b546e3..83e64edf0dc 100644 --- a/tests/test_proxy_functional.py +++ b/tests/test_proxy_functional.py @@ -63,7 +63,6 @@ async def get_request( return resp -aiofastnet: Any try: import aiofastnet except ImportError: From fcbc84fadb3b748113cdc15291a5e9cf62ac0745 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Tue, 9 Jun 2026 01:45:58 +0100 Subject: [PATCH 16/25] Apply suggestions from code review Co-authored-by: Sam Bull --- aiohttp/connector.py | 2 +- aiohttp/web_fileresponse.py | 2 +- aiohttp/web_runner.py | 2 +- tests/test_proxy_functional.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/aiohttp/connector.py b/aiohttp/connector.py index be385237b7b..812d480b8d3 100644 --- a/aiohttp/connector.py +++ b/aiohttp/connector.py @@ -56,7 +56,7 @@ try: import aiofastnet except ImportError: - aiofastnet = None + aiofastnet = None # type: ignore[assignment] if sys.version_info >= (3, 12): diff --git a/aiohttp/web_fileresponse.py b/aiohttp/web_fileresponse.py index d80bce202ee..6c227d624ee 100644 --- a/aiohttp/web_fileresponse.py +++ b/aiohttp/web_fileresponse.py @@ -33,7 +33,7 @@ try: import aiofastnet except ImportError: - aiofastnet = None + aiofastnet = None # type: ignore[assignment] _T_OnChunkSent = Optional[Callable[[bytes], Awaitable[None]]] diff --git a/aiohttp/web_runner.py b/aiohttp/web_runner.py index ce8f7665d71..3db153aa906 100644 --- a/aiohttp/web_runner.py +++ b/aiohttp/web_runner.py @@ -24,7 +24,7 @@ try: import aiofastnet except ImportError: - aiofastnet = None + aiofastnet = None # type: ignore[assignment] async def create_server( diff --git a/tests/test_proxy_functional.py b/tests/test_proxy_functional.py index 83e64edf0dc..68b3da63220 100644 --- a/tests/test_proxy_functional.py +++ b/tests/test_proxy_functional.py @@ -66,7 +66,7 @@ async def get_request( try: import aiofastnet except ImportError: - aiofastnet = None + aiofastnet = None # type: ignore[assignment] ASYNCIO_SUPPORTS_TLS_IN_TLS = sys.version_info >= (3, 11) or aiofastnet is not None From e558301098e9c68050a6598b706af21bd6376a87 Mon Sep 17 00:00:00 2001 From: taras Date: Wed, 10 Jun 2026 19:27:39 +0200 Subject: [PATCH 17/25] Update aiofastnet to 0.10.0 --- pyproject.toml | 2 +- requirements/runtime-deps.in | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1d96cf4206d..4cd95427af7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ dynamic = [ [project.optional-dependencies] speedups = [ "aiodns >= 3.3.0; sys_platform != 'android' and sys_platform != 'ios'", - "aiofastnet >= 0.9.0; platform_python_implementation == 'CPython' and (platform_machine == 'x86_64' or platform_machine == 'AMD64' or platform_machine == 'aarch64')", + "aiofastnet >= 0.10.0; platform_python_implementation == 'CPython' and (platform_machine == 'x86_64' or platform_machine == 'AMD64' or platform_machine == 'aarch64')", "Brotli >= 1.2; platform_python_implementation == 'CPython' and sys_platform != 'android' and sys_platform != 'ios'", "brotlicffi >= 1.2; platform_python_implementation != 'CPython'", "backports.zstd; platform_python_implementation == 'CPython' and python_version < '3.14' and sys_platform != 'android' and sys_platform != 'ios'", diff --git a/requirements/runtime-deps.in b/requirements/runtime-deps.in index 03974b7b217..48af9cc1552 100644 --- a/requirements/runtime-deps.in +++ b/requirements/runtime-deps.in @@ -1,7 +1,7 @@ # Extracted from `pyproject.toml` via `make sync-direct-runtime-deps` aiodns >= 3.3.0; sys_platform != 'android' and sys_platform != 'ios' -aiofastnet >= 0.9.0; platform_python_implementation == 'CPython' and (platform_machine == 'x86_64' or platform_machine == 'AMD64' or platform_machine == 'aarch64') +aiofastnet >= 0.10.0; platform_python_implementation == 'CPython' and (platform_machine == 'x86_64' or platform_machine == 'AMD64' or platform_machine == 'aarch64') aiohappyeyeballs >= 2.5.0 aiosignal >= 1.4.0 async-timeout >= 4.0, < 6.0 ; python_version < '3.11' From fe149b9972df22ce7dc245b8079913ee012c1502 Mon Sep 17 00:00:00 2001 From: taras Date: Thu, 11 Jun 2026 02:04:50 +0200 Subject: [PATCH 18/25] Upgrade to aiofastnet 0.11.0, fix sendfile return type --- aiohttp/web_fileresponse.py | 6 +++--- pyproject.toml | 2 +- requirements/lint.in | 2 +- requirements/runtime-deps.in | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/aiohttp/web_fileresponse.py b/aiohttp/web_fileresponse.py index 6c227d624ee..174c3e0d5d1 100644 --- a/aiohttp/web_fileresponse.py +++ b/aiohttp/web_fileresponse.py @@ -39,11 +39,11 @@ _T_OnChunkSent = Optional[Callable[[bytes], Awaitable[None]]] -async def sendfile(loop: asyncio.AbstractEventLoop, *args: Any, **kwargs: Any) -> int: +async def sendfile(loop: asyncio.AbstractEventLoop, *args: Any, **kwargs: Any) -> None: if aiofastnet is not None: - return await aiofastnet.sendfile(loop, *args, **kwargs) + await aiofastnet.sendfile(loop, *args, **kwargs) else: - return await loop.sendfile(*args, **kwargs) + await loop.sendfile(*args, **kwargs) NOSENDFILE: Final[bool] = bool(os.environ.get("AIOHTTP_NOSENDFILE")) diff --git a/pyproject.toml b/pyproject.toml index 4cd95427af7..7bf480ac1a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ dynamic = [ [project.optional-dependencies] speedups = [ "aiodns >= 3.3.0; sys_platform != 'android' and sys_platform != 'ios'", - "aiofastnet >= 0.10.0; platform_python_implementation == 'CPython' and (platform_machine == 'x86_64' or platform_machine == 'AMD64' or platform_machine == 'aarch64')", + "aiofastnet >= 0.11.0; platform_python_implementation == 'CPython' and (platform_machine == 'x86_64' or platform_machine == 'AMD64' or platform_machine == 'aarch64')", "Brotli >= 1.2; platform_python_implementation == 'CPython' and sys_platform != 'android' and sys_platform != 'ios'", "brotlicffi >= 1.2; platform_python_implementation != 'CPython'", "backports.zstd; platform_python_implementation == 'CPython' and python_version < '3.14' and sys_platform != 'android' and sys_platform != 'ios'", diff --git a/requirements/lint.in b/requirements/lint.in index e5c3f7f4533..dc88ce01512 100644 --- a/requirements/lint.in +++ b/requirements/lint.in @@ -1,5 +1,5 @@ aiodns -aiofastnet +aiofastnet >= 0.11.0 backports.zstd; implementation_name == "cpython" and python_version < "3.14" blockbuster freezegun diff --git a/requirements/runtime-deps.in b/requirements/runtime-deps.in index 48af9cc1552..ec66d1e8f1b 100644 --- a/requirements/runtime-deps.in +++ b/requirements/runtime-deps.in @@ -1,7 +1,7 @@ # Extracted from `pyproject.toml` via `make sync-direct-runtime-deps` aiodns >= 3.3.0; sys_platform != 'android' and sys_platform != 'ios' -aiofastnet >= 0.10.0; platform_python_implementation == 'CPython' and (platform_machine == 'x86_64' or platform_machine == 'AMD64' or platform_machine == 'aarch64') +aiofastnet >= 0.11.0; platform_python_implementation == 'CPython' and (platform_machine == 'x86_64' or platform_machine == 'AMD64' or platform_machine == 'aarch64') aiohappyeyeballs >= 2.5.0 aiosignal >= 1.4.0 async-timeout >= 4.0, < 6.0 ; python_version < '3.11' From 780bdd1b80cd62a6c0b3f6c40b11e1eea0768c97 Mon Sep 17 00:00:00 2001 From: taras Date: Thu, 11 Jun 2026 02:17:53 +0200 Subject: [PATCH 19/25] Fix unreachable mypy error --- aiohttp/connector.py | 4 ++-- aiohttp/web_fileresponse.py | 2 +- aiohttp/web_runner.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/aiohttp/connector.py b/aiohttp/connector.py index 812d480b8d3..8740402d830 100644 --- a/aiohttp/connector.py +++ b/aiohttp/connector.py @@ -112,7 +112,7 @@ async def create_connection( if aiofastnet is not None: return await aiofastnet.create_connection(loop, *args, **kwargs) else: - return await loop.create_connection(*args, **kwargs) + return await loop.create_connection(*args, **kwargs) # type: ignore[unreachable] async def start_tls( @@ -121,7 +121,7 @@ async def start_tls( if aiofastnet is not None: return await aiofastnet.start_tls(loop, *args, **kwargs) else: - return await loop.start_tls(*args, **kwargs) + return await loop.start_tls(*args, **kwargs) # type: ignore[unreachable] class Connection: diff --git a/aiohttp/web_fileresponse.py b/aiohttp/web_fileresponse.py index 174c3e0d5d1..09065c2f071 100644 --- a/aiohttp/web_fileresponse.py +++ b/aiohttp/web_fileresponse.py @@ -43,7 +43,7 @@ async def sendfile(loop: asyncio.AbstractEventLoop, *args: Any, **kwargs: Any) - if aiofastnet is not None: await aiofastnet.sendfile(loop, *args, **kwargs) else: - await loop.sendfile(*args, **kwargs) + await loop.sendfile(*args, **kwargs) # type: ignore[unreachable] NOSENDFILE: Final[bool] = bool(os.environ.get("AIOHTTP_NOSENDFILE")) diff --git a/aiohttp/web_runner.py b/aiohttp/web_runner.py index 3db153aa906..98785610966 100644 --- a/aiohttp/web_runner.py +++ b/aiohttp/web_runner.py @@ -33,7 +33,7 @@ async def create_server( if aiofastnet is not None: return await aiofastnet.create_server(loop, *args, **kwargs) else: - return await loop.create_server(*args, **kwargs) + return await loop.create_server(*args, **kwargs) # type: ignore[unreachable] __all__ = ( From db527939f1f6212c8d19b8aa10047e431a3d413d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2026 00:18:52 +0000 Subject: [PATCH 20/25] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- aiohttp/connector.py | 4 ++-- aiohttp/web_runner.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/aiohttp/connector.py b/aiohttp/connector.py index 8740402d830..f227acc0f11 100644 --- a/aiohttp/connector.py +++ b/aiohttp/connector.py @@ -112,7 +112,7 @@ async def create_connection( if aiofastnet is not None: return await aiofastnet.create_connection(loop, *args, **kwargs) else: - return await loop.create_connection(*args, **kwargs) # type: ignore[unreachable] + return await loop.create_connection(*args, **kwargs) # type: ignore[unreachable] async def start_tls( @@ -121,7 +121,7 @@ async def start_tls( if aiofastnet is not None: return await aiofastnet.start_tls(loop, *args, **kwargs) else: - return await loop.start_tls(*args, **kwargs) # type: ignore[unreachable] + return await loop.start_tls(*args, **kwargs) # type: ignore[unreachable] class Connection: diff --git a/aiohttp/web_runner.py b/aiohttp/web_runner.py index 98785610966..bbf5918aa85 100644 --- a/aiohttp/web_runner.py +++ b/aiohttp/web_runner.py @@ -33,7 +33,7 @@ async def create_server( if aiofastnet is not None: return await aiofastnet.create_server(loop, *args, **kwargs) else: - return await loop.create_server(*args, **kwargs) # type: ignore[unreachable] + return await loop.create_server(*args, **kwargs) # type: ignore[unreachable] __all__ = ( From 0a92e74862f286c2a2470aa8821ccdf7702e13f1 Mon Sep 17 00:00:00 2001 From: taras Date: Thu, 11 Jun 2026 03:15:11 +0200 Subject: [PATCH 21/25] Add type info for sendfile arguments --- aiohttp/web_fileresponse.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/aiohttp/web_fileresponse.py b/aiohttp/web_fileresponse.py index 09065c2f071..502a882c752 100644 --- a/aiohttp/web_fileresponse.py +++ b/aiohttp/web_fileresponse.py @@ -9,7 +9,7 @@ from mimetypes import MimeTypes from stat import S_ISREG from types import MappingProxyType -from typing import IO, TYPE_CHECKING, Any, Final, Optional +from typing import TYPE_CHECKING, BinaryIO, Final, Optional from . import hdrs from .abc import AbstractStreamWriter @@ -39,11 +39,19 @@ _T_OnChunkSent = Optional[Callable[[bytes], Awaitable[None]]] -async def sendfile(loop: asyncio.AbstractEventLoop, *args: Any, **kwargs: Any) -> None: +async def sendfile( + loop: asyncio.AbstractEventLoop, + transport: asyncio.Transport, + file: BinaryIO, + offset: int, + count: int, +) -> None: if aiofastnet is not None: - await aiofastnet.sendfile(loop, *args, **kwargs) + await aiofastnet.sendfile(loop, transport, file, offset, count) else: - await loop.sendfile(*args, **kwargs) # type: ignore[unreachable] + await loop.sendfile( # type: ignore[unreachable] + transport, file, offset, count + ) NOSENDFILE: Final[bool] = bool(os.environ.get("AIOHTTP_NOSENDFILE")) @@ -104,12 +112,12 @@ def __init__( self._path = pathlib.Path(path) self._chunk_size = chunk_size - def _seek_and_read(self, fobj: IO[Any], offset: int, chunk_size: int) -> bytes: + def _seek_and_read(self, fobj: BinaryIO, offset: int, chunk_size: int) -> bytes: fobj.seek(offset) - return fobj.read(chunk_size) # type: ignore[no-any-return] + return fobj.read(chunk_size) async def _sendfile_fallback( - self, writer: AbstractStreamWriter, fobj: IO[Any], offset: int, count: int + self, writer: AbstractStreamWriter, fobj: BinaryIO, offset: int, count: int ) -> AbstractStreamWriter: # To keep memory usage low,fobj is transferred in chunks # controlled by the constructor's chunk_size argument. @@ -130,7 +138,7 @@ async def _sendfile_fallback( return writer async def _sendfile( - self, request: "BaseRequest", fobj: IO[Any], offset: int, count: int + self, request: "BaseRequest", fobj: BinaryIO, offset: int, count: int ) -> AbstractStreamWriter: writer = await super().prepare(request) assert writer is not None From dc21a31ad7dbeceb8389de0e477a18f891788abf Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2026 01:16:33 +0000 Subject: [PATCH 22/25] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- aiohttp/web_fileresponse.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/aiohttp/web_fileresponse.py b/aiohttp/web_fileresponse.py index 502a882c752..fab821caf94 100644 --- a/aiohttp/web_fileresponse.py +++ b/aiohttp/web_fileresponse.py @@ -49,9 +49,7 @@ async def sendfile( if aiofastnet is not None: await aiofastnet.sendfile(loop, transport, file, offset, count) else: - await loop.sendfile( # type: ignore[unreachable] - transport, file, offset, count - ) + await loop.sendfile(transport, file, offset, count) # type: ignore[unreachable] NOSENDFILE: Final[bool] = bool(os.environ.get("AIOHTTP_NOSENDFILE")) From 4960d64be2c3ee037f52a386adc37d4bdb62c583 Mon Sep 17 00:00:00 2001 From: taras Date: Thu, 11 Jun 2026 03:33:37 +0200 Subject: [PATCH 23/25] Add type info for create_connection wrapper --- aiohttp/connector.py | 70 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 63 insertions(+), 7 deletions(-) diff --git a/aiohttp/connector.py b/aiohttp/connector.py index f227acc0f11..73811ec3e1b 100644 --- a/aiohttp/connector.py +++ b/aiohttp/connector.py @@ -106,22 +106,78 @@ async def create_connection( loop: asyncio.AbstractEventLoop, - *args: Any, - **kwargs: Any, + protocol_factory: Callable[[], ResponseHandler], + *, + ssl: SSLContext | None, + sock: socket.socket, + server_hostname: str | None, + ssl_shutdown_timeout: float | None = None, ) -> tuple[asyncio.Transport, ResponseHandler]: if aiofastnet is not None: - return await aiofastnet.create_connection(loop, *args, **kwargs) + return await aiofastnet.create_connection( + loop, + protocol_factory, + ssl=ssl, + sock=sock, + server_hostname=server_hostname, + ssl_shutdown_timeout=ssl_shutdown_timeout, + ) else: - return await loop.create_connection(*args, **kwargs) # type: ignore[unreachable] + if sys.version_info >= (3, 11): # type: ignore[unreachable] + return await loop.create_connection( + protocol_factory, + ssl=ssl, + sock=sock, + server_hostname=server_hostname, + ssl_shutdown_timeout=ssl_shutdown_timeout, + ) + else: + return await loop.create_connection( + protocol_factory, + ssl=ssl, + sock=sock, + server_hostname=server_hostname, + ) async def start_tls( - loop: asyncio.AbstractEventLoop, *args: Any, **kwargs: Any + loop: asyncio.AbstractEventLoop, + transport: asyncio.Transport, + protocol: ResponseHandler, + sslcontext: SSLContext, + *, + server_hostname: str | None, + ssl_handshake_timeout: float | None, + ssl_shutdown_timeout: float | None = None, ) -> asyncio.BaseTransport | None: if aiofastnet is not None: - return await aiofastnet.start_tls(loop, *args, **kwargs) + return await aiofastnet.start_tls( + loop, + transport, + protocol, + sslcontext, + server_hostname=server_hostname, + ssl_handshake_timeout=ssl_handshake_timeout, + ssl_shutdown_timeout=ssl_shutdown_timeout, + ) else: - return await loop.start_tls(*args, **kwargs) # type: ignore[unreachable] + if sys.version_info >= (3, 11): # type: ignore[unreachable] + return await loop.start_tls( + transport, + protocol, + sslcontext, + server_hostname=server_hostname, + ssl_handshake_timeout=ssl_handshake_timeout, + ssl_shutdown_timeout=ssl_shutdown_timeout, + ) + else: + return await loop.start_tls( + transport, + protocol, + sslcontext, + server_hostname=server_hostname, + ssl_handshake_timeout=ssl_handshake_timeout, + ) class Connection: From f60851a40019a57a7aece179aa3e27b38fa01ce0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2026 01:35:16 +0000 Subject: [PATCH 24/25] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- aiohttp/connector.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aiohttp/connector.py b/aiohttp/connector.py index 73811ec3e1b..89f80c709da 100644 --- a/aiohttp/connector.py +++ b/aiohttp/connector.py @@ -123,7 +123,7 @@ async def create_connection( ssl_shutdown_timeout=ssl_shutdown_timeout, ) else: - if sys.version_info >= (3, 11): # type: ignore[unreachable] + if sys.version_info >= (3, 11): # type: ignore[unreachable] return await loop.create_connection( protocol_factory, ssl=ssl, @@ -161,7 +161,7 @@ async def start_tls( ssl_shutdown_timeout=ssl_shutdown_timeout, ) else: - if sys.version_info >= (3, 11): # type: ignore[unreachable] + if sys.version_info >= (3, 11): # type: ignore[unreachable] return await loop.start_tls( transport, protocol, From 86e6d7861ce296de532864178cc883ac1cd0795d Mon Sep 17 00:00:00 2001 From: taras Date: Thu, 11 Jun 2026 03:41:01 +0200 Subject: [PATCH 25/25] Add type info for create_server --- aiohttp/web_runner.py | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/aiohttp/web_runner.py b/aiohttp/web_runner.py index bbf5918aa85..3c5feaf034d 100644 --- a/aiohttp/web_runner.py +++ b/aiohttp/web_runner.py @@ -2,6 +2,7 @@ import signal import socket from abc import ABC, abstractmethod +from collections.abc import Callable from typing import Any, Generic, TypeVar from yarl import URL @@ -28,12 +29,40 @@ async def create_server( - loop: asyncio.AbstractEventLoop, *args: Any, **kwargs: Any + loop: asyncio.AbstractEventLoop, + protocol_factory: Callable[[], asyncio.Protocol], + host: str | None = None, + port: int | None = None, + *, + sock: socket.socket | None = None, + ssl: SSLContext | None = None, + backlog: int = 100, + reuse_address: bool | None = None, + reuse_port: bool | None = None, ) -> asyncio.Server: if aiofastnet is not None: - return await aiofastnet.create_server(loop, *args, **kwargs) + return await aiofastnet.create_server( + loop, + protocol_factory, + host, + port, + sock=sock, + ssl=ssl, + backlog=backlog, + reuse_address=reuse_address, + reuse_port=reuse_port, + ) else: - return await loop.create_server(*args, **kwargs) # type: ignore[unreachable] + return await loop.create_server( # type: ignore[unreachable] + protocol_factory, + host, + port, + sock=sock, + ssl=ssl, + backlog=backlog, + reuse_address=reuse_address, + reuse_port=reuse_port, + ) __all__ = (