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]]}"">
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()
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)
I also encountered a 403 indicating the API for my google drive was not enabled:
make sure that you have both api's enabled (for the drive and the spreadhseets)
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With