Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 2 additions & 1 deletion .mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions CHANGES/10422.misc.rst
Original file line number Diff line number Diff line change
@@ -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.
1 change: 1 addition & 0 deletions CHANGES/10423.packaging.rst
Original file line number Diff line number Diff line change
@@ -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`.
4 changes: 3 additions & 1 deletion aiohttp/client_ws.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
1 change: 0 additions & 1 deletion aiohttp/http_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions aiohttp/web_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
6 changes: 5 additions & 1 deletion aiohttp/web_ws.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
2 changes: 1 addition & 1 deletion requirements/constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion requirements/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion requirements/lint.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion requirements/test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
32 changes: 31 additions & 1 deletion tests/test_benchmarks_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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,
Expand Down
14 changes: 0 additions & 14 deletions tests/test_client_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)})
Expand Down
1 change: 1 addition & 0 deletions tests/test_client_ws_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
7 changes: 7 additions & 0 deletions tests/test_http_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions tests/test_web_websocket_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()


Expand Down
Loading