Search code examples
pythondjangogoogle-glasspython-requestsgoogle-mirror-api

Attach image in static card with Mirror API and Python


I'm trying to insert an item card into timeline with an image as attachment.

If I try to insert an item, it goes well:

def notify_card(my_user, card=None):
    payload = card
    headers = {'Content-Type': 'application/json',
               'Authorization': 'Bearer {0}'.format(my_user.mirror_access_token)}
    url = OAUTH_API_BASE_URL + '/mirror/v1/timeline'
    r = requests.post(url, data=payload, headers=headers)
    if r.status_code == 401:
        new_access_token = __refresh_token(my_user.mirror_refresh_token)
        __create_or_update_user(access_token=new_access_token, refresh_token=user_vademecum.mirror_refresh_token)
        headers = {'Content-Type': 'application/json',
               'Authorization': 'Bearer {0}'.format(new_access_token)}
        r = requests.post(url, data=payload, headers=headers)

This works fine, an insert the card into the timeline.

The problem comes now, when I want to upload an image:

# Send media
import os
module_dir = os.path.dirname(__file__)  # get current directory
file_path = os.path.join(module_dir, 'bodegon.jpg')
file = {'bodegon.jpg': ('bodegon.jpg', open(file_path, 'rb'), 'image/jpg')}
headers = {
           'Authorization': 'Bearer {0}'.format(my_user.mirror_access_token)}
url = OAUTH_API_BASE_URL + 'upload/mirror/v1/timeline?uploadType=multipart'

r = requests.post(url, data={"message": {"bundleId": "0000001"}}, files=file, headers=headers)
print r.text

This code returns the error message:

{
 "error": {
  "errors": [
   {
    "domain": "global",
    "reason": "badContent",
    "message": "Media type 'application/octet-stream' is not supported. Valid media types: [image/*, audio/*, video/*]"
   }
  ],
  "code": 400,
  "message": "Media type 'application/octet-stream' is not supported. Valid media types: [image/*, audio/*, video/*]"
 }
}

If I set manually the content type:

headers = {'content-type': 'image/jpg',
           'Authorization': 'Bearer {0}'.format(my_user.mirror_access_token)}

This message is returned:

{
 "error": {
  "errors": [
   {
    "domain": "global",
    "reason": "required",
    "message": "Required"
   }
  ],
  "code": 400,
  "message": "Required"
 }
}

Also, I have try to send the media to this url:

https://www.googleapis.com/upload/mirror/v1/timeline?uploadType=media

instead of

https://www.googleapis.com/upload/mirror/v1/timeline?uploadType=multipart

In order to upload using the simple upload: https://developers.google.com/glass/media-upload

What I'm doing wrong?


Solution

  • Using the code you provided, requests will generate a request body roughly like:

    --{boundary}
    Content-Disposition: form-data; message="message"
    
    
    bundleId
    
    
    --{boundary}
    
    Content-Disposition: form-data; name="bodegon.jpg"
    
    
    
    {file contents}
    
    --{boundary}--
    

    Notice that the id you specify for bundleId does not appear. This means you may want it to be JSON encoded data as @Prisoner seems to suggest in his answer. Further judging by your question you need to provide a Content-Type for the file part of the upload. This can be solved like so:

    import json
    r = requests.post(url, files={"message": (, json.dumps({"bundleId": "0000001"}, 'application/json'), 'file': ('filename.jpg', file, 'image/jpeg')}, headers=headers)
    

    Where headers is the original dictionary you specified.

    Also, if you're uploading very large files you may want to consider not using requests alone for this. The file will be loaded into memory in its entirety. In a case like this you will most likely wish to stream the upload. You can use requests-toolbelt like so:

    import requests
    from requests_toolbelt import MultipartEncoder
    import json
    
    fields = {
        'message': (, json.dumps({'bundleId': '0000001'}, 'application/json'),
        'file': ('filename.jpg', file, 'image/jpeg')
    }
    
    encoder = MultipartEncoder(fields)
    headers = {
        'Authorization': 'Bearer <your token>',
        'Content-Type': encoder.content_type
    }
    
    r = requests.post(url, data=encoder, headers=headers)
    

    This will take care of encoding the data and allowing it to be streamed to the server.

    Note I did not check the names of the form parts with the documentation so using 'file' may not be right. I encourage you to look into this yourself.