Following this Shopify tutorial, I’m trying to upload an image to Shopify. A subtask is to translate this curl command to a python request. The file is uploaded by users and I pass the file variable to this function with request.FILES[‘filename’]:
curl -v
-F "Content-Type=image/png"
-F "success_action_status=201"
-F "acl=private"
-F "key=tmp/45732462614/products/7156c27e-0331-4bd0-b758-f345afaa90d1/watches_comparison.png"
-F "x-goog-date=20221024T181157Z"
-F "x-goog-credential=merchant-assets@shopify-tiers.iam.gserviceaccount.com/20221024/auto/storage/goog4_request"
-F "x-goog-algorithm=GOOG4-RSA-SHA256"
-F "x-goog-signature=039cb87e2787029b56f498beb2deb3b9c34d96da642c1955f79225793f853760906abbd894933c5b434899d315da13956b1f67d8be54f470571d7ac1487621766a2697dfb8699c57d4e67a8b36ea993fde0f888b8d1c8bd3f33539d8583936bc13f9001ea3e6d401de6ad7ad2ae52d722073caf250340d5b0e92032d7ad9e0ec560848b55ec0f943595578a1d6cae53cd222d719acb363ba2c825e3506a52b545dec5be57074f8b1b0d58298a0b4311016752f4cdb955b89508376c38f8b2755fce2423acb3f592a6f240a21d8d2f51c5f740a61a40ca54769a736d73418253ecdf685e15cfaf7284e6e4d5a784a63d0569a9c0cffb660028f659e68a68fb80e"
-F "policy=eyJjb25kaXRpb25zIjpbeyJDb250ZW50LVR5cGUiOiJpbWFnZVwvcG5nIn0seyJzdWNjZXNzX2FjdGlvbl9zdGF0dXMiOiIyMDEifSx7ImFjbCI6InByaXZhdGUifSxbImNvbnRlbnQtbGVuZ3RoLXJhbmdlIiwxLDIwOTcxNTIwXSx7ImJ1Y2tldCI6InNob3BpZnktc3RhZ2VkLXVwbG9hZHMifSx7ImtleSI6InRtcFwvZ2NzXC80NTczMjQ2MjYxNFwvcHJvZHVjdHNcLzcxNTZjMjdlLTAzMzEtNGJkMC1iNzU4LWYzNDVhZmFhOTBkMVwvd2F0Y2hlc19jb21wYXJpc29uLnBuZyJ9LHsieC1nb29nLWRhdGUiOiIyMDIyMTAyNFQxODExNTdaIn0seyJ4LWdvb2ctY3JlZGVudGlhbCI6Im1lcmNoYW50LWFzc2V0c0BzaG9waWZ5LXRpZXJzLmlhbS5nc2VydmljZWFjY291bnQuY29tXC8yMDIyMTAyNFwvYXV0b1wvc3RvcmFnZVwvZ29vZzRfcmVxdWVzdCJ9LHsieC1nb29nLWFsZ29yaXRobSI6IkdPT0c0LVJTQS1TSEEyNTYifV0sImV4cGlyYXRpb24iOiIyMDIyLTEwLTI1VDE4OjExOjU3WiJ9"
-F "file=@/Users/shopifyemployee/Desktop/watches_comparison.png"
"https://shopify-staged-uploads.storage.googleapis.com/"
I use chatgpt to come up with this python code:
def uploadImage(stagedTarget, file):
parameters = stagedTarget["parameters"]
url = stagedTarget["url"]
files = {
'file': file,
}
data = {
'Content-Type': parameters[0]['value'],
'success_action_status': parameters[1]['value'],
'acl': parameters[2]['value'],
'key': parameters[3]['value'],
'x-goog-date': parameters[4]['value'],
'x-goog-credential': parameters[5]['value'],
'x-goog-algorithm': parameters[6]['value'],
'x-goog-signature': parameters[7]['value'],
'policy': parameters[8]['value'],
}
print(f"{url = }, {data = }")
response = requests.post(url, files=files, data=data)
print(f"{response.status_code = }")
print(f"{response.text = }")
response = response.content
response = json.loads(response)
The server gives me this response:
web_1 | response.status_code = 400
web_1 | response.text = "<?xml version='1.0' encoding='UTF-8'?><Error><Code>EntityTooSmall</Code><Message>Your proposed upload is smaller than the minimum object size specified in your Policy Document.</Message><Details>Content-length smaller than lower bound on range</Details></Error>"
My file size is only 46KB. I don’t know why it’s too small. Itried a larger file and it’s still the same. I tried to call the curl command to upload a similar local image file and it’s ok. What did I do wrong?
Updated:
I try to update the python code like below and it gives me 400 error again:
parameters = stagedTarget["parameters"]
url = stagedTarget["url"]
headers = {
'Content-Type': 'multipart/form-data'
}
data = {
'Content-Type': parameters[0]['value'],
'success_action_status': parameters[1]['value'],
'acl': parameters[2]['value'],
'key': parameters[3]['value'],
'x-goog-date': parameters[4]['value'],
'x-goog-credential': parameters[5]['value'],
'x-goog-algorithm': parameters[6]['value'],
'x-goog-signature': parameters[7]['value'],
'policy': parameters[8]['value'],
'file': file,
}
response = requests.post(url, headers=headers, data=data)
web_1 | response.status_code = 400
web_1 | response.text = 'Bad content type. Please use multipart.'
2
Answers
Turns out the file has to be opened in binary mode. I didn't open the file
You need to pass an opened file object (likely in binary mode). Don’t pass just the filename: pass
open(request.FILES['filename'], 'rb')
as second argument touploadImage()
.Although it’s a bit surprising that requests doesn’t raise an error when passed a string for the
files
input. But I assume a string is just file contents for requests, albeit in non-binary mode, so requests doesn’t notice the difference.