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 @@ -275,7 +275,7 @@ jobs:
- os: windows
no-extensions: 'Y'
include:
- pyver: pypy-3.11
- pyver: pypy-3.12
no-extensions: 'Y'
os: ubuntu
experimental: false
Expand Down
4 changes: 2 additions & 2 deletions aiohttp/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ async def start_tls(
server_hostname: str | None,
ssl_handshake_timeout: float | None,
ssl_shutdown_timeout: float | None = None,
) -> asyncio.BaseTransport | None:
) -> asyncio.Transport | None:
if aiofastnet is not None:
return await aiofastnet.start_tls(
loop,
Expand Down Expand Up @@ -1473,7 +1473,7 @@ async def _start_tls_connection(
else:
underlying_transport.close()
raise
if isinstance(tls_transport, asyncio.Transport):
if tls_transport is not None:
fingerprint = self._get_fingerprint(req)
if fingerprint:
try:
Expand Down
4 changes: 2 additions & 2 deletions aiohttp/payload.py
Original file line number Diff line number Diff line change
Expand Up @@ -844,8 +844,8 @@ class BytesIOPayload(IOBasePayload):

def __init__(self, value: io.BytesIO, *args: Any, **kwargs: Any) -> None:
super().__init__(value, *args, **kwargs)
# Calculate size once during initialization
self._size = len(self._value.getbuffer()) - self._value.tell()
with self._value.getbuffer() as buf:
self._size = len(buf) - self._value.tell()

@property
def size(self) -> int:
Expand Down
4 changes: 2 additions & 2 deletions requirements/constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ python-dateutil==2.9.0.post0
# via freezegun
python-discovery==1.6.1
# via virtualenv
python-on-whales==0.81.0
python-on-whales==0.81.0 ; implementation_name == "cpython"
# via
# -r requirements/lint.in
# -r requirements/test-common.in
Expand Down Expand Up @@ -306,7 +306,7 @@ towncrier==26.9.0
# via
# -r requirements/doc.in
# sphinxcontrib-towncrier
trustme==1.2.1 ; platform_machine != "i686"
trustme==1.2.1 ; platform_machine != "i686" and implementation_name == "cpython"
# via
# -r requirements/lint.in
# -r requirements/test-common.in
Expand Down
4 changes: 2 additions & 2 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ python-dateutil==2.9.0.post0
# via freezegun
python-discovery==1.6.1
# via virtualenv
python-on-whales==0.81.0
python-on-whales==0.81.0 ; implementation_name == "cpython"
# via
# -r requirements/lint.in
# -r requirements/test-common.in
Expand Down Expand Up @@ -296,7 +296,7 @@ towncrier==26.9.0
# via
# -r requirements/doc.in
# sphinxcontrib-towncrier
trustme==1.2.1 ; platform_machine != "i686"
trustme==1.2.1 ; platform_machine != "i686" and implementation_name == "cpython"
# via
# -r requirements/lint.in
# -r requirements/test-common.in
Expand Down
4 changes: 2 additions & 2 deletions requirements/test-common.in
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ isal; python_version < "3.14" and implementation_name == "cpython" # no wheel fo
mypy; implementation_name == "cpython"
pytest-xdist
pytest_codspeed
python-on-whales
trustme; platform_machine != "i686" # no 32-bit wheels
python-on-whales; implementation_name == "cpython" # Used for autobahn, not run on PyPy
trustme; platform_machine != "i686" and implementation_name == "cpython" # TODO: include on PyPy once new release is out
zlib_ng
4 changes: 2 additions & 2 deletions requirements/test-common.txt
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ pytest-xdist==3.8.0
# via -r requirements/test-common.in
python-dateutil==2.9.0.post0
# via freezegun
python-on-whales==0.81.0
python-on-whales==0.81.0 ; implementation_name == "cpython"
# via -r requirements/test-common.in
rich==15.0.0
# via pytest-codspeed
Expand All @@ -127,7 +127,7 @@ tomli==2.4.1
# coverage
# mypy
# pytest
trustme==1.2.1 ; platform_machine != "i686"
trustme==1.2.1 ; platform_machine != "i686" and implementation_name == "cpython"
# via -r requirements/test-common.in
typing-extensions==4.16.0
# via
Expand Down
4 changes: 2 additions & 2 deletions requirements/test-ft.txt
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ pytest-xdist==3.8.0
# via -r requirements/test-common.in
python-dateutil==2.9.0.post0
# via freezegun
python-on-whales==0.81.0
python-on-whales==0.81.0 ; implementation_name == "cpython"
# via -r requirements/test-common.in
rich==15.0.0
# via pytest-codspeed
Expand All @@ -152,7 +152,7 @@ tomli==2.4.1
# coverage
# mypy
# pytest
trustme==1.2.1 ; platform_machine != "i686"
trustme==1.2.1 ; platform_machine != "i686" and implementation_name == "cpython"
# via -r requirements/test-common.in
typing-extensions==4.16.0 ; python_version < "3.13"
# via
Expand Down
4 changes: 2 additions & 2 deletions requirements/test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ pytest-xdist==3.8.0
# via -r requirements/test-common.in
python-dateutil==2.9.0.post0
# via freezegun
python-on-whales==0.81.0
python-on-whales==0.81.0 ; implementation_name == "cpython"
# via -r requirements/test-common.in
rich==15.0.0
# via pytest-codspeed
Expand All @@ -152,7 +152,7 @@ tomli==2.4.1
# coverage
# mypy
# pytest
trustme==1.2.1 ; platform_machine != "i686"
trustme==1.2.1 ; platform_machine != "i686" and implementation_name == "cpython"
# via -r requirements/test-common.in
typing-extensions==4.16.0 ; python_version < "3.13"
# via
Expand Down
9 changes: 5 additions & 4 deletions tests/test_client_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,8 @@ async def handler(request: web.Request) -> web.Response:
app = web.Application()
app.router.add_route("GET", "/", handler)

runner = web.AppRunner(app, tcp_keepalive=True, keepalive_timeout=0.001)
# Timeout must be enough to outlive first request on slow platforms.
runner = web.AppRunner(app, tcp_keepalive=True, keepalive_timeout=0.2)
await runner.setup()

site = web.SockSite(runner, unused_port_socket)
Expand All @@ -308,7 +309,7 @@ async def handler(request: web.Request) -> web.Response:
resp1 = await sess.get(f"http://{host}:{port}/")
await resp1.read()
# wait for server keepalive_timeout
await asyncio.sleep(0.01)
await asyncio.sleep(0.5)
resp2 = await sess.get(f"http://{host}:{port}/")
await resp2.read()
finally:
Expand All @@ -328,7 +329,7 @@ async def handler(request: web.Request) -> web.Response:
app = web.Application()
app.router.add_route("GET", "/", handler)

runner = web.AppRunner(app, tcp_keepalive=True, keepalive_timeout=0.001)
runner = web.AppRunner(app, tcp_keepalive=True, keepalive_timeout=0.2)
await runner.setup()

site = web.SockSite(runner, unused_port_socket)
Expand All @@ -342,7 +343,7 @@ async def handler(request: web.Request) -> web.Response:
await resp1.read()
# wait for server keepalive_timeout
# time.sleep is a more challenging scenario than asyncio.sleep
time.sleep(0.01)
time.sleep(0.5)
resp2 = await sess.get(f"http://{host}:{port}/")
await resp2.read()
finally:
Expand Down
26 changes: 26 additions & 0 deletions tests/test_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -2678,6 +2678,32 @@ async def test_start_tls_exception_with_ssl_shutdown_timeout_nonzero_pre_311() -
underlying_transport.abort.assert_not_called()


async def test_start_tls_connection_returns_none(
make_client_request: _RequestMaker,
) -> None:
"""A transport closed before the upgrade makes start_tls() return None."""
loop = asyncio.get_running_loop()
conn = aiohttp.TCPConnector()
req = make_client_request("GET", URL("https://example.com"), loop=loop)

with socket.socket() as listener:
listener.bind(("127.0.0.1", 0))
listener.listen()
transport, _ = await loop.create_connection(
asyncio.Protocol, *listener.getsockname()
)
transport.close()

# start_tls() returns None on asyncio, but not on aiofastnet.
with mock.patch.object(connector_module, "aiofastnet", None):
with pytest.raises(aiohttp.ClientConnectorError) as exc_info:
await conn._start_tls_connection(transport, req, ClientTimeout())

assert "Failed to start TLS" in exc_info.value.os_error.args[0]

await conn.close()


def test_client_timeout_total_zero_raises() -> None:
"""Test that ClientTimeout(total=0) raises ValueError.

Expand Down
3 changes: 2 additions & 1 deletion tests/test_multipart.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ async def read(self, size: int | None = None) -> bytes:
return self.content.read(size)

def at_eof(self) -> bool:
return self.content.tell() == len(self.content.getbuffer())
with self.content.getbuffer() as buf:
return self.content.tell() == len(buf)

async def readline(self, *, max_line_length: int | None = None) -> bytes:
return self.content.readline()
Expand Down
18 changes: 17 additions & 1 deletion tests/test_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,10 +403,16 @@ async def test_proxy_server_hostname_override( # type: ignore[misc]
)
@pytest.mark.usefixtures("enable_cleanup_closed")
@pytest.mark.parametrize("cleanup", (True, False))
@pytest.mark.parametrize(
"asyncio_transport",
(True, False),
ids=("asyncio-transport", "duck-typed-transport"),
)
async def test_https_connect_fingerprint_mismatch( # type: ignore[misc]
start_connection: mock.Mock,
ClientRequestMock: mock.Mock,
cleanup: bool,
asyncio_transport: bool,
make_client_request: _RequestMaker,
) -> None:
event_loop = asyncio.get_running_loop()
Expand All @@ -423,6 +429,16 @@ class TransportMock(asyncio.Transport):
def close(self) -> None:
pass

class DuckTypedTransportMock:
"""Models aiofastnet's transport, which subclasses no asyncio class."""

def close(self) -> None:
pass

transport_mock: object = (
TransportMock() if asyncio_transport else DuckTypedTransportMock()
)

url = URL("http://proxy.example.com")
proxy_resp = ClientResponse(
"get",
Expand Down Expand Up @@ -497,7 +513,7 @@ def close(self) -> None:
"start_tls",
autospec=True,
spec_set=True,
return_value=TransportMock(),
return_value=transport_mock,
),
):
req = make_client_request(
Expand Down
7 changes: 5 additions & 2 deletions tests/test_web_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -2061,15 +2061,18 @@ async def ws_handler(request: web.Request) -> web.WebSocketResponse:

for _ in range(frames):
await ws_writer.send_frame(frame_payload.encode(), WSMsgType.TEXT)
writer.write(encoded)
# Write an immutable snapshot: zero-copy selector transports hold
# buffer exports of the written object until flushed, which would
# make the clear() below raise BufferError.
writer.write(bytes(encoded))
await writer.drain()
await asyncio.wait_for(reading_paused.wait(), 5)

# Sent while reading is paused, so this frame is only ever read if the
# handover to the websocket resumes the transport.
encoded.clear()
await ws_writer.send_frame(b"last", WSMsgType.TEXT)
writer.write(encoded)
writer.write(bytes(encoded))
await writer.drain()
release_handler.set()

Expand Down
Loading