def test_multipart_encode_files_raises_exception_with_StringIO_content( ) -> None: files = {"file": ("test.txt", io.StringIO("content"), "text/plain")} with mock.patch("os.urandom", return_value=os.urandom(16)): with pytest.raises(TypeError): encode_request(data={}, files=files) # type: ignore
async def test_aiterator_content(): async def hello_world(): yield b"Hello, " yield b"world!" headers, stream = encode_request(content=hello_world()) assert not isinstance(stream, typing.Iterable) assert isinstance(stream, typing.AsyncIterable) content = b"".join([part async for part in stream]) assert headers == {"Transfer-Encoding": "chunked"} assert content == b"Hello, world!" with pytest.raises(StreamConsumed): [part async for part in stream] # Support 'data' for compat with requests. headers, stream = encode_request(data=hello_world()) # type: ignore assert not isinstance(stream, typing.Iterable) assert isinstance(stream, typing.AsyncIterable) content = b"".join([part async for part in stream]) assert headers == {"Transfer-Encoding": "chunked"} assert content == b"Hello, world!"
async def test_multipart_files_content(): files = {"file": io.BytesIO(b"<file content>")} headers, stream = encode_request(files=files, boundary=b"+++") assert isinstance(stream, typing.Iterable) assert isinstance(stream, typing.AsyncIterable) sync_content = b"".join([part for part in stream]) async_content = b"".join([part async for part in stream]) assert headers == { "Content-Length": "138", "Content-Type": "multipart/form-data; boundary=+++", } assert sync_content == b"".join( [ b"--+++\r\n", b'Content-Disposition: form-data; name="file"; filename="upload"\r\n', b"Content-Type: application/octet-stream\r\n", b"\r\n", b"<file content>\r\n", b"--+++--\r\n", ] ) assert async_content == b"".join( [ b"--+++\r\n", b'Content-Disposition: form-data; name="file"; filename="upload"\r\n', b"Content-Type: application/octet-stream\r\n", b"\r\n", b"<file content>\r\n", b"--+++--\r\n", ] )
def test_multipart_file_tuple_headers(content_type: typing.Optional[str]): file_name = "test.txt" expected_content_type = "text/plain" headers = {"Expires": "0"} files = { "file": (file_name, io.BytesIO(b"<file content>"), content_type, headers) } with mock.patch("os.urandom", return_value=os.urandom(16)): boundary = os.urandom(16).hex() headers, stream = encode_request(data={}, files=files) assert isinstance(stream, typing.Iterable) content = ( f'--{boundary}\r\nContent-Disposition: form-data; name="file"; ' f'filename="{file_name}"\r\nExpires: 0\r\nContent-Type: ' f"{expected_content_type}\r\n\r\n<file content>\r\n--{boundary}--\r\n" "".encode("ascii")) assert headers == { "Content-Type": f"multipart/form-data; boundary={boundary}", "Content-Length": str(len(content)), } assert content == b"".join(stream)
def test_multipart_headers_include_content_type() -> None: """Content-Type from 4th tuple parameter (headers) should override the 3rd parameter (content_type)""" file_name = "test.txt" expected_content_type = "image/png" headers = {"Content-Type": "image/png"} files = { "file": (file_name, io.BytesIO(b"<file content>"), "text_plain", headers) } with mock.patch("os.urandom", return_value=os.urandom(16)): boundary = os.urandom(16).hex() headers, stream = encode_request(data={}, files=files) assert isinstance(stream, typing.Iterable) content = ( f'--{boundary}\r\nContent-Disposition: form-data; name="file"; ' f'filename="{file_name}"\r\nContent-Type: ' f"{expected_content_type}\r\n\r\n<file content>\r\n--{boundary}--\r\n" "".encode("ascii")) assert headers == { "Content-Type": f"multipart/form-data; boundary={boundary}", "Content-Length": str(len(content)), } assert content == b"".join(stream)
def add_token_to_post_body(self, request: httpx.Request): data = dict(parse_qsl(request.content)) data['token'] = self.access_token headers, request.stream = encode_request(data=data) # Ensure our Content-Length header is updated to new value request.headers.update(headers)
def test_multipart_encode_non_seekable_filelike() -> None: """ Test that special readable but non-seekable filelike objects are supported, at the cost of reading them into memory at most once. """ class IteratorIO(io.IOBase): def __init__(self, iterator: typing.Iterator[bytes]) -> None: self._iterator = iterator def read(self, *args: typing.Any) -> bytes: return b"".join(self._iterator) def data() -> typing.Iterator[bytes]: yield b"Hello" yield b"World" fileobj: typing.Any = IteratorIO(data()) files = {"file": fileobj} headers, stream = encode_request(files=files, boundary=b"+++") assert isinstance(stream, typing.Iterable) content = ( b"--+++\r\n" b'Content-Disposition: form-data; name="file"; filename="upload"\r\n' b"Content-Type: application/octet-stream\r\n" b"\r\n" b"HelloWorld\r\n" b"--+++--\r\n" ) assert headers == { "Content-Type": "multipart/form-data; boundary=+++", "Content-Length": str(len(content)), } assert content == b"".join(stream)
async def test_bytesio_content(): headers, stream = encode_request(content=io.BytesIO(b"Hello, world!")) assert isinstance(stream, typing.Iterable) assert not isinstance(stream, typing.AsyncIterable) content = b"".join([part for part in stream]) assert headers == {"Content-Length": "13"} assert content == b"Hello, world!"
async def test_empty_content(): headers, stream = encode_request() assert isinstance(stream, httpx.SyncByteStream) assert isinstance(stream, httpx.AsyncByteStream) sync_content = stream.read() async_content = await stream.aread() assert headers == {} assert sync_content == b"" assert async_content == b""
async def test_empty_request(): headers, stream = encode_request(data={}, files={}) assert isinstance(stream, typing.Iterable) assert isinstance(stream, typing.AsyncIterable) sync_content = b"".join([part for part in stream]) async_content = b"".join([part async for part in stream]) assert headers == {} assert sync_content == b"" assert async_content == b""
async def test_bytes_content(): headers, stream = encode_request(content=b"Hello, world!") assert isinstance(stream, typing.Iterable) assert isinstance(stream, typing.AsyncIterable) sync_content = b"".join([part for part in stream]) async_content = b"".join([part async for part in stream]) assert headers == {"Content-Length": "13"} assert sync_content == b"Hello, world!" assert async_content == b"Hello, world!" # Support 'data' for compat with requests. headers, stream = encode_request(data=b"Hello, world!") # type: ignore assert isinstance(stream, typing.Iterable) assert isinstance(stream, typing.AsyncIterable) sync_content = b"".join([part for part in stream]) async_content = b"".join([part async for part in stream]) assert headers == {"Content-Length": "13"} assert sync_content == b"Hello, world!" assert async_content == b"Hello, world!"
async def test_urlencoded_list(): headers, stream = encode_request(data={"example": ["a", 1, True]}) assert isinstance(stream, typing.Iterable) assert isinstance(stream, typing.AsyncIterable) sync_content = b"".join([part for part in stream]) async_content = b"".join([part async for part in stream]) assert headers == { "Content-Length": "32", "Content-Type": "application/x-www-form-urlencoded", } assert sync_content == b"example=a&example=1&example=true" assert async_content == b"example=a&example=1&example=true"
async def test_urlencoded_content(): headers, stream = encode_request(data={"Hello": "world!"}) assert isinstance(stream, typing.Iterable) assert isinstance(stream, typing.AsyncIterable) sync_content = b"".join([part for part in stream]) async_content = b"".join([part async for part in stream]) assert headers == { "Content-Length": "14", "Content-Type": "application/x-www-form-urlencoded", } assert sync_content == b"Hello=world%21" assert async_content == b"Hello=world%21"
async def test_json_content(): headers, stream = encode_request(json={"Hello": "world!"}) assert isinstance(stream, typing.Iterable) assert isinstance(stream, typing.AsyncIterable) sync_content = b"".join([part for part in stream]) async_content = b"".join([part async for part in stream]) assert headers == { "Content-Length": "19", "Content-Type": "application/json", } assert sync_content == b'{"Hello": "world!"}' assert async_content == b'{"Hello": "world!"}'
def test_multipart_encode_files_allows_filenames_as_none() -> None: files = {"file": (None, io.BytesIO(b"<file content>"))} with mock.patch("os.urandom", return_value=os.urandom(16)): boundary = os.urandom(16).hex() headers, stream = encode_request(data={}, files=files) assert isinstance(stream, typing.Iterable) content = ( '--{0}\r\nContent-Disposition: form-data; name="file"\r\n\r\n' "<file content>\r\n--{0}--\r\n" "".format(boundary).encode("ascii")) assert headers == { "Content-Type": f"multipart/form-data; boundary={boundary}", "Content-Length": str(len(content)), } assert content == b"".join(stream)
def test_multipart_encode_unicode_file_contents() -> None: files = {"file": ("name.txt", "<únicode string>")} with mock.patch("os.urandom", return_value=os.urandom(16)): boundary = os.urandom(16).hex() headers, stream = encode_request(files=files) assert isinstance(stream, typing.Iterable) content = ('--{0}\r\nContent-Disposition: form-data; name="file";' ' filename="name.txt"\r\n' "Content-Type: text/plain\r\n\r\n<únicode string>\r\n" "--{0}--\r\n" "".format(boundary).encode("utf-8")) assert headers == { "Content-Type": f"multipart/form-data; boundary={boundary}", "Content-Length": str(len(content)), } assert content == b"".join(stream)
def test_multipart_encode_files_guesses_correct_content_type( file_name: str, expected_content_type: str) -> None: files = {"file": (file_name, io.BytesIO(b"<file content>"))} with mock.patch("os.urandom", return_value=os.urandom(16)): boundary = os.urandom(16).hex() headers, stream = encode_request(data={}, files=files) assert isinstance(stream, typing.Iterable) content = ( f'--{boundary}\r\nContent-Disposition: form-data; name="file"; ' f'filename="{file_name}"\r\nContent-Type: ' f"{expected_content_type}\r\n\r\n<file content>\r\n--{boundary}--\r\n" "".encode("ascii")) assert headers == { "Content-Type": f"multipart/form-data; boundary={boundary}", "Content-Length": str(len(content)), } assert content == b"".join(stream)
def test_multipart_encode(tmp_path: typing.Any) -> None: path = str(tmp_path / "name.txt") with open(path, "wb") as f: f.write(b"<file content>") data = { "a": "1", "b": b"C", "c": ["11", "22", "33"], "d": "", } files: RequestFiles = { "file": ("name.txt", open(path, "rb")), "file2": ("file2.txt", "<únicode string>"), } with mock.patch("os.urandom", return_value=os.urandom(16)): boundary = os.urandom(16).hex() headers, stream = encode_request(data=data, files=files) assert isinstance(stream, typing.Iterable) content = ( '--{0}\r\nContent-Disposition: form-data; name="a"\r\n\r\n1\r\n' '--{0}\r\nContent-Disposition: form-data; name="b"\r\n\r\nC\r\n' '--{0}\r\nContent-Disposition: form-data; name="c"\r\n\r\n11\r\n' '--{0}\r\nContent-Disposition: form-data; name="c"\r\n\r\n22\r\n' '--{0}\r\nContent-Disposition: form-data; name="c"\r\n\r\n33\r\n' '--{0}\r\nContent-Disposition: form-data; name="d"\r\n\r\n\r\n' '--{0}\r\nContent-Disposition: form-data; name="file";' ' filename="name.txt"\r\n' "Content-Type: text/plain\r\n\r\n<file content>\r\n" '--{0}\r\nContent-Disposition: form-data; name="file2";' ' filename="file2.txt"\r\n' "Content-Type: text/plain\r\n\r\n<únicode string>\r\n" "--{0}--\r\n" "".format(boundary).encode("utf-8") ) assert headers == { "Content-Type": f"multipart/form-data; boundary={boundary}", "Content-Length": str(len(content)), } assert content == b"".join(stream)
def test_multipart_encode_files_allows_bytes_or_str_content( value: typing.Union[str, bytes], output: str) -> None: files = {"file": ("test.txt", value, "text/plain")} with mock.patch("os.urandom", return_value=os.urandom(16)): boundary = os.urandom(16).hex() headers, stream = encode_request(data={}, files=files) assert isinstance(stream, typing.Iterable) content = ('--{0}\r\nContent-Disposition: form-data; name="file"; ' 'filename="test.txt"\r\n' "Content-Type: text/plain\r\n\r\n{1}\r\n" "--{0}--\r\n" "".format(boundary, output).encode("ascii")) assert headers == { "Content-Type": f"multipart/form-data; boundary={boundary}", "Content-Length": str(len(content)), } assert content == b"".join(stream)
async def test_async_bytesio_content(): class AsyncBytesIO: def __init__(self, content): self._idx = 0 self._content = content async def aread(self, chunk_size: int): chunk = self._content[self._idx : self._idx + chunk_size] self._idx = self._idx + chunk_size return chunk async def __aiter__(self): yield self._content # pragma: nocover headers, stream = encode_request(content=AsyncBytesIO(b"Hello, world!")) assert not isinstance(stream, typing.Iterable) assert isinstance(stream, typing.AsyncIterable) content = b"".join([part async for part in stream]) assert headers == {"Transfer-Encoding": "chunked"} assert content == b"Hello, world!"
def test_invalid_argument(): with pytest.raises(TypeError): encode_request(123) # type: ignore