It is safe to upload large files this way. File uploads are streaming by default, meaning that only one chunk will be loaded into memory at a time.
Non-file data fields can be included in the multipart form using by passing them to `data=...`.
+
+You can also send multiple files in one go with a multiple file field form.
+To do that, pass a list of `(field, <file>)` items instead of a dictionary, allowing you to pass multiple items with the same `field`.
+For instance this request sends 2 files, `foo.png` and `bar.png` in one request on the `images` form field:
+
+```python
+>>> files = [('images', ('foo.png', open('foo.png', 'rb'), 'image/png')),
+ ('images', ('bar.png', open('bar.png', 'rb'), 'image/png'))]
+>>> r = httpx.post("https://httpbin.org/post", files=files)
+```
## Customizing authentication
else:
yield self.DataField(name=name, value=value)
- for name, value in files.items():
+ file_items = files.items() if isinstance(files, typing.Mapping) else files
+ for name, value in file_items:
yield self.FileField(name=name, value=value)
def iter_chunks(self) -> typing.Iterator[bytes]:
# (filename, file (or text), content_type)
Tuple[Optional[str], FileContent, Optional[str]],
]
-RequestFiles = Mapping[str, FileTypes]
+RequestFiles = Union[Mapping[str, FileTypes], List[Tuple[str, FileTypes]]]
def test_invalid_argument():
with pytest.raises(TypeError):
encode(123)
+
+
+@pytest.mark.asyncio
+async def test_multipart_multiple_files_single_input_content():
+ files = [
+ ("file", io.BytesIO(b"<file content 1>")),
+ ("file", io.BytesIO(b"<file content 2>")),
+ ]
+ stream = encode(files=files, boundary=b"+++")
+ sync_content = b"".join([part for part in stream])
+ async_content = b"".join([part async for part in stream])
+
+ assert stream.can_replay()
+ assert stream.get_headers() == {
+ "Content-Length": "271",
+ "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 1>\r\n",
+ 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 2>\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 1>\r\n",
+ 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 2>\r\n",
+ b"--+++--\r\n",
+ ]
+ )