mirror of
https://github.com/django/django.git
synced 2024-12-22 17:16:24 +00:00
Refs #33865 -- Improved implementation of FakePayload.
FakePayload is a wrapper around io.BytesIO and is expected to masquerade as though it is a file-like object. For that reason it makes sense that it should inherit the correct signatures from io.BytesIO methods. Crucially an implementation of .readline() is added which will be necessary for this to behave more like the expected file-like objects as LimitedStream will be changed to defer to the wrapped stream object rather than rolling its own implementation for improved performance. It should be safe to adjust these signatures because FakePayload is only used internally within test client helpers, is undocumented, and thus private.
This commit is contained in:
parent
95182a8593
commit
57f5669d23
@ -6,7 +6,7 @@ from copy import copy
|
|||||||
from functools import partial
|
from functools import partial
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from io import BytesIO
|
from io import BytesIO, IOBase
|
||||||
from urllib.parse import unquote_to_bytes, urljoin, urlparse, urlsplit
|
from urllib.parse import unquote_to_bytes, urljoin, urlparse, urlsplit
|
||||||
|
|
||||||
from asgiref.sync import sync_to_async
|
from asgiref.sync import sync_to_async
|
||||||
@ -55,7 +55,7 @@ class RedirectCycleError(Exception):
|
|||||||
self.redirect_chain = last_response.redirect_chain
|
self.redirect_chain = last_response.redirect_chain
|
||||||
|
|
||||||
|
|
||||||
class FakePayload:
|
class FakePayload(IOBase):
|
||||||
"""
|
"""
|
||||||
A wrapper around BytesIO that restricts what can be read since data from
|
A wrapper around BytesIO that restricts what can be read since data from
|
||||||
the network can't be sought and cannot be read outside of its content
|
the network can't be sought and cannot be read outside of its content
|
||||||
@ -63,39 +63,49 @@ class FakePayload:
|
|||||||
that wouldn't work in real life.
|
that wouldn't work in real life.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, content=None):
|
def __init__(self, initial_bytes=None):
|
||||||
self.__content = BytesIO()
|
self.__content = BytesIO()
|
||||||
self.__len = 0
|
self.__len = 0
|
||||||
self.read_started = False
|
self.read_started = False
|
||||||
if content is not None:
|
if initial_bytes is not None:
|
||||||
self.write(content)
|
self.write(initial_bytes)
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return self.__len
|
return self.__len
|
||||||
|
|
||||||
def read(self, num_bytes=None):
|
def read(self, size=-1, /):
|
||||||
if not self.read_started:
|
if not self.read_started:
|
||||||
self.__content.seek(0)
|
self.__content.seek(0)
|
||||||
self.read_started = True
|
self.read_started = True
|
||||||
if num_bytes is None:
|
if size == -1 or size is None:
|
||||||
num_bytes = self.__len or 0
|
size = self.__len
|
||||||
assert (
|
assert (
|
||||||
self.__len >= num_bytes
|
self.__len >= size
|
||||||
), "Cannot read more than the available bytes from the HTTP incoming data."
|
), "Cannot read more than the available bytes from the HTTP incoming data."
|
||||||
content = self.__content.read(num_bytes)
|
content = self.__content.read(size)
|
||||||
self.__len -= num_bytes
|
self.__len -= len(content)
|
||||||
return content
|
return content
|
||||||
|
|
||||||
def write(self, content):
|
def readline(self, size=-1, /):
|
||||||
|
if not self.read_started:
|
||||||
|
self.__content.seek(0)
|
||||||
|
self.read_started = True
|
||||||
|
if size == -1 or size is None:
|
||||||
|
size = self.__len
|
||||||
|
assert (
|
||||||
|
self.__len >= size
|
||||||
|
), "Cannot read more than the available bytes from the HTTP incoming data."
|
||||||
|
content = self.__content.readline(size)
|
||||||
|
self.__len -= len(content)
|
||||||
|
return content
|
||||||
|
|
||||||
|
def write(self, b, /):
|
||||||
if self.read_started:
|
if self.read_started:
|
||||||
raise ValueError("Unable to write a payload after it's been read")
|
raise ValueError("Unable to write a payload after it's been read")
|
||||||
content = force_bytes(content)
|
content = force_bytes(b)
|
||||||
self.__content.write(content)
|
self.__content.write(content)
|
||||||
self.__len += len(content)
|
self.__len += len(content)
|
||||||
|
|
||||||
def close(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def closing_iterator_wrapper(iterable, close):
|
def closing_iterator_wrapper(iterable, close):
|
||||||
try:
|
try:
|
||||||
|
@ -290,7 +290,7 @@ class RequestsTests(SimpleTestCase):
|
|||||||
self.assertEqual(stream.read(2), b"")
|
self.assertEqual(stream.read(2), b"")
|
||||||
self.assertEqual(stream.read(), b"")
|
self.assertEqual(stream.read(), b"")
|
||||||
|
|
||||||
def test_stream(self):
|
def test_stream_read(self):
|
||||||
payload = FakePayload("name=value")
|
payload = FakePayload("name=value")
|
||||||
request = WSGIRequest(
|
request = WSGIRequest(
|
||||||
{
|
{
|
||||||
@ -302,6 +302,19 @@ class RequestsTests(SimpleTestCase):
|
|||||||
)
|
)
|
||||||
self.assertEqual(request.read(), b"name=value")
|
self.assertEqual(request.read(), b"name=value")
|
||||||
|
|
||||||
|
def test_stream_readline(self):
|
||||||
|
payload = FakePayload("name=value\nother=string")
|
||||||
|
request = WSGIRequest(
|
||||||
|
{
|
||||||
|
"REQUEST_METHOD": "POST",
|
||||||
|
"CONTENT_TYPE": "application/x-www-form-urlencoded",
|
||||||
|
"CONTENT_LENGTH": len(payload),
|
||||||
|
"wsgi.input": payload,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(request.readline(), b"name=value\n")
|
||||||
|
self.assertEqual(request.readline(), b"other=string")
|
||||||
|
|
||||||
def test_read_after_value(self):
|
def test_read_after_value(self):
|
||||||
"""
|
"""
|
||||||
Reading from request is allowed after accessing request contents as
|
Reading from request is allowed after accessing request contents as
|
||||||
|
@ -5,7 +5,9 @@ from django.test.client import FakePayload
|
|||||||
class FakePayloadTests(SimpleTestCase):
|
class FakePayloadTests(SimpleTestCase):
|
||||||
def test_write_after_read(self):
|
def test_write_after_read(self):
|
||||||
payload = FakePayload()
|
payload = FakePayload()
|
||||||
payload.read()
|
for operation in [payload.read, payload.readline]:
|
||||||
msg = "Unable to write a payload after it's been read"
|
with self.subTest(operation=operation.__name__):
|
||||||
with self.assertRaisesMessage(ValueError, msg):
|
operation()
|
||||||
payload.write(b"abc")
|
msg = "Unable to write a payload after it's been read"
|
||||||
|
with self.assertRaisesMessage(ValueError, msg):
|
||||||
|
payload.write(b"abc")
|
||||||
|
Loading…
Reference in New Issue
Block a user