Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,11 @@ jobs:
run: |
CARGO_VER=$(grep '^version = ' Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/')
PYPROJECT_VER=$(grep '^version = ' pyproject.toml | head -1 | sed 's/version = "\(.*\)"/\1/')
INIT_VER=$(grep '__version__ = ' python/requestx/__init__.py | sed 's/__version__ = "\(.*\)"/\1/')

echo "Cargo.toml: $CARGO_VER"
echo "pyproject.toml: $PYPROJECT_VER"
echo "__init__.py: $INIT_VER"

if [ "$CARGO_VER" != "$PYPROJECT_VER" ] || [ "$CARGO_VER" != "$INIT_VER" ]; then
if [ "$CARGO_VER" != "$PYPROJECT_VER" ]; then
echo "::error::Version mismatch! All versions must be identical."
exit 1
fi
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "requestx"
version = "1.0.10"
version = "1.0.12"
edition = "2021"
description = "High-performance Python HTTP client based on reqwest"
license = "MIT"
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "maturin"

[project]
name = "requestx"
version = "1.0.10"
version = "1.0.12"
description = "Highest-performance Python HTTP client based on Rust Speed"
readme = "README.md"
license = { text = "MIT" }
Expand Down
30 changes: 21 additions & 9 deletions python/requestx/_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -725,17 +725,29 @@ async def aiter_raw(self, chunk_size=None):
self._raw_content = all_content
self._stream_content = None
else:
# No async stream, yield from content
content = self.content
if chunk_size is None:
if content:
self._num_bytes_downloaded += len(content)
yield content
else:
for i in range(0, len(content), chunk_size):
chunk = content[i : i + chunk_size]
# If this is a streaming response, delegate to Rust's aiter_bytes
if self._stream_not_read or self._is_stream:
self._stream_not_read = False # Clear flag
rust_iter = self._response.aiter_bytes(chunk_size)
all_content = b""
async for chunk in rust_iter:
all_content += chunk
self._num_bytes_downloaded += len(chunk)
yield chunk
# Store content after iteration
self._raw_content = all_content
else:
# No async stream, yield from content
content = self.content
if chunk_size is None:
if content:
self._num_bytes_downloaded += len(content)
yield content
else:
for i in range(0, len(content), chunk_size):
chunk = content[i : i + chunk_size]
self._num_bytes_downloaded += len(chunk)
yield chunk

async def aiter_bytes(self, chunk_size=None):
"""Async iterate over the response body as bytes chunks."""
Expand Down
16 changes: 12 additions & 4 deletions src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -859,7 +859,9 @@ impl Response {
return Err(pyo3::exceptions::PyRuntimeError::new_err("Attempted to call an async iterator method on a sync stream."));
}

if self.is_stream_consumed && self.stream.is_none() {
// Allow iteration if we have content (even if stream was previously consumed)
// Only block if we have no content AND stream was consumed
if self.is_stream_consumed && self.content.is_empty() && self.stream.is_none() {
return Err(crate::exceptions::StreamConsumed::new_err(
"Attempted to read or stream content, but the content has already been streamed.",
));
Expand Down Expand Up @@ -900,7 +902,9 @@ impl Response {
return Err(pyo3::exceptions::PyRuntimeError::new_err("Attempted to call an async iterator method on a sync stream."));
}

if self.is_stream_consumed && self.stream.is_none() {
// Allow iteration if we have content (even if stream was previously consumed)
// Only block if we have no content AND stream was consumed
if self.is_stream_consumed && self.content.is_empty() && self.stream.is_none() {
return Err(crate::exceptions::StreamConsumed::new_err(
"Attempted to read or stream content, but the content has already been streamed.",
));
Expand Down Expand Up @@ -941,7 +945,9 @@ impl Response {
return Err(pyo3::exceptions::PyRuntimeError::new_err("Attempted to call an async iterator method on a sync stream."));
}

if self.is_stream_consumed && self.stream.is_none() {
// Allow iteration if we have content (even if stream was previously consumed)
// Only block if we have no content AND stream was consumed
if self.is_stream_consumed && self.content.is_empty() && self.stream.is_none() {
return Err(crate::exceptions::StreamConsumed::new_err(
"Attempted to read or stream content, but the content has already been streamed.",
));
Expand All @@ -962,7 +968,9 @@ impl Response {
return Err(pyo3::exceptions::PyRuntimeError::new_err("Attempted to call an async iterator method on a sync stream."));
}

if self.is_stream_consumed && self.stream.is_none() {
// Allow iteration if we have content (even if stream was previously consumed)
// Only block if we have no content AND stream was consumed
if self.is_stream_consumed && self.content.is_empty() && self.stream.is_none() {
return Err(crate::exceptions::StreamConsumed::new_err(
"Attempted to read or stream content, but the content has already been streamed.",
));
Expand Down