0
votes

Using the Python Quickstart example for the v4 Google Sheets API as a starting point, I've tried to make a library with read and write functions which can then be used by higher-level classes to easily interact with my sheet. This only works if I use the library itself as a script to call the read/write functions. Both read and write throw the following error if I use them after importing into an external script located in a different directory:

HttpError 404 when requesting https://sheets.googleapis.com/$discovery/v4/spreadsheets/

That URL looks malformed with "$discovery" in it.

Here's my library with a main section which works well if this library is run as a script:

# sheetlib.py
""" Google Docs Spreadsheets wrapper
"""

import httplib2
import os
import json
json.JSONEncoder.default=str

from apiclient import discovery
from oauth2client import client
from oauth2client import tools
from oauth2client.file import Storage


SCOPES = 'https://www.googleapis.com/auth/spreadsheets'
CLIENT_SECRET_FILE = 'client_secret.json'
CREDENTIAL_PATH = 'sheets.googleapis.test.json'
APPLICATION_NAME = 'Test Sheet'
SPREADSHEET_ID = 'abcdefg'


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.
    """
    store = Storage(CREDENTIAL_PATH)
    print 'Environment: {}'.format(json.dumps(os.environ.__dict__['data']))
    print 'Loaded store from {}: {}'.format(CREDENTIAL_PATH, json.dumps(store.__dict__))
    credentials = store.get()
    if not credentials or credentials.invalid:
        flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES)
        flow.user_agent = APPLICATION_NAME
        credentials = tools.run_flow(flow, store)
        print 'Storing credentials to ' + CREDENTIAL_PATH
    return credentials


def build_service():
    """ Returns service object for reading/writing to spreadsheet """
    credentials = get_credentials()
    print "credentials: {}".format(json.dumps(credentials.__dict__))
    http = credentials.authorize(httplib2.Http())
    discoveryUrl = ('https://sheets.googleapis.com/$discovery/rest?version=v4')
    service = discovery.build('sheets', 'v4', http=http, discoveryServiceUrl=discoveryUrl)
    print 'service: {}'.format(json.dumps(service._http.__dict__))
    return service


def write(range, values):
    service = build_service()
    body = {
        'values': values
    }
    service.spreadsheets().values().append(
        spreadsheetId=SPREADSHEET_ID, range=range,
        valueInputOption='RAW', body=body, insertDataOption='INSERT_ROWS').execute()


def read(range):
    """ Pass a range to read, like 'RawData!A:E' """
    service = build_service()
    resp = service.spreadsheets().values().get(spreadsheetId=SPREADSHEET_ID, range=range).execute()
    return resp


class Magic():
    """Reads and writes to the Magic tab of sheet"""

    def spell_list(self):
        return [r for r in read('Magic!A1:G100')['values'][1:]]


if __name__ == '__main__':
    m = Magic()
    print m.spell_list()

If I move the Magic class to another file located in a different directory and try to use imported read, it throws the 404 error:

# magic_test.py
from sheetlib import read
class BadMagic():
    """Reads and writes to the Magic tab of sheet"""

    def spell_list(self):
         return [r for r in read('Magic!A1:G100')['values'][1:]]
m = BadMagic()
m.spell_list()

Traceback (most recent call last):
  File "magic_test.py", line 0, in main
    return [r[0] for r in read('Magic!A2:A100')['values']]
  File "sheetlib.py", line 0, in read
    resp = service.spreadsheets().values().get(spreadsheetId=SPREADSHEET_ID, range=range).execute()
  File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/lib/google-api-python-client/apiclient/http.py", line 292, in execute
    raise HttpError(resp, content, self.uri)
apiclient.errors.HttpError: <HttpError 404 when requesting https://sheets.googleapis.com/$discovery/v4/spreadsheets/abcdefg/values/Magic%21A2%3AA100?alt=json returned "Not Found">

Exploring further, I see that the credentials: and service: output from the build_service() function is different depending on which script is using it:

Calling from sheetlib.py (working)

credentials:

{
  "scopes": "set([u'https://www.googleapis.com/auth/spreadsheets'])",
  "revoke_uri": "https://accounts.google.com/o/oauth2/revoke",
  "access_token": "asdf",
  "token_uri": "https://accounts.google.com/o/oauth2/token",
  "token_info_uri": "https://www.googleapis.com/oauth2/v3/tokeninfo",
  "token_response": {
    "access_token": "asdf",
    "token_type": "Bearer",
    "expires_in": 3600
  },
  "invalid": false,
  "refresh_token": "qwer",
  "client_id": "1234.apps.googleusercontent.com",
  "id_token": null,
  "client_secret": "zxcv",
  "token_expiry": "2017-03-08 17:01:42",
  "store": "<oauth2client.file.Storage object at 0x10bbd6690>",
  "user_agent": "Magic Sheet"
}

service:

{
  "force_exception_to_status_code": false,
  "forward_authorization_headers": false,
  "authorizations": [],
  "proxy_info": "<function proxy_info_from_environment at 0x10af9aed8>",
  "follow_redirects": true,
  "cache": null,
  "request": "<function new_request at 0x10b3dba28>",
  "connections": {},
  "certificates": "<httplib2.KeyCerts object at 0x10b3df3d0>",
  "optimistic_concurrency_methods": [
    "PUT",
    "PATCH"
  ],
  "follow_all_redirects": false,
  "timeout": null,
  "ignore_etag": false,
  "ca_certs": null,
  "credentials": "<httplib2.Credentials object at 0x10b3df410>",
  "disable_ssl_certificate_validation": false
}

Calling from magic_test.py (broken)

credentials:

{
  "access_token": "asdf",
  "token_uri": "https://accounts.google.com/o/oauth2/token",
  "invalid": false,
  "refresh_token": "qwer",
  "client_id": "1234.apps.googleusercontent.com",
  "id_token": null,
  "client_secret": "zxcv",
  "token_expiry": "2017-03-08 17:01:42",
  "store": "<oauth2client.file.Storage object at 0x1101a2e50>",
  "user_agent": "Accounting Sheet"
}

service:

{
  "force_exception_to_status_code": false,
  "forward_authorization_headers": false,
  "authorizations": [],
  "proxy_info": "<bound method type.from_environment of <class 'httplib2.ProxyInfo'>>",
  "follow_redirects": true,
  "cache": null,
  "request": "<function new_request at 0x1101bae60>",
  "connections": {
    "https:sheets.googleapis.com": "<httplib2.HTTPSConnectionWithTimeout instance at 0x1101b1ea8>"
  },
  "certificates": "<httplib2.KeyCerts object at 0x1101c2890>",
  "optimistic_concurrency_methods": [
    "PUT",
    "PATCH"
  ],
  "follow_all_redirects": false,
  "timeout": null,
  "ignore_etag": false,
  "ca_certs": null,
  "credentials": "<httplib2.Credentials object at 0x1101c28d0>",
  "disable_ssl_certificate_validation": false
}

Any clue why different parts of http2lib would be used depending on which script called it?

1

1 Answers

0
votes

You may refer with this thread. Be noted that the body arg included in update(), although shown in the documentation as json, actually needs to be a regular python dictionary. Also, your 404 error means that you requested something that doesn't exist (or that it doesn't want you to know exists). Here's an article which might help you in fixing 404 error.