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: