Skip to content

Commit 2c9dbad

Browse files
authored
Add StreamReader.total_raw_bytes to report the download progress (#11483)
1 parent 3fc1ead commit 2c9dbad

File tree

6 files changed

+65
-2
lines changed

6 files changed

+65
-2
lines changed

CHANGES/11483.feature.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Added ``StreamReader.total_raw_bytes`` to check the number of bytes downloaded
2+
-- by :user:`robpats`.

CONTRIBUTORS.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ Pahaz Blinov
287287
Panagiotis Kolokotronis
288288
Pankaj Pandey
289289
Parag Jain
290+
Patrick Lee
290291
Pau Freixes
291292
Paul Colomiets
292293
Paul J. Dorn

aiohttp/http_parser.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,7 @@ class DeflateBuffer:
936936
def __init__(self, out: StreamReader, encoding: Optional[str]) -> None:
937937
self.out = out
938938
self.size = 0
939+
out.total_compressed_bytes = self.size
939940
self.encoding = encoding
940941
self._started_decoding = False
941942

@@ -969,6 +970,7 @@ def feed_data(self, chunk: bytes) -> None:
969970
return
970971

971972
self.size += len(chunk)
973+
self.out.total_compressed_bytes = self.size
972974

973975
# RFC1950
974976
# bits 0..3 = CM = 0b1000 = 8 = "deflate"

aiohttp/streams.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ class StreamReader(AsyncStreamReaderMixin):
132132
"_eof_callbacks",
133133
"_eof_counter",
134134
"total_bytes",
135+
"total_compressed_bytes",
135136
)
136137

137138
def __init__(
@@ -159,6 +160,7 @@ def __init__(
159160
self._eof_callbacks: List[Callable[[], None]] = []
160161
self._eof_counter = 0
161162
self.total_bytes = 0
163+
self.total_compressed_bytes: Optional[int] = None
162164

163165
def __repr__(self) -> str:
164166
info = [self.__class__.__name__]
@@ -250,6 +252,12 @@ async def wait_eof(self) -> None:
250252
finally:
251253
self._eof_waiter = None
252254

255+
@property
256+
def total_raw_bytes(self) -> int:
257+
if self.total_compressed_bytes is None:
258+
return self.total_bytes
259+
return self.total_compressed_bytes
260+
253261
def unread_data(self, data: bytes) -> None:
254262
"""rollback reading some data from stream, inserting it to buffer head."""
255263
warnings.warn(

docs/streams.rst

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ Streaming API
2020
:attr:`aiohttp.ClientResponse.content` properties for accessing raw
2121
BODY data.
2222

23-
Reading Methods
24-
---------------
23+
Reading Attributes and Methods
24+
------------------------------
2525

2626
.. method:: StreamReader.read(n=-1)
2727
:async:
@@ -109,6 +109,13 @@ Reading Methods
109109
to the end of a HTTP chunk.
110110

111111

112+
.. attribute:: StreamReader.total_raw_bytes
113+
114+
The number of bytes of raw data downloaded (before decompression).
115+
116+
Readonly :class:`int` property.
117+
118+
112119
Asynchronous Iteration Support
113120
------------------------------
114121

tests/test_client_functional.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5586,3 +5586,46 @@ async def handler(request: web.Request) -> web.Response:
55865586

55875587
finally:
55885588
await asyncio.to_thread(f.close)
5589+
5590+
5591+
async def test_stream_reader_total_raw_bytes(aiohttp_client: AiohttpClient) -> None:
5592+
"""Test whether StreamReader.total_raw_bytes returns the number of bytes downloaded"""
5593+
source_data = b"@dKal^pH>1h|YW1:c2J$" * 4096
5594+
5595+
async def handler(request: web.Request) -> web.Response:
5596+
response = web.Response(body=source_data)
5597+
response.enable_compression()
5598+
return response
5599+
5600+
app = web.Application()
5601+
app.router.add_get("/", handler)
5602+
5603+
client = await aiohttp_client(app)
5604+
5605+
# Check for decompressed data
5606+
async with client.get(
5607+
"/", headers={"Accept-Encoding": "gzip"}, auto_decompress=True
5608+
) as resp:
5609+
assert resp.headers["Content-Encoding"] == "gzip"
5610+
assert int(resp.headers["Content-Length"]) < len(source_data)
5611+
data = await resp.content.read()
5612+
assert len(data) == len(source_data)
5613+
assert resp.content.total_raw_bytes == int(resp.headers["Content-Length"])
5614+
5615+
# Check for compressed data
5616+
async with client.get(
5617+
"/", headers={"Accept-Encoding": "gzip"}, auto_decompress=False
5618+
) as resp:
5619+
assert resp.headers["Content-Encoding"] == "gzip"
5620+
data = await resp.content.read()
5621+
assert resp.content.total_raw_bytes == len(data)
5622+
assert resp.content.total_raw_bytes == int(resp.headers["Content-Length"])
5623+
5624+
# Check for non-compressed data
5625+
async with client.get(
5626+
"/", headers={"Accept-Encoding": "identity"}, auto_decompress=True
5627+
) as resp:
5628+
assert "Content-Encoding" not in resp.headers
5629+
data = await resp.content.read()
5630+
assert resp.content.total_raw_bytes == len(data)
5631+
assert resp.content.total_raw_bytes == int(resp.headers["Content-Length"])

0 commit comments

Comments
 (0)