Skip to content

Commit c0cd1c8

Browse files
authored
fix download speed for Response.for_resource (#25)
1 parent afee2fd commit c0cd1c8

File tree

1 file changed

+28
-1
lines changed

1 file changed

+28
-1
lines changed

rolo/response.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,33 @@
1010
from types import ModuleType
1111

1212

13+
class _StreamIterableWrapper(t.Iterable[bytes]):
14+
"""
15+
This can wrap an IO[bytes] stream to return an Iterable with a default chunk size of 65536 bytes
16+
"""
17+
18+
def __init__(self, stream: t.IO[bytes], chunk_size: int = 65536):
19+
self.stream = stream
20+
self._chunk_size = chunk_size
21+
22+
def __iter__(self) -> t.Iterator[bytes]:
23+
"""
24+
When passing a stream back to the WSGI server, it will often iterate only 1 byte at a time. Using this chunking
25+
mechanism allows us to bypass this issue.
26+
The caller needs to call `close()` to properly close the file descriptor
27+
:return:
28+
"""
29+
while data := self.stream.read(self._chunk_size):
30+
if not data:
31+
return b""
32+
33+
yield data
34+
35+
def close(self):
36+
if hasattr(self.stream, "close"):
37+
self.stream.close()
38+
39+
1340
class Response(WerkzeugResponse):
1441
"""
1542
An HTTP Response object, which simply extends werkzeug's Response object with a few convenience methods.
@@ -100,4 +127,4 @@ def for_resource(cls, module: "ModuleType", path: str, *args, **kwargs) -> "Resp
100127
mimetype = mimetypes.guess_type(resource.name)
101128
mimetype = mimetype[0] if mimetype and mimetype[0] else "application/octet-stream"
102129

103-
return cls(resource.open("rb"), *args, mimetype=mimetype, **kwargs)
130+
return cls(_StreamIterableWrapper(resource.open("rb")), *args, mimetype=mimetype, **kwargs)

0 commit comments

Comments
 (0)