Search code examples
pythonpython-3.xpython-requestspostmanmultipartform-data

Python POST multipart/form-data request different behavior from Postman


I'm attempting to use this API endpoint to upload a file:

https://h.app.wdesk.com/s/cerebral-docs/?python#uploadfileusingpost

With this python function:

def upload_file(token, filepath, table_id):
    url = "https://h.app.wdesk.com/s/wdata/prep/api/v1/file"
    headers = {
        'Accept': 'application/json',
        'Authorization': f'Bearer {token}'
    }

    files = {
        "tableId": (None, table_id),
        "file": open(filepath, "rb")
    }
    resp = requests.post(url, headers=headers, files=files)
    print(resp.request.headers)
    return resp.json()

The Content-Type and Content-Length headers are computed and added by the requests library internally as per their documentation. When assigning to the files kwarg in the post function, the library knows it's supposed to be a multipart/form-data request.

The print out of the request header is as follows, showing the Content-Type and Content-Length that the library added. I've omitted the auth token.

{'User-Agent': 'python-requests/2.24.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive',
'Authorization': 'Bearer <omitted>', 'Content-Length': '8201', 'Content-Type': 'multipart/form-data; boundary=bb582b9071574462d44c4b43ec4d7bf3'}

The json response from the API is:

{'body': ['contentType must not be null'], 'code': 400}

The odd thing is that the same request, when made through Postman, gives a different response - which is what I expected from Python as well.

{ "code": 409, "body": "duplicate file name" }

These are the Postman request headers:

POST /s/wdata/prep/api/v1/file HTTP/1.1
Authorization: Bearer <omitted>
Accept: */*
Cache-Control: no-cache
Postman-Token: 34ed08d4-4467-4168-a4e4-c83b16ce9afb
Host: h.app.wdesk.com
Content-Type: multipart/form-data; boundary=--------------------------179907322036790253179546
Content-Length: 8279

The Postman request also computes the Content-Type and Content-Length headers when the request is sent, and are not user specified.

I am quite confused as to why I'm getting two different behaviors from the API service for the same request. There must be something I'm missing and can't figure out what it is.


Solution

  • Figured out what was wrong with my request, compared to NodeJS and Postman.

    The contentType being referred to in the API's error message was the file parameter's content type, not the http request header Content-Type.

    The upload started to work flawlessly when I updated my file parameter like so:

    files = {
            "tableId": (None, table_id),
            "file": (Path(filepath).name, open(filepath, "rb"), "text/csv", None)
        }
    

    I learned that Python's requests library will not automatically add the file's mime type to the request body. We need to be explicit about it.

    Hope this helps someone else too.