I am trying to connect my Python script with my project in Google App Script. I have followed all the insctructions in this guide.
I have of course deployed it as an executable API and have tested it with access to only myself, my organization and anyone options.
When I pass the request with devMode as true, it all works fine. I understand that in this case, it is running the latest saved version. However when I set it to false then I get back the error "message": "Requested entity was not found." which as I understand is trying to run the latest deployed version.
I have also tried going through these questions 1 and 2 but apparently, the problem they had was the opposite where the script wouldn't run with devMode set to true.
Everything else seems to be executing correctly but I cannot find the reason why it wouldn't run the script without being on devMode
This is my script:
"""
Shows basic usage of the Apps Script API.
Call the Apps Script API to create a new script project, upload a file to the
project, and log the script's URL to the user.
"""
from __future__ import print_function
import pickle
import os.path
from googleapiclient import errors
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
# If modifying these scopes, delete the file token.pickle.
SCOPES = ['https://www.googleapis.com/auth/script.projects',
'https://www.googleapis.com/auth/spreadsheets.currentonly'
]
SCRIPT_ID = 'someid'
def main():
"""Calls the Apps Script API.
"""
creds = None
# The file token.pickle stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.pickle'):
with open('token.pickle', 'rb') as token:
creds = pickle.load(token)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token.pickle', 'wb') as token:
pickle.dump(creds, token)
service = build('script', 'v1', credentials=creds)
# Call the Apps Script API
try:
# Create a new project
request = {'function': 'setData'}
response = service.scripts().run(body=request,
scriptId=SCRIPT_ID).execute()
print(response)
except errors.HttpError as error:
# The API encountered a problem.
print(error.content)
if __name__ == '__main__':
main()
I have been able to face the same issue and I have found out that changing the script Id to the Deployment Id worked.
The deployment Id can be found on the Apps Script script:
Open the script
Go to Deploy -> Manage Deployments
Get the Deployment ID from the active deployment
Once you have the deployment, go to the python script and modify the SCRIPT ID with the Deployment Id
Related
I began following the code sample given on googleapis github page to help me understand how the Email audit API works.
The sample initialized the API service like this:
from googleapiclient import sample_tools
service, flags = sample_tools.init(
argv,
"audit",
"v1",
__doc__,
__file__,
scope="https://www.googleapis.com/auth/apps/reporting/audit.readonly",
)
Since for my purposes, I'll need read AND write permissions, I included the scope as 'https://www.googleapis.com/auth/apps/reporting/audit'
Here's how I am trying to initialize the service:
from googleapiclient.discovery import build
from google.oauth2.credentials import Credentials
import os
SCOPES = [
'https://www.googleapis.com/auth/apps.reporting.audit'
]
creds = None
if os.path.exists('token.json'):
creds = Credentials.from_authorized_user_file('token.json', SCOPES)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token.json', 'w') as token:
token.write(creds.to_json())
#now attempting to initialize the audit service
auditService = build('audit', 'v1', credentials=creds)
Now, I am facing two issues here:
I can't access the given scope
After I am prompted to authorize the scopes by logging in to my admin account, I am shown the following message:
Authorization Error
Error 400:
invalid_scope
Some requested scopes cannot be shown: [https://www.googleapis.com/auth/apps.reporting.audit]
For testing, if I only request readonly scopes, I get:
googleapiclient.errors.UnknownApiNameOrVersion: name: audit version: v1
Can someone please guide me through how to properly set up an email monitor using googleapis python client? (Is the given sample on github outdated?)
The sample code mentioned actually refers to the (now deprecated) Enterprise Activity API. This service was moved to Reports API and as Enterprise Activity API, it is only available for Workspace domains.
So indeed, this script is outdated. If you want to use Reports API to manage Activities, you may want to refer to the python quickstart here.
The end goal of your script is unclear, however as you’ve mentioned you’d like to use Email Audit API, I’d recommend following this guide to confirm if this is the right API for your demand. Keep in mind that this API is also only available for Workspace Domains.
Alternatively, I’d also recommend having a look at GMail API capabilities to see if it fits your needs.
I am running a python script on the device with no screen/no browser (ssh access only). The script used offline Google's OAuth:
def get_authenticated_service( self , args):
flow = flow_from_clientsecrets(os.path.dirname(os.path.abspath(__file__)) + "/" + CLIENT_SECRET_FILE,
scope='https://www.googleapis.com/auth/photoslibrary',
message='client_secrets files is missing')
credentials = None
storage = Storage(os.path.dirname(os.path.abspath(__file__)) + "/" + "token-oauth2.json")
credentials = storage.get()
if credentials is None or credentials.invalid:
credentials = run_flow(flow, storage, args)
self.token = credentials.access_token
return build('photoslibrary', 'v1',
http=credentials.authorize(httplib2.Http()))
If token is expired, it says:
Go to the following link in your browser:
https://accounts.google.com/o/oauth2/auth?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fphotoslibrary&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&response_type=code&client_id=12345678-abcdefg.apps.googleusercontent.com&access_type=offline
But now Google says that redirect_uri=ietf:wg:oauth:2.0:oob is invalid.
I tried to manually replace it with 'http://localhost' (taken from CLIENT_SECRET_FILE), but when I take the code from the URL:
http://localhost/?code=4/12345_67899-t_vgPpDmLMGwD75F_w&scope=https://www.googleapis.com/auth/photoslibrary
the script says Authentication has failed: redirect_uri_mismatchBad Request.
I use google api client library 1.6.2.
How should I fix it?
I am running a python script on the device with no screen/no browser
(ssh access only).
The issue you are going to have is that the code needs to be authorized at least once. Run it on your local machine. in order to create the token the first time. Then copy it over to your server. with the file created by storage . there should be a refresh token in there.
Your refresh token should not be expiring. However if it expiring after a week then make sure that you have set your project to production in Google cloud console this way your refresh token will stop expiring.
There is no way around showing that consent screen to the user someone must authorize the code. That is done by showing the consent screen in a web browser.
oob issue
open your credentials.json file and make sure it says this
"redirect_uris":["http://localhost"]
If the oob end point is still there remove it.
The reason for your error is that the old credeintils.json file contained the following
"redirect_uris":["urn:ietf:wg:oauth:2.0:oob","http://localhost"]}
When the python client library ran it would always pick up the first one. That being "urn:ietf:wg:oauth:2.0:oob" which no longer works.
Now when you run your code it will probalby display a 404 error ignore this for now and look at the top bar url bar the code should be there in the response.
404
if you are getting a 404 and want to remove that read Loopback IP address (macOS, Linux, Windows desktop)
Google states.
To receive the authorization code using this URL, your application must be listening on the local web server. That is possible on many, but not all, platforms. However, if your platform supports it, this is the recommended mechanism for obtaining the authorization code.
So you need to figure out how to set up a local web app for an installed application in order to catch the response.
Google has not at this time told us how this is possible with all languages.
Sample
The following example works. It stores the user credentials in tokenPhotos.json. It will reload them as needed. Im not seeing any issues with OOB in this.
# To install the Google client library for Python, run the following command:
# pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib
from __future__ import print_function
import os.path
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
# If modifying these scopes, delete the file token.json.
SCOPES = ['https://www.googleapis.com/auth/photoslibrary']
CREDS = 'C:\YouTube\dev\credentials.json';
def main():
"""Shows basic usage of the Photos v1 API.
Prints the names and ids of the first 20 albums the user has access to.
"""
creds = None
# The file token.json stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('tokenPhotos.json'):
creds = Credentials.from_authorized_user_file('tokenPhotos.json', SCOPES)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
CREDS, SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('tokenPhotos.json', 'w') as token:
token.write(creds.to_json())
try:
service = build('photoslibrary', 'v1', credentials=creds, static_discovery=False)
# Call the Photos v1 API
results = service.albums().list(
pageSize=10, fields="albums(id,title),nextPageToken").execute()
albums = results.get('albums', [])
if not albums:
print('No albums found.')
return
print('albums:')
for album in albums:
print(u'{0} ({1})'.format(album['title'], album['id']))
except HttpError as error:
# TODO(developer) - Handle errors from Photos API.
print(f'An error occurred: {error}')
if __name__ == '__main__':
main()
There is something I don't understand, when it comes to using the Google Drive API.
I'm trying to develop a desktop application that lets the user save his config file to his personal Google Drive, so he can use the same config from any computer.
The Python Quickstart guide has an example how to let a user authenticate, but this example requires the user to have the "credentials.json" file that I created in the Google Console. My understanding is that I should not share this file publicly.
So can I allow users to synchronize their configuration on multiple desktop computers without giving them the app's credentials?
import os.path
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
# If modifying these scopes, delete the file token.json.
SCOPES = ['https://www.googleapis.com/auth/drive.metadata.readonly']
def main():
"""Shows basic usage of the Drive v3 API.
Prints the names and ids of the first 10 files the user has access to.
"""
creds = None
# The file token.json stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.json'):
creds = Credentials.from_authorized_user_file('token.json', SCOPES)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token.json', 'w') as token:
token.write(creds.to_json())
try:
service = build('drive', 'v3', credentials=creds)
# Call the Drive v3 API
results = service.files().list(
pageSize=10, fields="nextPageToken, files(id, name)").execute()
items = results.get('files', [])
if not items:
print('No files found.')
return
print('Files:')
for item in items:
print(u'{0} ({1})'.format(item['name'], item['id']))
except HttpError as error:
# TODO(developer) - Handle errors from drive API.
print(f'An error occurred: {error}')
if __name__ == '__main__':
main()
You should consult the TOS for using the Google apis
Asking developers to make reasonable efforts to keep their private keys private and not embed them in open source projects.
If you are giving your users the copy of your python code. You may not give your users your credetinals.json file. You must instead instruct your users on how to create their own credetinals.json file.
Solution
Solution to not sharing your credentials its to teach the users of your application to create their own credentials.
I have 1 idea. Use selenium and automate process of creating credentials.json. In this way non-technical users not need to do anything and with this automatic process credentials.json shall be created using users google account.
Apply this idea and you shall see good results.
I followed the instructions on Google API quickstart Python so that I can get started quickly. I downloaded the JSON file set with the default name and as a Desktop App. After making sure the each Python library was up to date, I pasted and ran the code on the page.
What happens after many tries is the following:
A browser tab will open
I will be asked to choose an account
I then get the screen where I am asked to allow access
View your Google Spreadsheets
I click Allow and wait
I then get a screen that tells me
Safari can't open the page localhost:random_port ...
&scope=https://www.googleapis.com/auth/spreadsheets.readonly
To make sure that there are no issues, I added the client ID to the list of allowed connections on admin.google.com, but that didn't seem to help.
Does anyone know what could be causing this issue?
Is there something else I need to do on admin.google.com?
I am using VS Code on Mac. My library versions are
google-api-core 1.23.0
google-api-python-client 1.12.4
google-auth 1.22.1
google-auth-httplib2 0.0.4
google-auth-oauthlib 0.4.1
googleapis-common-protos 1.52.0
For reference, here's the code from the top link that I am trying to use.
from __future__ import print_function
import pickle
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
# If modifying these scopes, delete the file token.pickle.
SCOPES = ['https://www.googleapis.com/auth/spreadsheets.readonly']
# The ID and range of a sample spreadsheet.
SAMPLE_SPREADSHEET_ID = '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms'
SAMPLE_RANGE_NAME = 'Class Data!A2:E'
def main():
"""Shows basic usage of the Sheets API.
Prints values from a sample spreadsheet.
"""
creds = None
# The file token.pickle stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.pickle'):
with open('token.pickle', 'rb') as token:
creds = pickle.load(token)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token.pickle', 'wb') as token:
pickle.dump(creds, token)
service = build('sheets', 'v4', credentials=creds)
# Call the Sheets API
sheet = service.spreadsheets()
result = sheet.values().get(spreadsheetId=SAMPLE_SPREADSHEET_ID,
range=SAMPLE_RANGE_NAME).execute()
values = result.get('values', [])
if not values:
print('No data found.')
else:
print('Name, Major:')
for row in values:
# Print columns A and E, which correspond to indices 0 and 4.
print('%s, %s' % (row[0], row[4]))
if __name__ == '__main__':
main()
Just Ran into the same question.
If you use Safari, it will try to access to https://localhost:xxxx/xxxx instead.
Pasting the url to another browser such as Chrome works fine.
to stop Safari keeps forcing HTTPS on localhost, go to (Safari > Preferences > Privacy > Manage Website Data...)
Search for localhost and press Remove.
Try the script again to see if it works.
The port you use in the following statement in the code:
flow.run_local_server(port=0)
should be exactly the same as the one you allow in the consent screen for that project.
Make sure the JS redirect URL points to the same port you will be serving from locally.
See google_auth_oauthlib.flow for more information about the module.
For anyone else having this issue. The issue is related to the OS/software on OS. I had no issues running this on a fresh install of Mac OS.
In my MacOS settings I changed my default browser to Google Chrome, and this same authentication flow began working.
this is my first contribution here.
I'm trying to access Gmail through a python script. To do so, I've created a Google Apps Script function and used the Apps Script API between the 2.
(This doc displays what I'm trying to do)
So the python script correctly accesses the API, but fails to execute the function.
While it works in the Script Editor, in Python it raises a permissions issue:
'errorMessage': 'Exception: The script does not have permission to perform that action.
Required permissions: (
https://www.googleapis.com/auth/gmail.labels ||
https://www.googleapis.com/auth/gmail.metadata ||
https://www.googleapis.com/auth/gmail.readonly ||
https://www.googleapis.com/auth/gmail.modify ||
https://mail.google.com/
)',
'errorType': 'ScriptError'
I guess it is related to the Client ID OAuth, since I was not able to find where to grant it permissions. I've just :
created the credentials in Google Cloud Platform,
exported it as creds.json in my python script folder.
Here is my code, almost copy pasted from a tutorial:
import pickle
import os.path
from googleapiclient import errors
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
# Here I've edited the scopes of authorizations required
SCOPES = [
"https://www.googleapis.com/auth/gmail.labels",
"https://www.googleapis.com/auth/gmail.metadata",
"https://www.googleapis.com/auth/gmail.readonly",
"https://www.googleapis.com/auth/gmail.modify",
"https://mail.google.com/"
]
def get_scripts_service():
creds = None
# The file token.pickle stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.pickle'):
with open('token.pickle', 'rb') as token:
creds = pickle.load(token)
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
# Here I've placed the downloaded credentials .json file
flow = InstalledAppFlow.from_client_secrets_file(
'creds.json', SCOPES)
creds = flow.run_local_server(port=0)
with open('token.pickle', 'wb') as token:
pickle.dump(creds, token)
return build('script', 'v1', credentials=creds)
service = get_scripts_service()
API_ID = # Here i've pasted my API_ID
request = {"function": # Here i've pasted my functionName}
try:
response = service.scripts().run(body=request, scriptId=API_ID).execute()
print (response)
except errors.HttpError as error:
# The API encountered a problem.
print(error.content)
How do I grant permissions to my script?
Simple as Aerials said! Thanks.
It was because the Client ID was created before I edited the scopes. I've deleted the token and created a new one.