Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Google Sheets API "update" method Http Error 400

I am trying to make a python script that reads and writes to a google spreadsheet. I've basically copied the python quickstart script at https://developers.google.com/sheets/quickstart/python and modified it using the reference at https://developers.google.com/resources/api-libraries/documentation/sheets/v4/python/latest/.

Everything works fine with the "get" method shown in the quickstart script. I can read the sheet with no errors. To use "update" instead of "get" (write to the sheet instead of read it), I removed the .readonly portion of the scope url. I also replaced the get() method with update() and included body as an argument in the update() method with a json object containing the values encoded with json.dumps. This was all according to the second reference above.

I get an HttpError 400 every time for "invalid data".

Code:

import httplib2
import os
import json

from apiclient import discovery
import oauth2client
from oauth2client import client
from oauth2client import tools

try:
    import argparse
    flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args()
except ImportError:
    flags = None

# If modifying these scopes, delete your previously saved credentials
# at ~/.credentials/sheets.googleapis.com-python-quickstart.json
SCOPES = 'https://www.googleapis.com/auth/spreadsheets'
CLIENT_SECRET_FILE = 'client_secret.json'
APPLICATION_NAME = 'Google Sheets API Python Quickstart'


def get_credentials():
    """Gets valid user credentials from storage.

    If nothing has been stored, or if the stored credentials are invalid,
    the OAuth2 flow is completed to obtain the new credentials.

    Returns:
        Credentials, the obtained credential.
    """
    home_dir = os.path.expanduser('~')
    credential_dir = os.path.join(home_dir, '.credentials')
    if not os.path.exists(credential_dir):
        os.makedirs(credential_dir)
    credential_path = os.path.join(credential_dir,
                                   'sheets.googleapis.com-python-quickstart.json')

    store = oauth2client.file.Storage(credential_path)
    credentials = store.get()
    if not credentials or credentials.invalid:
        flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES)
        flow.user_agent = APPLICATION_NAME
        if flags:
            credentials = tools.run_flow(flow, store, flags)
        else: # Needed only for compatibility with Python 2.6
            credentials = tools.run(flow, store)
        print('Storing credentials to ' + credential_path)
    return credentials

def main():
    """Shows basic usage of the Sheets API.

    Creates a Sheets API service object and prints the names and majors of
    students in a sample spreadsheet:
    https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit
    """
    credentials = get_credentials()
    http = credentials.authorize(httplib2.Http())
    discoveryUrl = ('https://sheets.googleapis.com/$discovery/rest?'
                    'version=v4')
    service = discovery.build('sheets', 'v4', http=http,
                              discoveryServiceUrl=discoveryUrl)

    spreadsheetId = '1Wbo5ilhw68IMUTSvnj_2yyRmWJ87NP-lHdJdaPBmTGA'
    rangeName = 'Class Data!A2:E'
    body = json.dumps({'values': [[0,0,0,0,0]]})
    result = service.spreadsheets().values().update(
        spreadsheetId=spreadsheetId, range=rangeName, body=body).execute()


if __name__ == '__main__':
    main()

Console errors:

Traceback (most recent call last):
  File "/Users/user/Dropbox/python/jobs/test.py", line 73, in <module>
    main()
  File "/Users/user/Dropbox/python/jobs/test.py", line 69, in main
    spreadsheetId=spreadsheetId, range=rangeName, body=body).execute()
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages/oauth2client/util.py", line 135, in positional_wrapper
    return wrapped(*args, **kwargs)
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages/googleapiclient/http.py", line 832, in execute
    raise HttpError(resp, content, uri=self.uri)
googleapiclient.errors.HttpError: <HttpError 400 when requesting https://sheets.googleapis.com/v4/spreadsheets/1Wbo5ilhw68IMUTSvnj_2yyRmWJ87NP-lHdJdaPBmTGA/values/Class%20Data%21A2%3AE?alt=json returned "Invalid value at 'data' (type.googleapis.com/google.apps.sheets.v4.ValueRange), "{"values": [[0, 0, 0, 0, 0]]}"">
like image 576
rspears69 Avatar asked May 21 '16 23:05

rspears69


3 Answers

I also started with the API example. Above information about enabling both APIs, for Drive and Sheets, helped me solving the access error.

Then I found that the .get(...).execute() returns an object, which is the type that is needed as input in the .update().execute(), it looks like:

{u'range': u'Sheet1!A1:B2', u'values': [[u'cella1', u'cellb1'],
[u'cella2', u'cellb2']], u'majorDimension': u'ROWS'}

After having made a fit between ranges, and also adding the argument valueInputOption='RAW' to the .update(), I successfully wrote to the Google sheet with this code snippet:

myBody = {u'range': u'Sheet1!A1:B2', u'values': [[u'Zellea1', u'Zelleb1'], [u'Zellea2', u'Zelleb2']], u'majorDimension': u'ROWS'}
rangeOutput = 'Sheet1!A1:B2'
res = spreadsheet.values().update( spreadsheetId=spreadsheetId, range=rangeOutput, valueInputOption='RAW', body=myBody ).execute()
like image 101
Guenter Koellner Avatar answered Oct 19 '22 12:10

Guenter Koellner


I setup myself to have a google dev account and encountered several setbacks, but I was able to manage.

I'm pretty sure the 401 is due to the incorrect JSON file provided to authenticate (specially if you have several projects and have downloaded several JSON files) enter image description here

I also encountered a 403 indicating the API for my google drive was not enabled:

enter image description here

make sure that you have both api's enabled (for the drive and the spreadhseets) enter image description here

Most of the time, when executing my code, the console returned a pretty clear Error and pointed towards the right direction:

raise HttpError(resp, content, uri=self.uri) googleapiclient.errors.HttpError: https://sheets.googleapis.com/v4/spreadsheets/DocumentID/values/Class%20Data%21A2%3AE?alt=json returned "Google Sheets API has not been used in project pytestapp before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/sheets.googleapis.com/overview?project=pytestapp then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.">

Process finished with exit code 1

like image 42
glls Avatar answered Oct 19 '22 13:10

glls


I figured out that the body arg included in update(), although shown in the documentation as json, actually needs to be a regular python dictionary. I was sending a body of text in json, and what the google api client was looking for was an actual python dict object I guess.

like image 3
rspears69 Avatar answered Oct 19 '22 13:10

rspears69