Skip to content

Commit f8fca7a

Browse files
r266-technoreplybenoitc
authored
fix: add __iter__ and __next__ to FileWrapper for PEP 3333 compliance (#3550)
* fix: add __iter__ and __next__ to FileWrapper for PEP 3333 compliance The WSGI spec (PEP 3333) requires that wsgi.file_wrapper return an iterable object. Gunicorn's FileWrapper only implemented __getitem__, which technically makes it iterable via old-style iteration but breaks code that explicitly relies on the iterator protocol (e.g., calling iter() or using next()). This adds __iter__ (returning self) and __next__ to make FileWrapper a proper iterator, maintaining backward compatibility with existing __getitem__-based usage. Fixes #3396 * Fix lint: move imports to top of file --------- Co-authored-by: contributor <noreply@users.noreply.github.com> Co-authored-by: Benoit Chesneau <bchesneau@gmail.com>
1 parent 0ad47db commit f8fca7a

File tree

2 files changed

+34
-1
lines changed

2 files changed

+34
-1
lines changed

gunicorn/http/wsgi.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,15 @@ def __getitem__(self, key):
3838
return data
3939
raise IndexError
4040

41+
def __iter__(self):
42+
return self
43+
44+
def __next__(self):
45+
data = self.filelike.read(self.blksize)
46+
if data:
47+
return data
48+
raise StopIteration
49+
4150

4251
class WSGIErrorsWrapper(io.RawIOBase):
4352

tests/test_http.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from gunicorn import util
1111
from gunicorn.http.body import Body, LengthReader, EOFReader
12-
from gunicorn.http.wsgi import Response
12+
from gunicorn.http.wsgi import FileWrapper, Response
1313
from gunicorn.http.unreader import Unreader, IterUnreader, SocketUnreader
1414
from gunicorn.http.errors import InvalidHeader, InvalidHeaderName, InvalidHTTPVersion
1515
from gunicorn.http.message import TOKEN_RE
@@ -253,3 +253,27 @@ def test_eof_reader_read_invalid_size():
253253
def test_invalid_http_version_error():
254254
assert str(InvalidHTTPVersion('foo')) == "Invalid HTTP Version: 'foo'"
255255
assert str(InvalidHTTPVersion((2, 1))) == 'Invalid HTTP Version: (2, 1)'
256+
257+
258+
def test_file_wrapper_iterable():
259+
"""FileWrapper should support the iterator protocol per PEP 3333."""
260+
filelike = io.BytesIO(b"hello world")
261+
wrapper = FileWrapper(filelike, blksize=5)
262+
263+
# Should be iterable
264+
assert hasattr(wrapper, '__iter__')
265+
assert hasattr(wrapper, '__next__')
266+
assert iter(wrapper) is wrapper
267+
268+
# Should yield chunks via next()
269+
assert next(wrapper) == b"hello"
270+
assert next(wrapper) == b" worl"
271+
assert next(wrapper) == b"d"
272+
with pytest.raises(StopIteration):
273+
next(wrapper)
274+
275+
# Also works with for loop
276+
filelike2 = io.BytesIO(b"abc")
277+
wrapper2 = FileWrapper(filelike2, blksize=2)
278+
chunks = list(wrapper2)
279+
assert chunks == [b"ab", b"c"]

0 commit comments

Comments
 (0)