Skip to content

Commit b3d98e8

Browse files
committed
remove unnecessary and buggy type checks in asyncio
Within asyncio, several `write`, `send` and `sendto` methods contained a check whether the `data` parameter is one of the *right* types, complaining that the data should be bytes-like. This error message is very misleading if one hands over a byte-like object that happens not to be one of the *right* types. Usually, these kinds of tests are not necessary at all, as the code the data is handed to will complain itself if the type is wrong. But in the case of the mentioned methods, the data may be put into a buffer if the data cannot be sent immediately. Any type errors will only be raised much later, when the buffer is finally sent. Interestingly, the test is incorrect as well: any memoryview is considered *right*, although it may be passed to functions that cannot deal with non-contiguous memoryviews, in which case the all the problems that the test should protect from reappear. There are several options one can go forward: * one could update the documentation to reflect the fact that not all bytes-like objects can be passed, but only special ones. This would be unfortunate as the underlying code actually can deal with all bytes-like data. * actually test whether the data is bytes-like. This is unfortunately not easy. The correct test would be to check whether the data can be parsed as by PyArg_Parse with a y* format. I am not aware of a simple test like this. * Remove the test. In this case one should assure that only bytes-like data will be put into the buffers if the data cannot be sent immediately. For simplicity I opted for the last option. One should note another problem here: if we accept objects like memoryview or bytearray (which we currently do), the user may modify the data after-the-fact, which will lead to weird, unexpected behavior. This could be mitigated by always copying the data. This is done in some of the modified methods, but not all, most likely for performance reasons. I think it would be beneficial to deal with this problem in a systematic way, but this is beyond the scope of this patch.
1 parent b9c50b4 commit b3d98e8

File tree

4 files changed

+10
-23
lines changed

4 files changed

+10
-23
lines changed

Lib/asyncio/proactor_events.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -336,10 +336,6 @@ def __init__(self, *args, **kw):
336336
self._empty_waiter = None
337337

338338
def write(self, data):
339-
if not isinstance(data, (bytes, bytearray, memoryview)):
340-
raise TypeError(
341-
f"data argument must be a bytes-like object, "
342-
f"not {type(data).__name__}")
343339
if self._eof_written:
344340
raise RuntimeError('write_eof() already called')
345341
if self._empty_waiter is not None:
@@ -485,10 +481,6 @@ def abort(self):
485481
self._force_close(None)
486482

487483
def sendto(self, data, addr=None):
488-
if not isinstance(data, (bytes, bytearray, memoryview)):
489-
raise TypeError('data argument must be bytes-like object (%r)',
490-
type(data))
491-
492484
if self._address is not None and addr not in (None, self._address):
493485
raise ValueError(
494486
f'Invalid address: must be None or {self._address}')
@@ -500,7 +492,8 @@ def sendto(self, data, addr=None):
500492
return
501493

502494
# Ensure that what we buffer is immutable.
503-
self._buffer.append((bytes(data), addr))
495+
data = bytes(data)
496+
self._buffer.append((data, addr))
504497
self._buffer_size += len(data) + self._header_size
505498

506499
if self._write_fut is None:

Lib/asyncio/selector_events.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,9 +1049,6 @@ def _read_ready__on_eof(self):
10491049
self.close()
10501050

10511051
def write(self, data):
1052-
if not isinstance(data, (bytes, bytearray, memoryview)):
1053-
raise TypeError(f'data argument must be a bytes-like object, '
1054-
f'not {type(data).__name__!r}')
10551052
if self._eof:
10561053
raise RuntimeError('Cannot call write() after write_eof()')
10571054
if self._empty_waiter is not None:
@@ -1071,7 +1068,7 @@ def write(self, data):
10711068
n = self._sock.send(data)
10721069
except (BlockingIOError, InterruptedError):
10731070
pass
1074-
except (SystemExit, KeyboardInterrupt):
1071+
except (TypeError, SystemExit, KeyboardInterrupt):
10751072
raise
10761073
except BaseException as exc:
10771074
self._fatal_error(exc, 'Fatal write error on socket transport')
@@ -1084,7 +1081,7 @@ def write(self, data):
10841081
self._loop._add_writer(self._sock_fd, self._write_ready)
10851082

10861083
# Add it to the buffer.
1087-
self._buffer.append(data)
1084+
self._buffer.append(bytes(data))
10881085
self._maybe_pause_protocol()
10891086

10901087
def _get_sendmsg_buffer(self):
@@ -1255,10 +1252,6 @@ def _read_ready(self):
12551252
self._protocol.datagram_received(data, addr)
12561253

12571254
def sendto(self, data, addr=None):
1258-
if not isinstance(data, (bytes, bytearray, memoryview)):
1259-
raise TypeError(f'data argument must be a bytes-like object, '
1260-
f'not {type(data).__name__!r}')
1261-
12621255
if self._address:
12631256
if addr not in (None, self._address):
12641257
raise ValueError(
@@ -1284,7 +1277,7 @@ def sendto(self, data, addr=None):
12841277
except OSError as exc:
12851278
self._protocol.error_received(exc)
12861279
return
1287-
except (SystemExit, KeyboardInterrupt):
1280+
except (TypeError, SystemExit, KeyboardInterrupt):
12881281
raise
12891282
except BaseException as exc:
12901283
self._fatal_error(

Lib/asyncio/sslproto.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -214,12 +214,10 @@ def write(self, data):
214214
This does not block; it buffers the data and arranges for it
215215
to be sent out asynchronously.
216216
"""
217-
if not isinstance(data, (bytes, bytearray, memoryview)):
218-
raise TypeError(f"data: expecting a bytes-like instance, "
219-
f"got {type(data).__name__}")
220217
if not data:
221218
return
222-
self._ssl_protocol._write_appdata((data,))
219+
# Ensure that what we buffer is immutable.
220+
self._ssl_protocol._write_appdata((bytes(data),))
223221

224222
def writelines(self, list_of_data):
225223
"""Write a list (or any iterable) of data bytes to the transport.

Lib/test/test_asyncio/test_selector_events.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1499,6 +1499,9 @@ def test_sendto_error_received_connected(self):
14991499

15001500
def test_sendto_str(self):
15011501
transport = self.datagram_transport()
1502+
def sendto(data, addr):
1503+
bytes(data) # raises TypeError
1504+
self.sock.sendto = sendto
15021505
self.assertRaises(TypeError, transport.sendto, 'str', ())
15031506

15041507
def test_sendto_connected_addr(self):

0 commit comments

Comments
 (0)