mirror of
https://github.com/frappe/gunicorn.git
synced 2026-07-02 10:41:30 +08:00
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>
This commit is contained in:
parent
0ad47db800
commit
f8fca7a72f
@ -38,6 +38,15 @@ class FileWrapper:
|
|||||||
return data
|
return data
|
||||||
raise IndexError
|
raise IndexError
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __next__(self):
|
||||||
|
data = self.filelike.read(self.blksize)
|
||||||
|
if data:
|
||||||
|
return data
|
||||||
|
raise StopIteration
|
||||||
|
|
||||||
|
|
||||||
class WSGIErrorsWrapper(io.RawIOBase):
|
class WSGIErrorsWrapper(io.RawIOBase):
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ from unittest import mock
|
|||||||
|
|
||||||
from gunicorn import util
|
from gunicorn import util
|
||||||
from gunicorn.http.body import Body, LengthReader, EOFReader
|
from gunicorn.http.body import Body, LengthReader, EOFReader
|
||||||
from gunicorn.http.wsgi import Response
|
from gunicorn.http.wsgi import FileWrapper, Response
|
||||||
from gunicorn.http.unreader import Unreader, IterUnreader, SocketUnreader
|
from gunicorn.http.unreader import Unreader, IterUnreader, SocketUnreader
|
||||||
from gunicorn.http.errors import InvalidHeader, InvalidHeaderName, InvalidHTTPVersion
|
from gunicorn.http.errors import InvalidHeader, InvalidHeaderName, InvalidHTTPVersion
|
||||||
from gunicorn.http.message import TOKEN_RE
|
from gunicorn.http.message import TOKEN_RE
|
||||||
@ -253,3 +253,27 @@ def test_eof_reader_read_invalid_size():
|
|||||||
def test_invalid_http_version_error():
|
def test_invalid_http_version_error():
|
||||||
assert str(InvalidHTTPVersion('foo')) == "Invalid HTTP Version: 'foo'"
|
assert str(InvalidHTTPVersion('foo')) == "Invalid HTTP Version: 'foo'"
|
||||||
assert str(InvalidHTTPVersion((2, 1))) == 'Invalid HTTP Version: (2, 1)'
|
assert str(InvalidHTTPVersion((2, 1))) == 'Invalid HTTP Version: (2, 1)'
|
||||||
|
|
||||||
|
|
||||||
|
def test_file_wrapper_iterable():
|
||||||
|
"""FileWrapper should support the iterator protocol per PEP 3333."""
|
||||||
|
filelike = io.BytesIO(b"hello world")
|
||||||
|
wrapper = FileWrapper(filelike, blksize=5)
|
||||||
|
|
||||||
|
# Should be iterable
|
||||||
|
assert hasattr(wrapper, '__iter__')
|
||||||
|
assert hasattr(wrapper, '__next__')
|
||||||
|
assert iter(wrapper) is wrapper
|
||||||
|
|
||||||
|
# Should yield chunks via next()
|
||||||
|
assert next(wrapper) == b"hello"
|
||||||
|
assert next(wrapper) == b" worl"
|
||||||
|
assert next(wrapper) == b"d"
|
||||||
|
with pytest.raises(StopIteration):
|
||||||
|
next(wrapper)
|
||||||
|
|
||||||
|
# Also works with for loop
|
||||||
|
filelike2 = io.BytesIO(b"abc")
|
||||||
|
wrapper2 = FileWrapper(filelike2, blksize=2)
|
||||||
|
chunks = list(wrapper2)
|
||||||
|
assert chunks == [b"ab", b"c"]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user