buffered.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. from __future__ import annotations
  2. __all__ = (
  3. "BufferedByteReceiveStream",
  4. "BufferedByteStream",
  5. "BufferedConnectable",
  6. )
  7. import sys
  8. from collections.abc import Callable, Iterable, Mapping
  9. from dataclasses import dataclass, field
  10. from typing import Any, SupportsIndex
  11. from .. import ClosedResourceError, DelimiterNotFound, EndOfStream, IncompleteRead
  12. from ..abc import (
  13. AnyByteReceiveStream,
  14. AnyByteStream,
  15. AnyByteStreamConnectable,
  16. ByteReceiveStream,
  17. ByteStream,
  18. ByteStreamConnectable,
  19. )
  20. if sys.version_info >= (3, 12):
  21. from typing import override
  22. else:
  23. from typing_extensions import override
  24. @dataclass(eq=False)
  25. class BufferedByteReceiveStream(ByteReceiveStream):
  26. """
  27. Wraps any bytes-based receive stream and uses a buffer to provide sophisticated
  28. receiving capabilities in the form of a byte stream.
  29. """
  30. receive_stream: AnyByteReceiveStream
  31. _buffer: bytearray = field(init=False, default_factory=bytearray)
  32. _closed: bool = field(init=False, default=False)
  33. async def aclose(self) -> None:
  34. await self.receive_stream.aclose()
  35. self._closed = True
  36. @property
  37. def buffer(self) -> bytes:
  38. """The bytes currently in the buffer."""
  39. return bytes(self._buffer)
  40. @property
  41. def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]:
  42. return self.receive_stream.extra_attributes
  43. def feed_data(self, data: Iterable[SupportsIndex], /) -> None:
  44. """
  45. Append data directly into the buffer.
  46. Any data in the buffer will be consumed by receive operations before receiving
  47. anything from the wrapped stream.
  48. :param data: the data to append to the buffer (can be bytes or anything else
  49. that supports ``__index__()``)
  50. """
  51. self._buffer.extend(data)
  52. async def receive(self, max_bytes: int = 65536) -> bytes:
  53. if max_bytes < 1:
  54. raise ValueError("max_bytes must be a positive integer")
  55. if self._closed:
  56. raise ClosedResourceError
  57. if self._buffer:
  58. chunk = bytes(self._buffer[:max_bytes])
  59. del self._buffer[:max_bytes]
  60. return chunk
  61. elif isinstance(self.receive_stream, ByteReceiveStream):
  62. return await self.receive_stream.receive(max_bytes)
  63. else:
  64. # With a bytes-oriented object stream, we need to handle any surplus bytes
  65. # we get from the receive() call
  66. chunk = await self.receive_stream.receive()
  67. if len(chunk) > max_bytes:
  68. # Save the surplus bytes in the buffer
  69. self._buffer.extend(chunk[max_bytes:])
  70. return chunk[:max_bytes]
  71. else:
  72. return chunk
  73. async def receive_exactly(self, nbytes: int) -> bytes:
  74. """
  75. Read exactly the given amount of bytes from the stream.
  76. :param nbytes: the number of bytes to read
  77. :return: the bytes read
  78. :raises ~anyio.IncompleteRead: if the stream was closed before the requested
  79. amount of bytes could be read from the stream
  80. """
  81. while True:
  82. remaining = nbytes - len(self._buffer)
  83. if remaining <= 0:
  84. retval = self._buffer[:nbytes]
  85. del self._buffer[:nbytes]
  86. return bytes(retval)
  87. try:
  88. if isinstance(self.receive_stream, ByteReceiveStream):
  89. chunk = await self.receive_stream.receive(remaining)
  90. else:
  91. chunk = await self.receive_stream.receive()
  92. except EndOfStream as exc:
  93. raise IncompleteRead from exc
  94. self._buffer.extend(chunk)
  95. async def receive_until(self, delimiter: bytes, max_bytes: int) -> bytes:
  96. """
  97. Read from the stream until the delimiter is found or max_bytes have been read.
  98. :param delimiter: the marker to look for in the stream
  99. :param max_bytes: maximum number of bytes that will be read before raising
  100. :exc:`~anyio.DelimiterNotFound`
  101. :return: the bytes read (not including the delimiter)
  102. :raises ~anyio.IncompleteRead: if the stream was closed before the delimiter
  103. was found
  104. :raises ~anyio.DelimiterNotFound: if the delimiter is not found within the
  105. bytes read up to the maximum allowed
  106. """
  107. delimiter_size = len(delimiter)
  108. offset = 0
  109. while True:
  110. # Check if the delimiter can be found in the current buffer
  111. index = self._buffer.find(delimiter, offset)
  112. if index >= 0:
  113. found = self._buffer[:index]
  114. del self._buffer[: index + len(delimiter) :]
  115. return bytes(found)
  116. # Check if the buffer is already at or over the limit
  117. if len(self._buffer) >= max_bytes:
  118. raise DelimiterNotFound(max_bytes)
  119. # Read more data into the buffer from the socket
  120. try:
  121. data = await self.receive_stream.receive()
  122. except EndOfStream as exc:
  123. raise IncompleteRead from exc
  124. # Move the offset forward and add the new data to the buffer
  125. offset = max(len(self._buffer) - delimiter_size + 1, 0)
  126. self._buffer.extend(data)
  127. class BufferedByteStream(BufferedByteReceiveStream, ByteStream):
  128. """
  129. A full-duplex variant of :class:`BufferedByteReceiveStream`. All writes are passed
  130. through to the wrapped stream as-is.
  131. """
  132. def __init__(self, stream: AnyByteStream):
  133. """
  134. :param stream: the stream to be wrapped
  135. """
  136. super().__init__(stream)
  137. self._stream = stream
  138. @override
  139. async def send_eof(self) -> None:
  140. await self._stream.send_eof()
  141. @override
  142. async def send(self, item: bytes) -> None:
  143. await self._stream.send(item)
  144. class BufferedConnectable(ByteStreamConnectable):
  145. """
  146. Wraps a byte stream connectable to produce :class:`BufferedByteStream` connections.
  147. Use this when you want the streams returned by :meth:`connect` to have the buffered
  148. receive API (e.g. :meth:`~BufferedByteReceiveStream.receive_exactly` and
  149. :meth:`~BufferedByteReceiveStream.receive_until`).
  150. :param connectable: the byte stream connectable to wrap
  151. """
  152. def __init__(self, connectable: AnyByteStreamConnectable):
  153. """
  154. :param connectable: the connectable to wrap
  155. """
  156. self.connectable = connectable
  157. @override
  158. async def connect(self) -> BufferedByteStream:
  159. stream = await self.connectable.connect()
  160. return BufferedByteStream(stream)