Skip to content

Commit

Permalink
Allow str content for multipart upload files (#2400)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomchristie committed Oct 6, 2022
1 parent 770d4f2 commit 0ebe925
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 8 deletions.
10 changes: 8 additions & 2 deletions httpx/_multipart.py
Expand Up @@ -122,8 +122,14 @@ def __init__(self, name: str, value: FileTypes) -> None:
# requests does the opposite (it overwrites the header with the 3rd tuple element)
headers["Content-Type"] = content_type

if isinstance(fileobj, (str, io.StringIO)):
raise TypeError(f"Expected bytes or bytes-like object got: {type(fileobj)}")
if "b" not in getattr(fileobj, "mode", "b"):
raise TypeError(
"Multipart file uploads must be opened in binary mode, not text mode."
)
if isinstance(fileobj, io.StringIO):
raise TypeError(
"Multipart file uploads require 'io.BytesIO', not 'io.StringIO'."
)

self.filename = filename
self.file = fileobj
Expand Down
2 changes: 1 addition & 1 deletion httpx/_types.py
Expand Up @@ -80,7 +80,7 @@

RequestData = Mapping[str, Any]

FileContent = Union[IO[bytes], bytes]
FileContent = Union[IO[bytes], bytes, str]
FileTypes = Union[
# file (or bytes)
FileContent,
Expand Down
29 changes: 24 additions & 5 deletions tests/test_multipart.py
Expand Up @@ -339,18 +339,37 @@ def test_multipart_encode_files_allows_bytes_content() -> None:
assert content == b"".join(stream)


def test_multipart_encode_files_raises_exception_with_str_content() -> None:
files = {"file": ("test.txt", "<bytes content>", "text/plain")}
def test_multipart_encode_files_allows_str_content() -> None:
files = {"file": ("test.txt", "<str content>", "text/plain")}
with mock.patch("os.urandom", return_value=os.urandom(16)):
boundary = os.urandom(16).hex()

with pytest.raises(TypeError):
encode_request(data={}, files=files) # type: ignore
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<str 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_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


def test_multipart_encode_files_raises_exception_with_text_mode_file() -> None:
with tempfile.TemporaryFile(mode="w") as upload:
files = {"file": ("test.txt", upload, "text/plain")}
with pytest.raises(TypeError):
encode_request(data={}, files=files) # type: ignore

Expand Down

0 comments on commit 0ebe925

Please sign in to comment.