From 3084703ca60f1ef689fe4e4e16b06d1fbb885b4a Mon Sep 17 00:00:00 2001 From: Miro <200482516+Mirochill@users.noreply.github.com> Date: Mon, 18 May 2026 22:56:02 +0200 Subject: [PATCH 1/2] Broaden receive data buffer types --- h11/_connection.py | 2 +- h11/_receivebuffer.py | 4 +++- h11/tests/test_connection.py | 16 ++++++++++++++++ newsfragments/186.feature.rst | 2 ++ 4 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 newsfragments/186.feature.rst diff --git a/h11/_connection.py b/h11/_connection.py index e37d82a..6949004 100644 --- a/h11/_connection.py +++ b/h11/_connection.py @@ -361,7 +361,7 @@ def trailing_data(self) -> Tuple[bytes, bool]: """ return (bytes(self._receive_buffer), self._receive_buffer_closed) - def receive_data(self, data: bytes) -> None: + def receive_data(self, data: Union[bytes, bytearray, memoryview]) -> None: """Add data to our internal receive buffer. This does not actually do any processing on the data, just stores diff --git a/h11/_receivebuffer.py b/h11/_receivebuffer.py index e5c4e08..7bbb0cb 100644 --- a/h11/_receivebuffer.py +++ b/h11/_receivebuffer.py @@ -50,7 +50,9 @@ def __init__(self) -> None: self._next_line_search = 0 self._multiple_lines_search = 0 - def __iadd__(self, byteslike: Union[bytes, bytearray]) -> "ReceiveBuffer": + def __iadd__( + self, byteslike: Union[bytes, bytearray, memoryview] + ) -> "ReceiveBuffer": self._data += byteslike return self diff --git a/h11/tests/test_connection.py b/h11/tests/test_connection.py index 01260dc..fbf68c0 100644 --- a/h11/tests/test_connection.py +++ b/h11/tests/test_connection.py @@ -238,6 +238,22 @@ def test_chunk_boundaries() -> None: assert conn.next_event() == EndOfMessage() +@pytest.mark.parametrize("data_wrapper", [bytearray, memoryview]) +def test_receive_data_accepts_byteslike_objects(data_wrapper: Any) -> None: + conn = Connection(our_role=SERVER) + + conn.receive_data( + data_wrapper(b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") + ) + + assert conn.next_event() == Request( + method="GET", + target="/", + headers=[("Host", "example.com")], + ) + assert conn.next_event() == EndOfMessage() + + def test_client_talking_to_http10_server() -> None: c = Connection(CLIENT) c.send(Request(method="GET", target="/", headers=[("Host", "example.com")])) diff --git a/newsfragments/186.feature.rst b/newsfragments/186.feature.rst new file mode 100644 index 0000000..a3e4750 --- /dev/null +++ b/newsfragments/186.feature.rst @@ -0,0 +1,2 @@ +Allow ``Connection.receive_data()`` to accept ``bytearray`` and +``memoryview`` inputs in its public type hints. From ed01bb39c2ef991c978801cdbff7d15057d3cbc9 Mon Sep 17 00:00:00 2001 From: Miro <200482516+Mirochill@users.noreply.github.com> Date: Sat, 10 Oct 2026 13:16:14 +0200 Subject: [PATCH 2/2] Address review and refresh validation --- h11/_connection.py | 15 ++++++++------- h11/_receivebuffer.py | 9 +++++---- h11/tests/test_connection.py | 10 +++++----- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/h11/_connection.py b/h11/_connection.py index 6949004..215a794 100644 --- a/h11/_connection.py +++ b/h11/_connection.py @@ -10,9 +10,13 @@ overload, Tuple, Type, + TYPE_CHECKING, Union, ) +if TYPE_CHECKING: + from typing_extensions import Buffer + from ._events import ( ConnectionClosed, Data, @@ -361,7 +365,7 @@ def trailing_data(self) -> Tuple[bytes, bool]: """ return (bytes(self._receive_buffer), self._receive_buffer_closed) - def receive_data(self, data: Union[bytes, bytearray, memoryview]) -> None: + def receive_data(self, data: "Buffer") -> None: """Add data to our internal receive buffer. This does not actually do any processing on the data, just stores @@ -501,18 +505,15 @@ def next_event(self) -> Union[Event, Type[NEED_DATA], Type[PAUSED]]: raise @overload - def send(self, event: ConnectionClosed) -> None: - ... + def send(self, event: ConnectionClosed) -> None: ... @overload def send( self, event: Union[Request, InformationalResponse, Response, Data, EndOfMessage] - ) -> bytes: - ... + ) -> bytes: ... @overload - def send(self, event: Event) -> Optional[bytes]: - ... + def send(self, event: Event) -> Optional[bytes]: ... def send(self, event: Event) -> Optional[bytes]: """Convert a high-level event into bytes that can be sent to the peer, diff --git a/h11/_receivebuffer.py b/h11/_receivebuffer.py index 7bbb0cb..3bfe0b1 100644 --- a/h11/_receivebuffer.py +++ b/h11/_receivebuffer.py @@ -1,6 +1,9 @@ import re import sys -from typing import List, Optional, Union +from typing import List, Optional, TYPE_CHECKING + +if TYPE_CHECKING: + from typing_extensions import Buffer __all__ = ["ReceiveBuffer"] @@ -50,9 +53,7 @@ def __init__(self) -> None: self._next_line_search = 0 self._multiple_lines_search = 0 - def __iadd__( - self, byteslike: Union[bytes, bytearray, memoryview] - ) -> "ReceiveBuffer": + def __iadd__(self, byteslike: "Buffer") -> "ReceiveBuffer": self._data += byteslike return self diff --git a/h11/tests/test_connection.py b/h11/tests/test_connection.py index fbf68c0..1899bc0 100644 --- a/h11/tests/test_connection.py +++ b/h11/tests/test_connection.py @@ -1,4 +1,4 @@ -from typing import Any, cast, Dict, List, Optional, Tuple, Type +from typing import Any, Callable, cast, Dict, List, Optional, Tuple, Type, Union import pytest @@ -239,12 +239,12 @@ def test_chunk_boundaries() -> None: @pytest.mark.parametrize("data_wrapper", [bytearray, memoryview]) -def test_receive_data_accepts_byteslike_objects(data_wrapper: Any) -> None: +def test_receive_data_accepts_byteslike_objects( + data_wrapper: Callable[[bytes], Union[bytearray, memoryview]], +) -> None: conn = Connection(our_role=SERVER) - conn.receive_data( - data_wrapper(b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") - ) + conn.receive_data(data_wrapper(b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")) assert conn.next_event() == Request( method="GET",