From 89294c8c5aa0c5d83b3e46588e1fe98fa29cfad7 Mon Sep 17 00:00:00 2001 From: gngpp Date: Wed, 30 Sep 2026 22:44:10 +0800 Subject: [PATCH] docs(response): clarify close and streaming ownership --- docs/source/guide/basic.md | 14 +++++++++----- docs/source/guide/blocking.md | 2 ++ python/wreq/blocking.py | 10 +++++----- python/wreq/wreq.py | 10 +++++----- src/client/resp/http.rs | 20 ++++++++++---------- 5 files changed, 31 insertions(+), 25 deletions(-) diff --git a/docs/source/guide/basic.md b/docs/source/guide/basic.md index 793de9b5..c284043f 100644 --- a/docs/source/guide/basic.md +++ b/docs/source/guide/basic.md @@ -173,13 +173,17 @@ async def main(): client = Client() response = await client.get("https://httpbin.org/stream/10") - async for chunk in response.stream(): - if isinstance(chunk, memoryview): - sys.stdout.buffer.write(chunk) - elif isinstance(chunk, HeaderMap): - print("Trailers:", chunk) + async with response: + async with response.stream() as streamer: + async for chunk in streamer: + if isinstance(chunk, memoryview): + sys.stdout.buffer.write(chunk) + elif isinstance(chunk, HeaderMap): + print("Trailers:", chunk) ``` Data chunks are read-only `memoryview` objects; trailer frames are `HeaderMap` objects. The example writes data directly through the buffer protocol. Each view stays valid after the stream is closed. See [Binary data](../getting-started/quickstart.md#binary-data) for copying and releasing views. + +Finish a body read before closing the response. If a read task is still running, cancel it and await its cancellation first: `response.close()` does not cancel active reads or guarantee an immediate socket shutdown. `response.stream()` transfers the body to the streamer, so manage the streamer with its own context manager as shown above. --- diff --git a/docs/source/guide/blocking.md b/docs/source/guide/blocking.md index a65cc3d9..01479d21 100644 --- a/docs/source/guide/blocking.md +++ b/docs/source/guide/blocking.md @@ -188,3 +188,5 @@ if __name__ == "__main__": ``` Data chunks are read-only `memoryview` objects that stay valid after the stream is closed. `resp.bytes()` returns the same type. Pass views directly to APIs that accept the buffer protocol; use `bytes(view)` or `view.tobytes()` only when you need a copy. + +Do not close a response while another thread is reading its body. `resp.close()` discards the retained body and marks its connection as non-reusable, but does not guarantee an immediate socket shutdown or interrupt an active read. A body transferred by `resp.stream()` belongs to the streamer and needs its own context manager, as shown above. diff --git a/python/wreq/blocking.py b/python/wreq/blocking.py index ddc7e794..e1f656d5 100644 --- a/python/wreq/blocking.py +++ b/python/wreq/blocking.py @@ -114,11 +114,11 @@ def bytes(self) -> memoryview: def close(self) -> None: r""" - Close the response. - - This method closes the network connection regardless of whether connection pooling is - enabled or not. It is recommended to use context managers (`with` statement) to properly - manage response lifecycle instead of calling this method manually. + Discard the retained body and mark its connection as non-reusable. + This does not guarantee an immediate socket shutdown or interrupt an active read. + Do not close concurrently with a body read. A body transferred to a Streamer + is managed separately; previously returned memoryviews remain valid. + Prefer a context manager (`with`) for response cleanup. """ def __enter__(self) -> Any: ... diff --git a/python/wreq/wreq.py b/python/wreq/wreq.py index 31155aa2..0fa1957f 100644 --- a/python/wreq/wreq.py +++ b/python/wreq/wreq.py @@ -430,11 +430,11 @@ async def bytes(self) -> memoryview: async def close(self) -> None: r""" - Close the response. - - This method closes the network connection regardless of whether connection pooling is - enabled or not. It is recommended to use async context managers (`async with` statement) - to properly manage response lifecycle instead of calling this method manually. + Discard the retained body and mark its connection as non-reusable. + This does not guarantee an immediate socket shutdown or cancel an active read. + Cancel and await any body-read task before closing. A body transferred to a + Streamer is managed separately; previously returned memoryviews remain valid. + Prefer an async context manager (`async with`) for response cleanup. """ async def __aenter__(self) -> Any: ... diff --git a/src/client/resp/http.rs b/src/client/resp/http.rs index 1831b822..a292313c 100644 --- a/src/client/resp/http.rs +++ b/src/client/resp/http.rs @@ -249,11 +249,11 @@ impl Response { NoGIL::new(fut, cancel).await } - /// Close the response. - /// - /// This method closes the network connection regardless of whether connection pooling is - /// enabled or not. It is recommended to use async context managers (`async with` statement) - /// to properly manage response lifecycle instead of calling this method manually. + /// Discard the retained body and mark its connection as non-reusable. + /// This does not guarantee an immediate socket shutdown or cancel an active read. + /// Cancel and await any body-read task before closing. A body transferred to a + /// Streamer is managed separately; previously returned memoryviews remain valid. + /// Prefer an async context manager (`async with`) for response cleanup. pub async fn close(&self) { Python::attach(|py| { py.detach(|| { @@ -410,11 +410,11 @@ impl BlockingResponse { }) } - /// Close the response. - /// - /// This method closes the network connection regardless of whether connection pooling is - /// enabled or not. It is recommended to use context managers (`with` statement) to properly - /// manage response lifecycle instead of calling this method manually. + /// Discard the retained body and mark its connection as non-reusable. + /// This does not guarantee an immediate socket shutdown or interrupt an active read. + /// Do not close concurrently with a body read. A body transferred to a Streamer + /// is managed separately; previously returned memoryviews remain valid. + /// Prefer a context manager (`with`) for response cleanup. #[inline] pub fn close(&self, py: Python) { py.detach(|| {