From c67fd8f4d674dacfe5d4209c1a5428fdb53b4e39 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Feb 2025 14:15:14 +0000 Subject: [PATCH 1/5] Bump mypy from 1.14.1 to 1.15.0 (#10384) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [mypy](https://github.com/python/mypy) from 1.14.1 to 1.15.0.
Changelog

Sourced from mypy's changelog.

Mypy Release Notes

Next Release

...

Mypy 1.15

We’ve just uploaded mypy 1.15 to the Python Package Index (PyPI). Mypy is a static type checker for Python. This release includes new features, performance improvements and bug fixes. You can install it as follows:

python3 -m pip install -U mypy

You can read the full documentation for this release on Read the Docs.

Performance Improvements

Mypy is up to 40% faster in some use cases. This improvement comes largely from tuning the performance of the garbage collector. Additionally, the release includes several micro-optimizations that may be impactful for large projects.

Contributed by Jukka Lehtosalo

Mypyc Accelerated Mypy Wheels for ARM Linux

For best performance, mypy can be compiled to C extension modules using mypyc. This makes mypy 3-5x faster than when interpreted with pure Python. We now build and upload mypyc accelerated mypy wheels for manylinux_aarch64 to PyPI, making it easy for Linux users on ARM platforms to realise this speedup -- just pip install the latest mypy.

Contributed by Christian Bundy and Marc Mueller (PR mypy_mypyc-wheels#76, PR mypy_mypyc-wheels#89).

--strict-bytes

By default, mypy treats bytearray and memoryview values as assignable to the bytes type, for historical reasons. Use the --strict-bytes flag to disable this behavior. PEP 688 specified the removal of this special case. The flag will be enabled by default in mypy 2.0.

Contributed by Ali Hamdan (PR 18263) and Shantanu Jain (PR 13952).

Improvements to Reachability Analysis and Partial Type Handling in Loops

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=mypy&package-manager=pip&previous-version=1.14.1&new-version=1.15.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sam Bull --- requirements/constraints.txt | 2 +- requirements/dev.txt | 2 +- requirements/lint.txt | 2 +- requirements/test.txt | 2 +- tests/test_client_functional.py | 14 -------------- 5 files changed, 4 insertions(+), 18 deletions(-) diff --git a/requirements/constraints.txt b/requirements/constraints.txt index 9bb9eb0b62c..37ad31c4391 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -108,7 +108,7 @@ multidict==6.1.0 # -r requirements/multidict.in # -r requirements/runtime-deps.in # yarl -mypy==1.14.1 ; implementation_name == "cpython" +mypy==1.15.0 ; implementation_name == "cpython" # via # -r requirements/lint.in # -r requirements/test.in diff --git a/requirements/dev.txt b/requirements/dev.txt index 5250c375662..1af9a93415a 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -105,7 +105,7 @@ multidict==6.1.0 # via # -r requirements/runtime-deps.in # yarl -mypy==1.14.1 ; implementation_name == "cpython" +mypy==1.15.0 ; implementation_name == "cpython" # via # -r requirements/lint.in # -r requirements/test.in diff --git a/requirements/lint.txt b/requirements/lint.txt index 30c88c5761e..20c345649ca 100644 --- a/requirements/lint.txt +++ b/requirements/lint.txt @@ -39,7 +39,7 @@ markdown-it-py==3.0.0 # via rich mdurl==0.1.2 # via markdown-it-py -mypy==1.14.1 ; implementation_name == "cpython" +mypy==1.15.0 ; implementation_name == "cpython" # via -r requirements/lint.in mypy-extensions==1.0.0 # via mypy diff --git a/requirements/test.txt b/requirements/test.txt index 42b35e8d8df..93ab6812dd8 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -55,7 +55,7 @@ multidict==6.1.0 # via # -r requirements/runtime-deps.in # yarl -mypy==1.14.1 ; implementation_name == "cpython" +mypy==1.15.0 ; implementation_name == "cpython" # via -r requirements/test.in mypy-extensions==1.0.0 # via mypy diff --git a/tests/test_client_functional.py b/tests/test_client_functional.py index 6c8bbca634f..414ee1dd51b 100644 --- a/tests/test_client_functional.py +++ b/tests/test_client_functional.py @@ -3477,20 +3477,6 @@ async def handler(request: web.Request) -> web.Response: assert resp.status == 200 -async def test_close_context_manager(aiohttp_client: AiohttpClient) -> None: - # a test for backward compatibility with yield from syntax - async def handler(request: web.Request) -> NoReturn: - assert False - - app = web.Application() - app.router.add_get("/", handler) - - client = await aiohttp_client(app) - ctx = client.get("/") - ctx.close() - assert not ctx._coro.cr_running - - async def test_session_auth(aiohttp_client: AiohttpClient) -> None: async def handler(request: web.Request) -> web.Response: return web.json_response({"headers": dict(request.headers)}) From 0d44b6acace64f73045c14869af9069dc801926e Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Thu, 6 Feb 2025 15:12:11 +0000 Subject: [PATCH 2/5] Revert incorrect typing changes (#10222) --- .github/workflows/ci-cd.yml | 2 +- .mypy.ini | 3 ++- aiohttp/http_parser.py | 1 - aiohttp/web_protocol.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 9d2b8b9f27f..61fb230371c 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -49,7 +49,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v5 with: - python-version: 3.13 + python-version: 3.11 - name: Cache PyPI uses: actions/cache@v4.2.0 with: diff --git a/.mypy.ini b/.mypy.ini index 4a1333c4704..54efdae5bad 100644 --- a/.mypy.ini +++ b/.mypy.ini @@ -11,8 +11,9 @@ disallow_untyped_calls = True disallow_untyped_decorators = True disallow_untyped_defs = True # TODO(PY312): explicit-override -enable_error_code = ignore-without-code, possibly-undefined, redundant-expr, redundant-self, truthy-bool, truthy-iterable, unused-awaitable +enable_error_code = deprecated, ignore-without-code, possibly-undefined, redundant-expr, redundant-self, truthy-bool, truthy-iterable, unused-awaitable extra_checks = True +follow_untyped_imports = True implicit_reexport = False no_implicit_optional = True pretty = True diff --git a/aiohttp/http_parser.py b/aiohttp/http_parser.py index d44f4cb12f1..bb78aea2fcd 100644 --- a/aiohttp/http_parser.py +++ b/aiohttp/http_parser.py @@ -670,7 +670,6 @@ def feed_data( ) -> Tuple[List[Tuple[RawResponseMessage, StreamReader]], bool, bytes]: if SEP is None: SEP = b"\r\n" if DEBUG else b"\n" - assert SEP is not None return super().feed_data(data, SEP, *args, **kwargs) def parse_message(self, lines: List[bytes]) -> RawResponseMessage: diff --git a/aiohttp/web_protocol.py b/aiohttp/web_protocol.py index 613ef91d65e..4dbda424bd2 100644 --- a/aiohttp/web_protocol.py +++ b/aiohttp/web_protocol.py @@ -583,13 +583,13 @@ async def start(self) -> None: else: task = loop.create_task(coro) try: - resp, reset = await task # type: ignore[possibly-undefined] + resp, reset = await task except ConnectionError: self.log_debug("Ignored premature client disconnection") break # Drop the processed task from asyncio.Task.all_tasks() early - del task # type: ignore[possibly-undefined] + del task # https://github.com/python/mypy/issues/14309 if reset: # type: ignore[possibly-undefined] self.log_debug("Ignored premature client disconnection 2") From fae142f59841d64eb57bed189397f9baca0c44a6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 6 Feb 2025 10:07:19 -0600 Subject: [PATCH 3/5] Add benchmark for streaming API iter_chunks (#10426) --- tests/test_benchmarks_client.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/tests/test_benchmarks_client.py b/tests/test_benchmarks_client.py index 61439183334..ac3131e9750 100644 --- a/tests/test_benchmarks_client.py +++ b/tests/test_benchmarks_client.py @@ -124,7 +124,7 @@ def test_one_hundred_get_requests_with_512kib_chunked_payload( aiohttp_client: AiohttpClient, benchmark: BenchmarkFixture, ) -> None: - """Benchmark 100 GET requests with a payload of 512KiB.""" + """Benchmark 100 GET requests with a payload of 512KiB using read.""" message_count = 100 payload = b"a" * (2**19) @@ -148,6 +148,36 @@ def _run() -> None: loop.run_until_complete(run_client_benchmark()) +def test_one_hundred_get_requests_iter_chunks_on_512kib_chunked_payload( + loop: asyncio.AbstractEventLoop, + aiohttp_client: AiohttpClient, + benchmark: BenchmarkFixture, +) -> None: + """Benchmark 100 GET requests with a payload of 512KiB using iter_chunks.""" + message_count = 100 + payload = b"a" * (2**19) + + async def handler(request: web.Request) -> web.Response: + resp = web.Response(body=payload) + resp.enable_chunked_encoding() + return resp + + app = web.Application() + app.router.add_route("GET", "/", handler) + + async def run_client_benchmark() -> None: + client = await aiohttp_client(app) + for _ in range(message_count): + resp = await client.get("/") + async for _ in resp.content.iter_chunks(): + pass + await client.close() + + @benchmark + def _run() -> None: + loop.run_until_complete(run_client_benchmark()) + + def test_get_request_with_251308_compressed_chunked_payload( loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient, From 51daf7190e7674773c22011a4e443df8b5e66437 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 6 Feb 2025 10:27:02 -0600 Subject: [PATCH 4/5] Disable writelines for test_write_large_payload_deflate_compression_data_in_eof (#10423) --- CHANGES/10423.packaging.rst | 1 + tests/test_http_writer.py | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 CHANGES/10423.packaging.rst diff --git a/CHANGES/10423.packaging.rst b/CHANGES/10423.packaging.rst new file mode 100644 index 00000000000..6cf58c5a10b --- /dev/null +++ b/CHANGES/10423.packaging.rst @@ -0,0 +1 @@ +Fixed test ``test_write_large_payload_deflate_compression_data_in_eof_writelines`` failing with Python 3.12.9+ or 3.13.2+ -- by :user:`bdraco`. diff --git a/tests/test_http_writer.py b/tests/test_http_writer.py index 95dabeab377..b76cac0fe58 100644 --- a/tests/test_http_writer.py +++ b/tests/test_http_writer.py @@ -19,6 +19,12 @@ def enable_writelines() -> Generator[None, None, None]: yield +@pytest.fixture +def disable_writelines() -> Generator[None, None, None]: + with mock.patch("aiohttp.http_writer.SKIP_WRITELINES", True): + yield + + @pytest.fixture def force_writelines_small_payloads() -> Generator[None, None, None]: with mock.patch("aiohttp.http_writer.MIN_PAYLOAD_FOR_WRITELINES", 1): @@ -123,6 +129,7 @@ async def test_write_payload_length( assert b"da" == content.split(b"\r\n\r\n", 1)[-1] +@pytest.mark.usefixtures("disable_writelines") async def test_write_large_payload_deflate_compression_data_in_eof( protocol: BaseProtocol, transport: asyncio.Transport, From 289259d123763911c8f408e2979d820fd10dc053 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 6 Feb 2025 10:27:18 -0600 Subject: [PATCH 5/5] Add human readable error messages for WebSocket PING/PONG timeouts (#10422) --- CHANGES/10422.misc.rst | 3 +++ aiohttp/client_ws.py | 4 +++- aiohttp/web_ws.py | 6 +++++- tests/test_client_ws_functional.py | 1 + tests/test_web_websocket_functional.py | 1 + 5 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 CHANGES/10422.misc.rst diff --git a/CHANGES/10422.misc.rst b/CHANGES/10422.misc.rst new file mode 100644 index 00000000000..7ecb1c0e2e2 --- /dev/null +++ b/CHANGES/10422.misc.rst @@ -0,0 +1,3 @@ +Added human-readable error messages to the exceptions for WebSocket disconnects due to PONG not being received -- by :user:`bdraco`. + +Previously, the error messages were empty strings, which made it hard to determine what went wrong. diff --git a/aiohttp/client_ws.py b/aiohttp/client_ws.py index 757f5f0388f..bfd53ea64bf 100644 --- a/aiohttp/client_ws.py +++ b/aiohttp/client_ws.py @@ -163,7 +163,9 @@ def _ping_task_done(self, task: "asyncio.Task[None]") -> None: self._ping_task = None def _pong_not_received(self) -> None: - self._handle_ping_pong_exception(ServerTimeoutError()) + self._handle_ping_pong_exception( + ServerTimeoutError(f"No PONG received after {self._pong_heartbeat} seconds") + ) def _handle_ping_pong_exception(self, exc: BaseException) -> None: """Handle exceptions raised during ping/pong processing.""" diff --git a/aiohttp/web_ws.py b/aiohttp/web_ws.py index 4f104780c7d..813002e7871 100644 --- a/aiohttp/web_ws.py +++ b/aiohttp/web_ws.py @@ -186,7 +186,11 @@ def _ping_task_done(self, task: "asyncio.Task[None]") -> None: def _pong_not_received(self) -> None: if self._req is not None and self._req.transport is not None: - self._handle_ping_pong_exception(asyncio.TimeoutError()) + self._handle_ping_pong_exception( + asyncio.TimeoutError( + f"No PONG received after {self._pong_heartbeat} seconds" + ) + ) def _handle_ping_pong_exception(self, exc: BaseException) -> None: """Handle exceptions raised during ping/pong processing.""" diff --git a/tests/test_client_ws_functional.py b/tests/test_client_ws_functional.py index 42c705ffb96..190096bbf5e 100644 --- a/tests/test_client_ws_functional.py +++ b/tests/test_client_ws_functional.py @@ -926,6 +926,7 @@ async def handler(request: web.Request) -> NoReturn: assert resp.close_code is WSCloseCode.ABNORMAL_CLOSURE assert msg.type is WSMsgType.ERROR assert isinstance(msg.data, ServerTimeoutError) + assert str(msg.data) == "No PONG received after 0.05 seconds" async def test_close_websocket_while_ping_inflight( diff --git a/tests/test_web_websocket_functional.py b/tests/test_web_websocket_functional.py index 43e98e9f453..6bdd5808362 100644 --- a/tests/test_web_websocket_functional.py +++ b/tests/test_web_websocket_functional.py @@ -841,6 +841,7 @@ async def handler(request: web.Request) -> NoReturn: assert ws.close_code == WSCloseCode.ABNORMAL_CLOSURE assert ws_server_close_code == WSCloseCode.ABNORMAL_CLOSURE assert isinstance(ws_server_exception, asyncio.TimeoutError) + assert str(ws_server_exception) == "No PONG received after 0.025 seconds" await ws.close()