Last updated: Apr 11, 2024
Reading time·5 min

requests-toolbelt module to send multipart/form-dataTo send a "multipart/form-data" request with the requests library in
Python:
files parameter as a dictionary when calling requests.post()files parameter is set, requests sends a multipart/form-data
POST request instead of a application/x-www-form-urlencoded POST request.from pprint import pprint import requests url = 'https://httpbin.org/post' response = requests.post( url, files={'id': 1, 'site': 'bobbyhadz.com'}, timeout=30 ) print(response.status_code) pprint(response.json()['headers'])
As shown in the code sample, the files parameter doesn't have to contain
files.
However, the files parameter has to be used even if you don't need to upload
files to the server.
Make sure you have the requests library installed to be able to run the code snippet.
pip install requests # or with pip3 pip3 install requests

httpbin API is a bit flaky, so the HTTP request might time out depending on the time of the day.When the files parameter is specified in the call to request.post(), then
the requests library sets the Content-Type header to multipart/form-data
in the POST request.
You can access the request headers as response.json()['headers'] to verify
that the Content-Type header is set to multipart/form-data.
from pprint import pprint # ... print(response.status_code) pprint(response.json()['headers'])
The output will look something like this:
200 {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Content-Length': '252', 'Content-Type': 'multipart/form-data; ' 'boundary=344105e37bee4840291f830328272d08', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.28.2', 'X-Amzn-Trace-Id': 'Root=1-648b1179-6d365c68138be548314b79a0' }
When making multipart/form-data HTTP requests, make sure you aren't setting
the Content-Type header yourself.
The requests library will automatically set the Content-Type header
correctly and it will include the boundary parameter as shown in the code sample
above.
The boundary parameter needs to match the boundary that is used in the
request body, so it has to be automatically set by requests.
The same approach can be used if you need to send a file over the network.
import requests url = 'https://httpbin.org/post' files = {'file': open('data.json', 'rb')} response = requests.post(url, files=files, timeout=30) print(response.text) print(response.status_code)
The example assumes that you have a data.json file in the same directory as
your Python script.
{ "id": 1, "name": "bobby hadz", "site": "bobbyhadz.com", "topic": "Python" }

You can set the filename, Content-Type and headers explicitly by using a tuple for the dictionary value.
import requests url = 'https://httpbin.org/post' files = { 'file': ( 'example.xlsx', open('example.xlsx', 'rb'), 'application/vnd.ms-excel', {'Expires': '0'} ) } response = requests.post(url, files=files, timeout=30) print(response.text) print(response.status_code)
The code sample assumes that you have an example.xlsx file in the same
directory as your Python main.py script.

The tuple we passed for the file dictionary key contains 4 elements:
In other words, the tuple has the following format:
(filename, data, Content-Type, headers).
If the dictionary value is a simple string, the filename will be the same as the dictionary key.
import requests url = 'https://httpbin.org/post' files = { 'session_id': '192ZXJAWKjewiqe1j23XXA2h3' } response = requests.post(url, files=files, timeout=30) print(response.text) print(response.status_code)
Running the code sample will produce output similar to this:
{ "args": {}, "data": "", "files": { "session_id": "192ZXJAWKjewiqe1j23XXA2h3" }, "form": {}, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Content-Length": "177", "Content-Type": "multipart/form-data; boundary=ff9ca52fa6489578695603d655e51c7b", "Host": "httpbin.org", "User-Agent": "python-requests/2.28.2", "X-Amzn-Trace-Id": "Root=1-648b17bf-5cc0e58b1dce855345d7ceb6" }, "json": null, "origin": "109.120.245.4", "url": "https://httpbin.org/post" }

The files property is set to the specified session_id.
If the dictionary value is a tuple and the first item is None, the filename
property will not be included.
import requests url = 'https://httpbin.org/post' files = { 'session_id': (None, '192ZXJAWKjewiqe1j23XXA2h3') } response = requests.post(url, files=files, timeout=30) print(response.text) print(response.status_code)
Running the code sample above produces the following output.
{ "args": {}, "data": "", "files": {}, "form": { "session_id": "192ZXJAWKjewiqe1j23XXA2h3" }, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Content-Length": "154", "Content-Type": "multipart/form-data; boundary=5ae1c6c70e9f9e62a9b6309634aa475f", "Host": "httpbin.org", "User-Agent": "python-requests/2.28.2", "X-Amzn-Trace-Id": "Root=1-648b191d-3bc7da643eb0c7137285fe10" }, "json": null, "origin": "109.120.245.4", "url": "https://httpbin.org/post" }
The files dictionary is empty and the form property is set to the specified
session_id.
The first value in the tuple should be None for plain text fields.
files = { 'session_id': (None, '192ZXJAWKjewiqe1j23XXA2h3') }
The None value serves as a placeholder for the filename field which is only
used for file uploads.
When sending text fields, set the first element in the tuple to None.
The tuple value can be:
The fileobj element can be an actual file or a string when dealing with
plain-text fields.
Here is an example that sets the fileobj element to a string.
import requests url = 'https://httpbin.org/post' files = { 'file': ( 'example.csv', 'some,data,to,send\nanother,row,to,send\n' ) } response = requests.post(url, files=files, timeout=30) print(response.text) print(response.status_code)
Running the code sample produces the following output.
{ "args": {}, "data": "", "files": { "file": "some,data,to,send\nanother,row,to,send\n" }, "form": {}, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Content-Length": "185", "Content-Type": "multipart/form-data; boundary=e01ea5853a69f145728a7aba04d78fdd", "Host": "httpbin.org", "User-Agent": "python-requests/2.28.2", "X-Amzn-Trace-Id": "Root=1-648b1aad-7789258e70d4320e76bf7f82" }, "json": null, "origin": "109.120.245.4", "url": "https://httpbin.org/post" }
The example sends a string that is received as a file.
requests-toolbelt module to send multipart/form-dataYou can also use the requests-toolbelt
module when sending multipart/form-data requests.
First, open your terminal in your project's root directory and install the module.
pip install requests-toolbelt # or with pip3 pip3 install requests-toolbelt
Now import and use the module as follows.
import requests from requests_toolbelt.multipart.encoder import MultipartEncoder url = 'https://httpbin.org/post' multipart_data = MultipartEncoder( fields={ # Plain text fields 'field1': 'value1', 'field2': 'value2', # File upload field 'file': ('example.xlsx', open('example.xlsx', 'rb'), 'text/plain') } ) response = requests.post(url, data=multipart_data, headers={'Content-Type': multipart_data.content_type}, timeout=30) print(response.text) print(response.status_code)
Running the code sample produces the following output.
{ "args": {}, "data": "", "files": { "file": "data:text/plain;base64, ... REST TRUNCATED" }, "form": { "field1": "value1", "field2": "value2" }, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Content-Length": "5213", "Content-Type": "multipart/form-data; boundary=3fc36883f949412e8c0986a8d86f25f6", "Host": "httpbin.org", "User-Agent": "python-requests/2.28.2", "X-Amzn-Trace-Id": "Root=1-648b1d21-57f5cc64591f2f8d45db717c" }, "json": null, "origin": "109.120.245.4", "url": "https://httpbin.org/post" } 200
The file is set under files > file and the plain text fields are specified
under form.
The requests-toolbelt module enables us to stream multipart form data using
the MultipartEncoder class.
You can also use multipart/form-data encoding for requests that don't require
files.
import requests from requests_toolbelt.multipart.encoder import MultipartEncoder url = 'https://httpbin.org/post' multipart_data = MultipartEncoder( fields={ # Plain text fields 'field1': 'value1', 'field2': 'value2', } ) response = requests.post(url, data=multipart_data, headers={'Content-Type': multipart_data.content_type}, timeout=30) print(response.text) print(response.status_code)
Running the code sample produces the following output.
{ "args": {}, "data": "", "files": {}, "form": { "field1": "value1", "field2": "value2" }, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Content-Length": "224", "Content-Type": "multipart/form-data; boundary=161e261edbca4e3389aac536bc795b16", "Host": "httpbin.org", "User-Agent": "python-requests/2.28.2", "X-Amzn-Trace-Id": "Root=1-648b1e05-3c74b8ed4e5d02ab6ee061a9" }, "json": null, "origin": "109.120.245.4", "url": "https://httpbin.org/post" } 200
We only set plain text fields when issuing the request, so the files object is
empty.
You can check out more examples of using the requests-toolbelt module on the
package's GitHub page.
You can learn more about the related topics by checking out the following tutorials: