* File upoads with no filename should not set a Content-Type in their multipart data.
* Update type annotations to allow file uploads to be a string
RequestFiles = typing.Dict[
str,
typing.Union[
- typing.IO[typing.AnyStr], # file
- typing.Tuple[str, typing.IO[typing.AnyStr]], # (filename, file)
+ # file (or str)
+ typing.Union[typing.IO[typing.AnyStr], typing.AnyStr],
+ # (filename, file (or str))
typing.Tuple[
- str, typing.IO[typing.AnyStr], str
- ], # (filename, file, content_type)
+ typing.Optional[str], typing.Union[typing.IO[typing.AnyStr], typing.AnyStr],
+ ],
+ # (filename, file (or str), content_type)
+ typing.Tuple[
+ typing.Optional[str],
+ typing.Union[typing.IO[typing.AnyStr], typing.AnyStr],
+ typing.Optional[str],
+ ],
],
]
value[2] if len(value) > 2 else self.guess_content_type()
)
- def guess_content_type(self) -> str:
+ def guess_content_type(self) -> typing.Optional[str]:
if self.filename:
return mimetypes.guess_type(self.filename)[0] or "application/octet-stream"
else:
- return "application/octet-stream"
+ return None
def render_headers(self) -> bytes:
parts = [b"Content-Disposition: form-data; ", _format_param("name", self.name)]
if self.filename:
filename = _format_param("filename", self.filename)
parts.extend([b"; ", filename])
- content_type = self.content_type.encode()
- parts.extend([b"\r\nContent-Type: ", content_type, b"\r\n\r\n"])
+ if self.content_type is not None:
+ content_type = self.content_type.encode()
+ parts.extend([b"\r\nContent-Type: ", content_type])
+ parts.append(b"\r\n\r\n")
return b"".join(parts)
def render_data(self) -> bytes:
assert content_type == f"multipart/form-data; boundary={boundary}"
assert body == (
- '--{0}\r\nContent-Disposition: form-data; name="file"\r\n'
- "Content-Type: application/octet-stream\r\n\r\n<file content>\r\n"
- "--{0}--\r\n"
+ '--{0}\r\nContent-Disposition: form-data; name="file"\r\n\r\n'
+ "<file content>\r\n--{0}--\r\n"
"".format(boundary).encode("ascii")
)
+@pytest.mark.parametrize(
+ "file_name,expected_content_type",
+ [("example.json", "application/json"), ("example.log", "application/octet-stream")],
+)
+def test_multipart_encode_files_guesses_correct_content_type(
+ file_name, expected_content_type
+):
+ files = {"file": (file_name, io.BytesIO(b"<file content>"))}
+ with mock.patch("os.urandom", return_value=os.urandom(16)):
+ boundary = binascii.hexlify(os.urandom(16)).decode("ascii")
+
+ body, content_type = multipart.multipart_encode(data={}, files=files)
+
+ assert content_type == f"multipart/form-data; boundary={boundary}"
+ assert body == (
+ 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")
+ )
+
+
def test_multipart_encode_files_allows_str_content():
files = {"file": ("test.txt", "<string content>", "text/plain")}
with mock.patch("os.urandom", return_value=os.urandom(16)):