0

I'm trying to post files to my graphql endpoint @ scaphold.io. I normally write Javascript, but I have to translate this particular operation into Python.

The command to do so with curl looks like this (from their docs):

curl -v https://us-west-2.api.scaphold.io/graphql/scaphold-graphql \
  -H "Content-Type:multipart/form-data" \
  -F 'query=mutation CreateFile($input: CreateFileInput!) { createFile(input: $input) { changedFile { id name blobMimeType blobUrl user { id username } } } }' \
  -F 'variables={ "input": { "name": "Profile Picture", "userId": "VXNlcjoxMA==", "blobFieldName": "myBlobField" } };type=application/json' \
  -F myBlobField=@mark-zuckerberg.jpg

Where the blobFieldName prop in variables matches the Form field name that holds the file to upload.

Using Requests, I've gotten this far:

import requests
from requests_toolbelt import MultipartEncoder

url = 'https://us-west-2.api.scaphold.io/graphql/scaphold-graphql'
multipart_data = MultipartEncoder(
    fields={
        "query":"mutation CreateFile($input: CreateFileInput!) { createFile(input: $input) { changedFile { id name blobMimeType blobUrl user { id username } } } }",
        "variables": { "input": { "name": "Profile Picture", "userId": "VXNlcjoxMA==", "blobFieldName": "myBlobField" } },
        "type":'application/json',
        "myBlobField": ('example.jpg', open('example.jpg', 'rb'), 'image/jpeg' )
        }      
)
req_headers = {'Content-Type':multipart_data.content_type, 'Authorization':'Bearer myreallylongkey'}

r = requests.post(url, data=multipart_data, headers=req_headers)

Unfortunately this is met with the AttributeError:

Traceback (most recent call last):
  File "test-gql.py", line 38, in <module>
    "myBlobField": ('example.jpg', open('example.jpg', 'rb'), 'image/jpeg' )
  File "/home/bmp/code/wayhome/python-phash/requests_toolbelt/multipart/encoder.py", line 119, in __init__
    self._prepare_parts()
  File "/home/bmp/code/wayhome/python-phash/requests_toolbelt/multipart/encoder.py", line 240, in _prepare_parts
    self.parts = [Part.from_field(f, enc) for f in self._iter_fields()]
  File "/home/bmp/code/wayhome/python-phash/requests_toolbelt/multipart/encoder.py", line 488, in from_field
    body = coerce_data(field.data, encoding)
  File "/home/bmp/code/wayhome/python-phash/requests_toolbelt/multipart/encoder.py", line 466, in coerce_data
    return CustomBytesIO(data, encoding)
  File "/home/bmp/code/wayhome/python-phash/requests_toolbelt/multipart/encoder.py", line 529, in __init__
    buffer = encode_with(buffer, encoding)
  File "/home/bmp/code/wayhome/python-phash/requests_toolbelt/multipart/encoder.py", line 410, in encode_with
    return string.encode(encoding)
AttributeError: 'dict' object has no attribute 'encode'

I'm afraid I'm not Pythonic enough to grok this error, but I've eliminated a few suspects:

  • Translating a simple query from curl to Python works fine, so I know it isn't permissions, etc.
  • Using a different file, of the plain/text flavor, fails w/ the same error
  • Using a tuple-of-tuples instead of a dict for fields (as described here)has no effect
Community
  • 1
  • 1
Brandon
  • 7,736
  • 9
  • 47
  • 72
  • curl converters: http://curl.trillworks.com/ and https://shibukawa.github.io/curl_as_dsl/index.html – furas Dec 26 '16 at 04:31
  • 2
    see your last link - they use tuple with `files=`, not with `data=` – furas Dec 26 '16 at 04:36
  • @furas thanks, those converters are awesome (esp first one). Just a note if anyone else tries them: the second converter only supports python 3, and the first converter appears to incorrectly truncate any string with a `=` character. otherwise, its code looks correct. re: `files` vs `data`, I believe when using `MultpartEncoder`, the param changes to `data`. I can say that `data` worked when implementing the solution in the accepted answer, at least. – Brandon Dec 26 '16 at 16:55

1 Answers1

4

From your example , it seems that you are passing "variables" as a dictionary but instead it should be a string. Change

        "variables": { "input": { "name": "Profile Picture", "userId": "VXNlcjoxMA==", "blobFieldName": "myBlobField" } },

to

        "variables": '{ "input": { "name": "Profile Picture", "userId": "VXNlcjoxMA==", "blobFieldName": "myBlobField" } }',

Please note the use to single quotes to make it a string EDIT :: from the code of MultipartEncoder, MultipartEncoder tries to run .encode(...) method on the values. .encode(...) comes with strings. Because the datatype of value of key "variables" is dict, .encode(...) seems to be failing on it.

Aditya
  • 85
  • 3