3

I am trying to make an API call in Python 3.8.

I have implemented the request in curl and it is as follows:

curl --location --request POST 'https://url.mirakl.net/api/orders/1720178-A/documents' --header 'Authorization: 9xxxxxxxx-4xxx-4xxx-8xxx-xxxxxxxxxxx6' --header 'Accept: application/json' --header 'Content-Type: multipart/form-data' --frm 'files=@"1720178-A.pdf"' --form 'order_documents="<body><order_documents><order_document><file_name>1720178-A.pdf</file_name><type_code>CUSTOMER_INVOICE</type_code> </order_document></order_documents></body>";type=application/xml'

It works, but Python code does not:

    url = "https://url.mirakl.net/api/orders/1720178-A/documents"
    payload = {"order_documents": '<body><order_documents><order_document><file_name>1720178-A.pdf</file_name>'
                                  '<type_code>CUSTOMER_INVOICE</type_code></order_document></order_documents></body>'}
    files = [
        ("files", ('1720178-A.pdf', open('1720178-A.pdf', 'rb')))
    ]

    headers = {
        'Authorization': 'xxxxxxxxx-xxx5-4xxx-xxxx-xxxxxxxxx6',
        'Accept': 'application/json',
        'Content-Type': 'multipart/form-data'
    }

    response = requests.request("POST", url, headers=headers, data=payload, files=files)
    print(response.text)

Returning the following error: {"status":400,"message":"Bad Request"}

The error is obvious in how I'm implementing the request, but I don't know what I'm missing.

I have also tried with json payload with same result:

payload = {"order_documents":[{"file_name":"1720178-A.pdf","type_code":"CUSTOMER_INVOICE"}]}

In my day to day, I use Python for other tasks, but since I'm using it for this one, I want to do it the right way, without relying on running curl from python.

P.S. Looking at the body of the petition I can't see anything wrong.

print(requests.Request("POST", url, headers=headers, files=files, data=payload).prepare().body.decode('US-ASCII', errors='ignore'))
--b54bd40f2809e798c2a04069686c35fc
Content-Disposition: form-data; name="order_documents"

{'file_name': '1720178-A.pdf', 'type_code': 'CUSTOMER_INVOICE'}
--b54bd40f2809e798c2a04069686c35fc
Content-Disposition: form-data; name="files"; filename="1720178-A.pdf"
Content-Type: application/pdf

%PDF-1.7
%
....
....
....
....
%%EOF

--b54bd40f2809e798c2a04069686c35fc--


Red
  • 26,798
  • 7
  • 36
  • 58
Hugo L.M
  • 1,053
  • 17
  • 31
  • 1
    1. You can browse to that website and use Chrome's network inspect tools 2. might be a user-agent header missing? https://stackoverflow.com/questions/10606133/sending-user-agent-using-requests-library-in-python – Y.R. Jun 28 '21 at 21:19
  • 1
    Did you try `files={'1720178-A.pdf': open('1720178-A.pdf', 'rb')}` – A.Raouf Jun 28 '21 at 21:21
  • 1
    @A.Raouf thanks, but that's not the problem either. I've tried to try it out. But that field must be 'files'. – Hugo L.M Jun 29 '21 at 07:47
  • 1
    @Y.R. header was already sent by default `{'User-Agent': 'python-requests/2.25.1', 'Accept-Encoding': 'gzip, deflate', 'Accept': 'application/json', 'Connection': 'keep-alive', 'Authorization': 'xxxxxx-xxx-xx-xxxx-xxxxxxx', 'Content-Type': 'multipart/form-data', 'Content-Length': '433'}`. I tried sending custom one, but is not the problem. – Hugo L.M Jun 29 '21 at 10:33

2 Answers2

2

I have finally found the source of the problem.

To implement the API call (not just one, but several), I downloaded the Postman collection that the retailer makes available to consumers.

What was my failure after checking that the Postman call worked correctly? Using the auto code generation provided by the software. Although it is an implementation for the request library, it sets the content-type header, which causes the error. requests autogenerate it correctly:

{
  "User-Agent": "sitename.app",
  "Accept-Encoding": "gzip, deflate",
  "Accept": "application/json",
  "Connection": "keep-alive",
  "Authorization": "xxx-xxxx-xxx-xxx",
  "Content-Length": "22680",
  "Content-Type": "multipart/form-data; boundary=aea8e12ea4fd0c7d9debb64f69ad0718"
}

In the following screenshot, I show how the collection includes the content type and takes it to the implementation even though it makes use of requests:

enter image description here

Hugo L.M
  • 1,053
  • 17
  • 31
0

I had similar problems very recently with multipart/form sending. Here is how I solved it.

I generated the json part as follows:

import os

data = {
            'title': (None, "testFiles", 'application/json'),
            "short": (None, "testFileShort", 'application/json'),
            "ref_name_1": (None, "TestRef2", 'application/json'),
            "upload_file_1" = (os.path.basename("test.txt"), open("test.txt", 'rb'), 'application/octet-stream')
}

I am not entirely sure, why is it so fuzzy about the json, or if this is the most elegant way, but it works perfectly for me.

And the request code is the following:

import requests

def POST(self, address, json=None, files=None):
    url = self.URL + address
    return requests.post(url=url, json=json, files=files)

try:
    r = httpBackend.POST(
        "/resources/create-files-item",
        files=data)
except Exception:
    return None
Peter Devenyi
  • 177
  • 2
  • 15
  • I am splitting the file to be uploaded from the rest of the information. But if I put it as you indicate, it doesn't seem to work either. – Hugo L.M Jun 29 '21 at 08:26
  • Can you maybe try to create a test, where you don't generate the header yourself, but let the library figure out? In this way at least you can reduce the possible places of error. – Peter Devenyi Jun 29 '21 at 09:39
  • Yes, still a bad request. I have tried this way as you suggested [GitHub Gist](https://gist.github.com/hugo-lorenzo-mato/b7d35dbe060d4637eefb9e65c641f937) – Hugo L.M Jun 29 '21 at 11:06
  • Printing response.request.headers, seem ok: `{'User-Agent': 'python-requests/2.25.1', 'Accept-Encoding': 'gzip, deflate', 'Accept': 'application/json', 'Connection': 'keep-alive', 'Authorization': 'xxxxxxxxx-xxxxxxxxxx-xxxxxxxx-xxxxxx, 'Content-Type': 'multipart/form-data', 'Content-Length': '22707'}` – Hugo L.M Jun 29 '21 at 11:09