Skip to content

Commit 87c4c78

Browse files
committed
Added the receive_nowait() method to all streams
Closes #482.
1 parent 1dabfd8 commit 87c4c78

File tree

9 files changed

+96
-12
lines changed

9 files changed

+96
-12
lines changed

docs/versionhistory.rst

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ This library adheres to `Semantic Versioning 2.0 <http://semver.org/>`_.
1010
- Bumped minimum version of trio to v0.22
1111
- Added ``create_unix_datagram_socket`` and ``create_connected_unix_datagram_socket`` to
1212
create UNIX datagram sockets (PR by Jean Hominal)
13+
- Added the ``receive_nowait()`` method to the entire stream class hierarchy
1314
- Improved type annotations:
1415

1516
- Several functions and methods that previously only accepted coroutines as the return

src/anyio/_backends/_asyncio.py

+10
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,16 @@ def _spawn_task_from_thread(
755755
class StreamReaderWrapper(abc.ByteReceiveStream):
756756
_stream: asyncio.StreamReader
757757

758+
def receive_nowait(self, max_bytes: int = 65536) -> bytes:
759+
if self._stream.exception():
760+
raise self._stream.exception()
761+
elif not self._stream._buffer: # type: ignore[attr-defined]
762+
raise WouldBlock
763+
764+
data = self._stream._buffer[:max_bytes] # type: ignore[attr-defined]
765+
del self._stream._buffer[:max_bytes] # type: ignore[attr-defined]
766+
return data
767+
758768
async def receive(self, max_bytes: int = 65536) -> bytes:
759769
data = await self._stream.read(max_bytes)
760770
if data:

src/anyio/_backends/_trio.py

+12
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,18 @@ def __init__(self, trio_socket: TrioSocketType) -> None:
363363
self._receive_guard = ResourceGuard("reading from")
364364
self._send_guard = ResourceGuard("writing to")
365365

366+
def receive_nowait(self, max_bytes: int = 65536) -> bytes:
367+
with self._receive_guard:
368+
try:
369+
data = self._raw_socket.recv(max_bytes)
370+
except BaseException as exc:
371+
self._convert_socket_error(exc)
372+
373+
if data:
374+
return data
375+
else:
376+
raise EndOfStream
377+
366378
async def receive(self, max_bytes: int = 65536) -> bytes:
367379
with self._receive_guard:
368380
try:

src/anyio/abc/_streams.py

+30-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from collections.abc import Callable
55
from typing import Any, Generic, TypeVar, Union
66

7-
from .._core._exceptions import EndOfStream
7+
from .._core._exceptions import EndOfStream, WouldBlock
88
from .._core._typedattr import TypedAttributeProvider
99
from ._resources import AsyncResource
1010
from ._tasks import TaskGroup
@@ -36,6 +36,20 @@ async def __anext__(self) -> T_co:
3636
except EndOfStream:
3737
raise StopAsyncIteration
3838

39+
def receive_nowait(self) -> T_co:
40+
"""
41+
Receive the next item if it can be done without waiting.
42+
43+
:raises ~anyio.ClosedResourceError: if the receive stream has been explicitly
44+
closed
45+
:raises ~anyio.EndOfStream: if this stream has been closed from the other end
46+
:raises ~anyio.BrokenResourceError: if this stream has been rendered unusable
47+
due to external causes
48+
:raises ~anyio.WouldBlock: if there is no item immeditately available
49+
50+
"""
51+
raise WouldBlock
52+
3953
@abstractmethod
4054
async def receive(self) -> T_co:
4155
"""
@@ -132,6 +146,21 @@ async def __anext__(self) -> bytes:
132146
except EndOfStream:
133147
raise StopAsyncIteration
134148

149+
def receive_nowait(self, max_bytes: int = 65536) -> bytes:
150+
"""
151+
Receive at most ``max_bytes`` bytes from the peer, if it can be done without
152+
blocking.
153+
154+
.. note:: Implementors of this interface should not return an empty
155+
:class:`bytes` object, and users should ignore them.
156+
157+
:param max_bytes: maximum number of bytes to receive
158+
:return: the received bytes
159+
:raises ~anyio.EndOfStream: if this stream has been closed from the other end
160+
:raises ~anyio.WouldBlock: if there is no data waiting to be received
161+
"""
162+
raise WouldBlock
163+
135164
@abstractmethod
136165
async def receive(self, max_bytes: int = 65536) -> bytes:
137166
"""

src/anyio/streams/buffered.py

+21
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,27 @@ def buffer(self) -> bytes:
3232
def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]:
3333
return self.receive_stream.extra_attributes
3434

35+
def receive_nowait(self, max_bytes: int = 65536) -> bytes:
36+
if self._closed:
37+
raise ClosedResourceError
38+
39+
if self._buffer:
40+
chunk = bytes(self._buffer[:max_bytes])
41+
del self._buffer[:max_bytes]
42+
return chunk
43+
elif isinstance(self.receive_stream, ByteReceiveStream):
44+
return self.receive_stream.receive_nowait(max_bytes)
45+
else:
46+
# With a bytes-oriented object stream, we need to handle any surplus bytes
47+
# we get from the receive_nowait() call
48+
chunk = self.receive_stream.receive_nowait()
49+
if len(chunk) > max_bytes:
50+
# Save the surplus bytes in the buffer
51+
self._buffer.extend(chunk[max_bytes:])
52+
return chunk[:max_bytes]
53+
else:
54+
return chunk
55+
3556
async def receive(self, max_bytes: int = 65536) -> bytes:
3657
if self._closed:
3758
raise ClosedResourceError

src/anyio/streams/memory.py

-11
Original file line numberDiff line numberDiff line change
@@ -65,17 +65,6 @@ def __post_init__(self) -> None:
6565
self._state.open_receive_channels += 1
6666

6767
def receive_nowait(self) -> T_co:
68-
"""
69-
Receive the next item if it can be done without waiting.
70-
71-
:return: the received item
72-
:raises ~anyio.ClosedResourceError: if this send stream has been closed
73-
:raises ~anyio.EndOfStream: if the buffer is empty and this stream has been
74-
closed from the sending end
75-
:raises ~anyio.WouldBlock: if there are no items in the buffer and no tasks
76-
waiting to send
77-
78-
"""
7968
if self._closed:
8069
raise ClosedResourceError
8170

src/anyio/streams/stapled.py

+3
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ class StapledByteStream(ByteStream):
3434
send_stream: ByteSendStream
3535
receive_stream: ByteReceiveStream
3636

37+
def receive_nowait(self, max_bytes: int = 65536) -> bytes:
38+
return self.receive_stream.receive_nowait(max_bytes)
39+
3740
async def receive(self, max_bytes: int = 65536) -> bytes:
3841
return await self.receive_stream.receive(max_bytes)
3942

src/anyio/streams/text.py

+7
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ def __post_init__(self, encoding: str, errors: str) -> None:
4242
decoder_class = codecs.getincrementaldecoder(encoding)
4343
self._decoder = decoder_class(errors=errors)
4444

45+
def receive_nowait(self) -> str:
46+
while True:
47+
chunk = self.transport_stream.receive_nowait()
48+
decoded = self._decoder.decode(chunk)
49+
if decoded:
50+
return decoded
51+
4552
async def receive(self) -> str:
4653
while True:
4754
chunk = await self.transport_stream.receive()

src/anyio/streams/tls.py

+12
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from .. import (
1212
BrokenResourceError,
1313
EndOfStream,
14+
WouldBlock,
1415
aclose_forcefully,
1516
get_cancelled_exc_class,
1617
)
@@ -195,6 +196,17 @@ async def aclose(self) -> None:
195196

196197
await self.transport_stream.aclose()
197198

199+
def receive_nowait(self, max_bytes: int = 65536) -> bytes:
200+
try:
201+
data = self._ssl_object.read(max_bytes)
202+
except ssl.SSLError:
203+
raise WouldBlock
204+
205+
if not data:
206+
raise EndOfStream
207+
208+
return data
209+
198210
async def receive(self, max_bytes: int = 65536) -> bytes:
199211
data = await self._call_sslobject_method(self._ssl_object.read, max_bytes)
200212
if not data:

0 commit comments

Comments
 (0)