Python: Sending multipart/form-data request with requests

avatar
Borislav Hadzhiev

Last updated: Apr 11, 2024
5 min

banner

# Table of Contents

  1. Python: Sending multipart/form-data request with requests
  2. Setting the filename, Content-Type and Headers explicitly
  3. Using the requests-toolbelt module to send multipart/form-data

# Python: Sending multipart/form-data request with requests

To send a "multipart/form-data" request with the requests library in Python:

  1. Specify the files parameter as a dictionary when calling requests.post()
  2. When the files parameter is set, requests sends a multipart/form-data POST request instead of a application/x-www-form-urlencoded POST request.
main.py
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'])
The code for this article is available on GitHub

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.

shell
pip install requests # or with pip3 pip3 install requests

send multipart form data request without files

Note that the 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.

main.py
from pprint import pprint # ... print(response.status_code) pprint(response.json()['headers'])
The code for this article is available on GitHub

The output will look something like this:

main.py
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.

main.py
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.

data.json
{ "id": 1, "name": "bobby hadz", "site": "bobbyhadz.com", "topic": "Python" }

send multipart form data request with requests in python

# Setting the filename, Content-Type and Headers explicitly

You can set the filename, Content-Type and headers explicitly by using a tuple for the dictionary value.

main.py
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 for this article is available on GitHub

The code sample assumes that you have an example.xlsx file in the same directory as your Python main.py script.

explicitly specifying filename content type headers

The tuple we passed for the file dictionary key contains 4 elements:

  1. The name of the file.
  2. The data to send over the network.
  3. The Content-Type.
  4. The headers.

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.

main.py
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:

main.py
{ "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" }

filename same as dictionary key

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.

main.py
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)
The code for this article is available on GitHub

Running the code sample above produces the following output.

main.py
{ "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.

main.py
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:

  • A tuple of 2 elements - (filename, fileobj)
  • A tuple of 3 elements - (filename, fileobj, content_type)
  • A tuple of 4 elements - (filename, fileobj, content_type, custom_headers)

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.

main.py
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.

main.py
{ "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.

# Using the requests-toolbelt module to send multipart/form-data

You 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.

shell
pip install requests-toolbelt # or with pip3 pip3 install requests-toolbelt

Now import and use the module as follows.

main.py
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)
The code for this article is available on GitHub

Running the code sample produces the following output.

main.py
{ "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.

main.py
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)
The code for this article is available on GitHub

Running the code sample produces the following output.

main.py
{ "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.

# Additional Resources

You can learn more about the related topics by checking out the following tutorials:

I wrote a book in which I share everything I know about how to become a better, more efficient programmer.
book cover
You can use the search field on my Home Page to filter through all of my articles.