3

I'm trying to run a google app script function remotely from a python flask app. This function creates google calendar events with inputs from a google sheet. I referred to this documentation from Google in order to set up the python script to run the appscript function. I followed every step required to deploy the app script project as an executable API and connected it to a google developer project and made OAuth 2.0 ID credentials as well.

From the API executable documentation, I got the following code and modified it to run as an object which can be called from the main server file.

from __future__ import print_function
from googleapiclient import errors
from googleapiclient.discovery import build
from httplib2 import Http
from oauth2client import file as oauth_file, client, tools


class CreateGCalEvent:
    def main(self):
        """Runs the sample.
        """
        SCRIPT_ID = 'my app script deployment ID was put here'

        # Set up the Apps Script API
        SCOPES = [
            'https://www.googleapis.com/auth/script.scriptapp',
            'https://www.googleapis.com/auth/drive.readonly',
            'https://www.googleapis.com/auth/drive',
        ]
        store = oauth_file.Storage('token.json')
        creds = store.get()
        if not creds or creds.invalid:
            flow = client.flow_from_clientsecrets('app_script_creds.json', SCOPES)
            creds = tools.run_flow(flow, store)
        service = build('script', 'v1', credentials=creds)

        # Create an execution request object.
        request = {"function": "getFoldersUnderRoot"}

        try:
            # Make the API request.
            response = service.scripts().run(body=request,
                    scriptId=SCRIPT_ID).execute()

            if 'error' in response:
                # The API executed, but the script returned an error.

                # Extract the first (and only) set of error details. The values of
                # this object are the script's 'errorMessage' and 'errorType', and
                # an list of stack trace elements.
                error = response['error']['details'][0]
                print("Script error message: {0}".format(error['errorMessage']))

                if 'scriptStackTraceElements' in error:
                    # There may not be a stacktrace if the script didn't start
                    # executing.
                    print("Script error stacktrace:")
                    for trace in error['scriptStackTraceElements']:
                        print("\t{0}: {1}".format(trace['function'],
                            trace['lineNumber']))
            else:
                # The structure of the result depends upon what the Apps Script
                # function returns. Here, the function returns an Apps Script Object
                # with String keys and values, and so the result is treated as a
                # Python dictionary (folderSet).
                folderSet = response['response'].get('result', {})
                if not folderSet:
                    print('No folders returned!')
                else:
                    print('Folders under your root folder:')
                    for (folderId, folder) in folderSet.items():
                        print("\t{0} ({1})".format(folder, folderId))

        except errors.HttpError as e:
            # The API encountered a problem before the script started executing.
            print(e.content)

Here is where the error comes. It can neither locate token.json nor the app_script_creds.json.

Now with a service account and any normal OAuth2.0 ID, when I create it, I will be given the option to download the credentials.json but here, this is all I seem to be getting, an App Script IDthis is all I seem to be getting, an App Script ID with no edit access or credentials to download as JSON. I created another OAuth ID in the same project as shown in the screenshot which has the edit access and json ready for download. When I used that json file inside the python script, It told me that it was expecting redirect uris, which I don't know for what it is or where to redirect to.

What do I need to do to get this working?

arjunhd
  • 57
  • 6

1 Answers1

0

I adapted some code that I used for connecting to the App Scripts API. I hope it works for you too. The code is pretty much the same thing as this.

You can use from_client_secrets_file since you're already loading these credentials from the file. So, what the code does is look for a token file first. If the token file is not there, it logs in the user (prompting using the Google authorization screen) and stores the new token in the file as pickle.

Regarding the credentials in the Google console you need to pick the Desktop application when creating them because that is basically what a server is.

Note: with this, you can only have one user that will be doing all of these actions. This is because the server script will start a local server on the server machine to authenticate you, your client code will not see any of this.

import logging
import pickle
from pathlib import Path

from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request


class GoogleApiService:

    def __init__(self, , scopes):
        """
        Args:
            scopes: scopes required by the script. There needs to be at least
            one scope specified.
        """

        self.client_secrets= Path('credentials/credentials.json')
        self.token_path = Path('credentials/token.pickle')
        self.credentials = None
        self.scopes = scopes

    def get_service(self):
        self.__authenticate()
        return build('script', 'v1', credentials=self.credentials)

    def __authenticate(self):
        log.debug(f'Looking for existing token in {self.token_path}')
        if self.token_path.exists():
            with self.token_path.open('rb') as token:
                self.credentials = pickle.load(token)

            if self.__token_expired():
                self.credentials.refresh(Request())

        # If we can't find any token, we log in and save it
        else:
            self.__log_in()
            self.__save_token()

    def __log_in(self):
        flow = InstalledAppFlow.from_client_secrets_file(
            self.client_secrets,
            self.scopes
        )
        self.credentials = flow.run_local_server(port=0)

    def __save_token(self):
        with self.token_path.open('wb') as token:
            pickle.dump(self.credentials, token)

    def __token_expired(self):
        return self.credentials and self.credentials.expired and \
            self.credentials.refresh_token


# Example for Google Apps Scripts
def main():            
     request = {'function': 'some_function', 'parameters': params}
     gapi_service = GoogleApiService()
     with gapi_service.get_service() as service:
          response = service.scripts().run(
              scriptId=self.script_id,
              body=request
           ).execute()
    
           if response.get('error'):
               message = response['error']['details'][0]['errorMessage']
               raise RuntimeError(message)
           else:
               return response['response']['result']
vinkomlacic
  • 1,822
  • 1
  • 9
  • 19