Skip to content

Commit c09c538

Browse files
Artem Yushkovskiyasvetlov
authored andcommitted
Deprecate obsolete timeout in ClientSession.ws_connect() (#3946)
1 parent 4a2308a commit c09c538

File tree

5 files changed

+131
-24
lines changed

5 files changed

+131
-24
lines changed

CHANGES/3945.removal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Deprecate obsolete `timeout: float` and `receive_timeout: Optional[float]` in `ClientSession.ws_connect()`. Change default websocket receive timeout from `None` to `10.0`.

aiohttp/client.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@
6666
from .client_reqrep import ClientResponse as ClientResponse
6767
from .client_reqrep import Fingerprint as Fingerprint
6868
from .client_reqrep import RequestInfo as RequestInfo
69+
from .client_ws import DEFAULT_WS_CLIENT_TIMEOUT
6970
from .client_ws import ClientWebSocketResponse as ClientWebSocketResponse
71+
from .client_ws import ClientWSTimeout
7072
from .connector import BaseConnector as BaseConnector
7173
from .connector import NamedPipeConnector as NamedPipeConnector
7274
from .connector import TCPConnector as TCPConnector
@@ -584,7 +586,7 @@ def ws_connect(
584586
url: StrOrURL, *,
585587
method: str=hdrs.METH_GET,
586588
protocols: Iterable[str]=(),
587-
timeout: float=10.0,
589+
timeout: Union[ClientWSTimeout, float]=sentinel,
588590
receive_timeout: Optional[float]=None,
589591
autoclose: bool=True,
590592
autoping: bool=True,
@@ -623,7 +625,7 @@ async def _ws_connect(
623625
url: StrOrURL, *,
624626
method: str=hdrs.METH_GET,
625627
protocols: Iterable[str]=(),
626-
timeout: float=10.0,
628+
timeout: Union[ClientWSTimeout, float]=sentinel,
627629
receive_timeout: Optional[float]=None,
628630
autoclose: bool=True,
629631
autoping: bool=True,
@@ -638,6 +640,25 @@ async def _ws_connect(
638640
compress: int=0,
639641
max_msg_size: int=4*1024*1024
640642
) -> ClientWebSocketResponse:
643+
if timeout is not sentinel:
644+
if isinstance(timeout, ClientWSTimeout):
645+
ws_timeout = timeout
646+
else:
647+
warnings.warn("parameter 'timeout' of type 'float' "
648+
"is deprecated, please use "
649+
"'timeout=ClientWSTimeout(ws_close=...)'",
650+
DeprecationWarning,
651+
stacklevel=2)
652+
ws_timeout = ClientWSTimeout(ws_close=timeout)
653+
else:
654+
ws_timeout = DEFAULT_WS_CLIENT_TIMEOUT
655+
if receive_timeout is not None:
656+
warnings.warn("float parameter 'receive_timeout' "
657+
"is deprecated, please use parameter "
658+
"'timeout=ClientWSTimeout(ws_receive=...)'",
659+
DeprecationWarning,
660+
stacklevel=2)
661+
ws_timeout = attr.evolve(ws_timeout, ws_receive=receive_timeout)
641662

642663
if headers is None:
643664
real_headers = CIMultiDict() # type: CIMultiDict[str]
@@ -766,11 +787,10 @@ async def _ws_connect(
766787
writer,
767788
protocol,
768789
resp,
769-
timeout,
790+
ws_timeout,
770791
autoclose,
771792
autoping,
772793
self._loop,
773-
receive_timeout=receive_timeout,
774794
heartbeat=heartbeat,
775795
compress=compress,
776796
client_notakeover=notakeover)

aiohttp/client_ws.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from typing import Any, Optional
55

66
import async_timeout
7+
import attr
78

89
from .client_exceptions import ClientError
910
from .client_reqrep import ClientResponse
@@ -25,19 +26,27 @@
2526
)
2627

2728

29+
@attr.s(frozen=True, slots=True)
30+
class ClientWSTimeout:
31+
ws_receive = attr.ib(type=Optional[float], default=None)
32+
ws_close = attr.ib(type=Optional[float], default=None)
33+
34+
35+
DEFAULT_WS_CLIENT_TIMEOUT = ClientWSTimeout(ws_receive=None, ws_close=10.0)
36+
37+
2838
class ClientWebSocketResponse:
2939

3040
def __init__(self,
3141
reader: 'FlowControlDataQueue[WSMessage]',
3242
writer: WebSocketWriter,
3343
protocol: Optional[str],
3444
response: ClientResponse,
35-
timeout: float,
45+
timeout: ClientWSTimeout,
3646
autoclose: bool,
3747
autoping: bool,
3848
loop: asyncio.AbstractEventLoop,
3949
*,
40-
receive_timeout: Optional[float]=None,
4150
heartbeat: Optional[float]=None,
4251
compress: int=0,
4352
client_notakeover: bool=False) -> None:
@@ -50,8 +59,7 @@ def __init__(self,
5059
self._closed = False
5160
self._closing = False
5261
self._close_code = None # type: Optional[int]
53-
self._timeout = timeout
54-
self._receive_timeout = receive_timeout
62+
self._timeout = timeout # type: ClientWSTimeout
5563
self._autoclose = autoclose
5664
self._autoping = autoping
5765
self._heartbeat = heartbeat
@@ -187,7 +195,8 @@ async def close(self, *, code: int=1000, message: bytes=b'') -> bool:
187195

188196
while True:
189197
try:
190-
with async_timeout.timeout(self._timeout, loop=self._loop):
198+
with async_timeout.timeout(self._timeout.ws_close,
199+
loop=self._loop):
191200
msg = await self._reader.read()
192201
except asyncio.CancelledError:
193202
self._close_code = 1006
@@ -222,7 +231,7 @@ async def receive(self, timeout: Optional[float]=None) -> WSMessage:
222231
self._waiting = self._loop.create_future()
223232
try:
224233
with async_timeout.timeout(
225-
timeout or self._receive_timeout,
234+
timeout or self._timeout.ws_receive,
226235
loop=self._loop):
227236
msg = await self._reader.read()
228237
self._reset_heartbeat()

docs/client_reference.rst

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -455,8 +455,8 @@ The client session supports the context manager protocol for self closing.
455455
<ClientResponse>` object.
456456

457457
.. comethod:: ws_connect(url, *, method='GET', \
458-
protocols=(), timeout=10.0,\
459-
receive_timeout=None,\
458+
protocols=(), \
459+
timeout=sentinel,\
460460
auth=None,\
461461
autoclose=True,\
462462
autoping=True,\
@@ -476,12 +476,11 @@ The client session supports the context manager protocol for self closing.
476476

477477
:param tuple protocols: Websocket protocols
478478

479-
:param float timeout: Timeout for websocket to close. ``10`` seconds
480-
by default
481-
482-
:param float receive_timeout: Timeout for websocket to receive
483-
complete message. ``None`` (unlimited)
484-
seconds by default
479+
:param timeout: a :class:`ClientWSTimeout` timeout for websocket.
480+
By default, the value
481+
`ClientWSTimeout(ws_receive=None, ws_close=10.0)` is used
482+
(``10.0`` seconds for the websocket to close).
483+
``None`` means no timeout will be used.
485484

486485
:param aiohttp.BasicAuth auth: an object that represents HTTP
487486
Basic Authorization (optional)
@@ -1404,7 +1403,24 @@ ClientTimeout
14041403

14051404
:class:`float`, ``None`` by default.
14061405

1407-
.. versionadded:: 3.3
1406+
1407+
.. class:: ClientWSTimeout(*, ws_receive=None, ws_close=None)
1408+
1409+
A data class for websocket client timeout settings.
1410+
1411+
.. attribute:: ws_receive
1412+
1413+
A timeout for websocket to receive a complete message.
1414+
1415+
:class:`float`, ``None`` by default.
1416+
1417+
.. attribute:: ws_close
1418+
1419+
A timeout for the websocket to close.
1420+
1421+
:class:`float`, ``10.0`` by default.
1422+
1423+
.. versionadded:: 4.0
14081424

14091425
RequestInfo
14101426
^^^^^^^^^^^

tests/test_client_ws_functional.py

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import aiohttp
77
from aiohttp import hdrs, web
8+
from aiohttp.client_ws import ClientWSTimeout
89

910

1011
@pytest.fixture
@@ -340,7 +341,7 @@ async def handler(request):
340341
assert resp.closed
341342

342343

343-
async def test_close_timeout(aiohttp_client) -> None:
344+
async def test_close_timeout_sock_close_read(aiohttp_client) -> None:
344345

345346
async def handler(request):
346347
ws = web.WebSocketResponse()
@@ -353,7 +354,39 @@ async def handler(request):
353354
app = web.Application()
354355
app.router.add_route('GET', '/', handler)
355356
client = await aiohttp_client(app)
356-
resp = await client.ws_connect('/', timeout=0.2, autoclose=False)
357+
timeout = ClientWSTimeout(ws_close=0.2)
358+
resp = await client.ws_connect('/', timeout=timeout, autoclose=False)
359+
360+
await resp.send_bytes(b'ask')
361+
362+
msg = await resp.receive()
363+
assert msg.data == 'test'
364+
assert msg.type == aiohttp.WSMsgType.TEXT
365+
366+
msg = await resp.close()
367+
assert resp.closed
368+
assert isinstance(resp.exception(), asyncio.TimeoutError)
369+
370+
371+
async def test_close_timeout_deprecated(aiohttp_client) -> None:
372+
373+
async def handler(request):
374+
ws = web.WebSocketResponse()
375+
await ws.prepare(request)
376+
await ws.receive_bytes()
377+
await ws.send_str('test')
378+
await asyncio.sleep(1)
379+
return ws
380+
381+
app = web.Application()
382+
app.router.add_route('GET', '/', handler)
383+
client = await aiohttp_client(app)
384+
with pytest.warns(DeprecationWarning,
385+
match="parameter 'timeout' of type 'float' "
386+
"is deprecated, please use "
387+
r"'timeout=ClientWSTimeout\(ws_close=...\)'"
388+
):
389+
resp = await client.ws_connect('/', timeout=0.2, autoclose=False)
357390

358391
await resp.send_bytes(b'ask')
359392

@@ -485,7 +518,7 @@ async def handler(request):
485518
await resp.close()
486519

487520

488-
async def test_receive_timeout(aiohttp_client) -> None:
521+
async def test_receive_timeout_sock_read(aiohttp_client) -> None:
489522

490523
async def handler(request):
491524
ws = web.WebSocketResponse()
@@ -498,10 +531,38 @@ async def handler(request):
498531
app.router.add_route('GET', '/', handler)
499532

500533
client = await aiohttp_client(app)
501-
resp = await client.ws_connect('/', receive_timeout=0.1)
534+
receive_timeout = ClientWSTimeout(ws_receive=0.1)
535+
resp = await client.ws_connect('/', timeout=receive_timeout)
502536

503537
with pytest.raises(asyncio.TimeoutError):
504-
await resp.receive(0.05)
538+
await resp.receive(timeout=0.05)
539+
540+
await resp.close()
541+
542+
543+
async def test_receive_timeout_deprecation(aiohttp_client) -> None:
544+
545+
async def handler(request):
546+
ws = web.WebSocketResponse()
547+
await ws.prepare(request)
548+
await ws.receive()
549+
await ws.close()
550+
return ws
551+
552+
app = web.Application()
553+
app.router.add_route('GET', '/', handler)
554+
555+
client = await aiohttp_client(app)
556+
with pytest.warns(
557+
DeprecationWarning,
558+
match="float parameter 'receive_timeout' "
559+
"is deprecated, please use parameter "
560+
r"'timeout=ClientWSTimeout\(ws_receive=...\)'"
561+
):
562+
resp = await client.ws_connect('/', receive_timeout=0.1)
563+
564+
with pytest.raises(asyncio.TimeoutError):
565+
await resp.receive(timeout=0.05)
505566

506567
await resp.close()
507568

0 commit comments

Comments
 (0)