Not seeing spreadsheets created with Drive/Sheets API (Python) - python

I have looked all over the forums and documentation to try to understand what I am missing here, but I'm also very new to Python so could be making a simple mistake. I am trying to create a spreadsheet in a shared folder, then share that sheet with my main account (using a service account to create the sheet because not sure if there is another way). I can create the sheet in the shared drive but I can't see to see the newly created sheets even while applying shared permissions without receiving any errors. Here is what I have so far
`
from __future__ import print_function
from oauth2client.service_account import ServiceAccountCredentials
from googleapiclient.discovery import build
SCOPES = [
'https://www.googleapis.com/auth/drive.metadata',
'https://www.googleapis.com/auth/spreadsheets',
'https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/drive.file',
]
# Get credentials
creds = ServiceAccountCredentials.from_json_keyfile_name('creds.json', SCOPES)
service = build('drive', 'v3', credentials=creds)
# Name sheet and provide parent ID of shared folder within shared drive
sheet_metadata = {
'name': 'This will be cools',
'mimeType': 'application/vnd.google-apps.spreadsheet',
'parents': 'SHARED FOLDER ID INSIDE SHARED DRIVE',
}
# Sharing permissions
shared_permissions = {
'role': 'writer',
'type': 'user',
'emailAddress': 'MY PERSONAL EMAIL'
}
results = service.files().create(body=sheet_metadata,fields='id').execute()
permission = service.permissions().create(
fileId=results.get('id'),
body=shared_permissions
)
`
I was having trouble figuring out how to pass the ID of the newly created sheet given that I don't have it because it is being created, but it seems this is working but still cannot see the new sheet when I access the shared folder in the shared drive. Any insight is greatly appreciated.

I was missing .execute() after adjusting permissions. Here is the correct permissions portion with slight adjustment to grab the sheet id right before that works perfect to create a sheet and make it visible.
sheet_id = results.get('id')
permissions = service.permissions().create(
fileId=sheet_id, body=shared_permissions).execute()

Related

How to create and show a folder in google drive using python script?

This code is showing the id of the folder_name but it is not showing up the folder_name in google drive. My purpose is to create and show the folder_name in google drive. How to do it?
import httplib2
from googleapiclient.discovery import build
from oauth2client.service_account import ServiceAccountCredentials
scope = 'https://www.googleapis.com/auth/drive'
credentials = ServiceAccountCredentials.from_json_keyfile_name('credentials.json', scope)
http = httplib2.Http()
drive_service = build('drive', 'v3', http=credentials.authorize(http))
def createFolder(name):
file_metadata = {
'name': name,
'mimeType': 'application/vnd.google-apps.folder'
}
file = drive_service.files().create(body=file_metadata,
fields='id').execute()
print('Folder ID: %s' % file.get('id'))
createFolder('folder_name')
In your script, the folder is created by the service account. I think that this is the reason of your issue. The Google Drive of service account is different from your Google Drive. By this, the folder created by the service account cannot be seen in your Google Drive using the browser. When you want to see the folder created by the service account at the browser, how about the following flow?
Create new folder in your Google Drive of your account.
Please create new folder in your Google Drive. In this case, you can create it using the browser.
Share the created new folder with the email of the service account.
By this, your created folder in your Google Drive can be accessed by the service account.
The email address can be confirmed in the file of credentials.json.
Create a folder to the shared folder using the service account with the script.
By this flow, I think that your goal can be achieved. For this, please modify your script as follows.
From:
file_metadata = {
'name': name,
'mimeType': 'application/vnd.google-apps.folder'
}
To:
file_metadata = {
'name': name,
'mimeType': 'application/vnd.google-apps.folder',
'parents': ['### folder ID ###']
}
Please replace ### folder ID ### with your created folder ID in your Google Drive.
By above modification, the folder created by the script can be seen at your Google Drive using your browser.
Reference:
Files: create of Drive API v3

How do I read Google API credentials from a TOML file with Python?

I'm trying to pull some data using the Google Sheets Api. This is the beginning of the code:
# Import the python libraries.
import gspread
from oauth2client.service_account import ServiceAccountCredentials
from pathlib import Path
import os
import json
# Get JSON_DATA from the build environment.
jsondict = json.loads(os.environ['JSON_DATA'])
# Use creds to create a client to interact with the Google Drive API
scope = ['https://spreadsheets.google.com/feeds','https://www.googleapis.com/auth/drive']
creds = ServiceAccountCredentials.from_json_keyfile_dict(jsondict, scope)
client = gspread.authorize(creds)
# Open the Google Sheet by ID.
sheet1 = client.open_by_key("somekey").sheet1
# Extract all of the records for each row.
sheetdata1 = sheet1.get_all_records()
In the tutorial I am referring to, this is what the author says about the JSON_DATA object:
Note: the ‘JSON_DATA’ variable in the python code is a Netlify build environment variable that I set with JSON format Google API credential information to keep my secret stuff out of the script.
My netlify.toml, which contains the build environment variable has this:
[build]
command = "hugo"
publish = "public"
[build.environment]
HUGO_VERSION = "0.80.0"
[context]
[context.branch-deploy]
command = "hugo -F -b $DEPLOY_PRIME_URL"
[context.deploy-preview]
command = "hugo -F -b $DEPLOY_PRIME_URL"
[context.production]
[context.production.environment]
HUGO_ENV = "production"
I know that to include my credentials downloaded from Google (in a JSON file), I have to put this in the netlify.toml:
[installed]
client_id = "something.apps.googleusercontent.com"
project_id = "someiD"
auth_uri = "https://accounts.google.com/o/oauth2/auth"
token_uri = "whatevergoeshere"
client_secret = "somesecret"
redirect_uris = [ "something", "http://localhost" ]
but how do I read in these credentials for the Python code? That line seems to indicated that it wants a JSON file only.
Okay, so you need to copy paste the contents of the json key file in the actual environment variable itself:
JSON_DATA = '{"key":"secret"}'
Not the most straight forward way, but it works.
In order to maintain sanity I would use the web interface to work with those variables.
https://docs.netlify.com/configure-builds/environment-variables/
The first line parses whatever is in the environment variable to a dict. There's no need in this case to parse the toml itself, as it has already been parsed for you.
The JSON_DATA object simply includes more information than the credentials object received from Google. But the methods called that require the credentials from Google will simply ignore the additional parameters that they don't need. So as long as you keep the names ob the credentials objects the same, methods such as ServiceAccountCredentials.from_json_keyfile_dict() will simply ignore those it doesn't need.

unable to copy a google slide file using google drive api

I want to copy an existing template ppt present in my google drive. Then I want to change the placeholder text to some other text.
here is what I am trying.
from google.oauth2 import service_account
import googleapiclient.discovery
SCOPES = (
'https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/presentations',
)
SERVICE_ACCOUNT_FILE = 'cred.json'
credentials = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES)
SLIDES = discovery.build('slides', 'v1', credentials=credentials)
DRIVE = discovery.build('drive', 'v3', credentials=credentials)
TMPLFILE = 'title slide template'
rsp = DRIVE.files().list(q="name='%s'" % TMPLFILE).execute().get('files')[0]
print(rsp)
DATA = {'name': 'Google Slides API template DEMO'}
print('** Copying template %r as %r' % (rsp['name'], DATA['name']))
DECK_ID = DRIVE.files().copy(body=DATA, fileId=rsp['id']).execute().get('id')
print(DECK_ID)
print('** Replacing placeholder text')
reqs = [
{'replaceAllText': {
'containsText': {'text': '{{text}}'},
'replaceText': final_til[0]
}},
]
SLIDES.presentations().batchUpdate(body={'requests': reqs},
presentationId=DECK_ID).execute()
print('DONE')
But it is not working. I don't get any error. everything works fine but I don't see the new ppt.
Output:
{'kind': 'drive#file', 'id': '15mVjkrT7PkckKetK_q9aYRVxaDcwDdHpAh7xjrAWB6Q', 'name': 'title slide template', 'mimeType': 'application/vnd.google-apps.presentation'} <--- rsp
** Copying template 'title slide template' as 'Google Slides API template DEMO'
11O97tySSNaboW6YRVD62Q7HLs8aVuS2pWyLYXImdSec <-- DECK_ID
** Replacing placeholder text
DONE
If I change
SLIDES.presentations().batchUpdate(body={'requests': reqs},
presentationId=DECK_ID).execute()
to
SLIDES.presentations().batchUpdate(body={'requests': reqs},
presentationId=rsp.get('id')).execute()
then it does replace the text but in my template file which I don't want.
Why is this happening?
I believe your current situation and goal as follows.
From your script,
You are using googleapis for python.
You have already been able to use Drive API and Slides API using the service account.
an existing template ppt present is a Google Slides which is not the PowerPoint file.
Modification points:
From your script and But it is not working. I don't get any error. everything works fine but I don't see the new ppt., I understood that you might want to see the Google Slides copied by the service account at your Google Drive.
When the Google Slides is copied by the service account, the copied Google Slides is put to the Drive of the service account. The Drive of service account is different from your Google Drive. By this, you cannot see the copied Google Slides on your Drive. I thought that this might be the reason of your issue.
In order to see the Google Slides copied by the service account at your Google Drive, for example, the following workarounds can be used.
Share the copied Google Slides with your email of Google account.
In this case, you can see the shared file at the shared folder.
At first, it creates new folder in your Google Drive and share the folder with the email of the service account. And when the Google Slides is copied, it sets the shared folder as the destination folder.
Workaround 1:
In this workaround, it shares the copied Google Slides with your email of Google account. When this is reflected to your script, it becomes as follows.
Modified script:
In this case, new permission is created to the copied file using "Permissions: create".
From:
print(DECK_ID)
print('** Replacing placeholder text')
To:
print(DECK_ID)
permission = {
'type': 'user',
'role': 'writer',
'emailAddress': '####gmail.com' # <--- Please set the email of your Google account.
}
DRIVE.permissions().create(fileId=DECK_ID, body=permission).execute()
print('** Replacing placeholder text')
Workaround 2:
In this workaround, the Google Slides is copied to the shared folder in your Google Drive. Before you use this script, please create new folder and share the folder with the email of service account. When this is reflected to your script, it becomes as follows.
Modified script:
In this case, the metadata is added to the request body of DRIVE.files().copy().
From:
DATA = {'name': 'Google Slides API template DEMO'}
To:
DATA = {'name': 'Google Slides API template DEMO', 'parents': ['###']}
Please set the folder ID of shared folder to ###.
References:
Permissions: create
Files: copy
#Tanaike's answer is great, but there is one other option too:
Account Impersonation
credentials = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES)
delegated_credentials = credentials.with_subject(<email>)
DRIVE = build('drive','v3', credentials = delegated_credentials)
Here is a good overview: Using OAuth 2.0 for Server to Server Applications, specifically this section goes through the code.
Remember to set Domain Wide Delegation in both the GCP console and the Admin console.
The project initialized in the GCP Cloud console has also been granted scopes from within the Admin console > Security > API Controls > Domain wide delegation > Add new
The first thing the script does is build the credentials using from_service_account_file:
credentials = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES)
Then it builds the delegated credentials, that is, the user to be impersonated:
delegated_credentials = credentials.with_subject('<EMAIL>')
From there it can build the service as normal. You can save to the user's drive as if it were the user doing it themselves.
References
Service Accounts
Using OAuth 2.0 for Server to Server Applications

Problem with listing files in Google API (credentials)

I'm trying use google drive api. I created a service account credentials and downloaded from console cloud. The problem is that I'm part of an organization in gsuit and when I try list my files, it's empty, but I have files in my drive.
from apiclient.discovery import build
from oauth2client.service_account import ServiceAccountCredentials
credentials = ServiceAccountCredentials.from_json_keyfile_name(
"credentials.json", scopes=['https://www.googleapis.com/auth/drive'])
service = build('drive', 'v3', credentials=credentials)
print(service.files().list().execute())
What could be?
Actually you are not providing a lot of information but make sure on the api credentials you issued you selected the 'Other UI' option on the field 'Where will you be calling the API from' and you chose 'User data' instead of 'Application data', also the scope should be 'https://www.googleapis.com/auth/drive.readonly.metadata' for listing data.
'https://www.googleapis.com/auth/drive' is correct too but given that it is a gsuite account there can be limitations on generic scopes even for your own data.
Also you should do service = DRIVE.files().list().execute().get('files', [])
for f in files:
print(f['name'])
and enumerate that files array to get the files.
if that doesn't work have a look at the api docs and if you can't figure it out please post more details and try to do some debugging and post the results here.
Edit: Try using the restapi too with the appropriate credentials and see if the files are fetched successfully there. https://developers.google.com/drive/api/v2/reference/files/list

403 insufficient permission for gmail api with python

i am trying to run the gmail API to get email from a specif user.
However i always receive the following error:
googleapiclient.errors.HttpError: https://www.googleapis.com/gmail/v1/users/me/settings/filters?alt=json returned "Insufficient Permission">
I have tried all the different options suggested from changing the scope to enabling the less secure applications in the gmail setting.
The last thing is that my code could be wrong:
from __future__ import print_function
from googleapiclient import discovery
from googleapiclient.discovery import build
from httplib2 import Http
from oauth2client import file, client, tools
# Setup the Gmail API
SCOPES = 'https://mail.google.com/'
store = file.Storage('credentials.json')
creds = store.get()
if not creds or creds.invalid:
flow = client.flow_from_clientsecrets('client_secret.json', SCOPES)
creds = tools.run_flow(flow, store)
service = build('gmail', 'v1', http=creds.authorize(Http()))
# Call the Gmail API
# results = service.users().labels().list(userId='me').execute()
# labels = results.get('labels', [])
# if not labels:
# print('No labels found.')
# else:
# print('Labels:')
# for label in labels:
# print(label['name'])
label_id = 'mine' # ID of user label to add
filter = {
'criteria': {
'from': 'string#test.com'
}#,
#'action': {
#'addLabelIds': [label_id],
#'removeLabelIds': ['INBOX']
#}
}
result = service.users().settings().filters().create(userId='me', body=filter).execute()
print ('Created filter: %s' % result.get('id'))
Could please help me?
Thanks
Just remove credentials.json file from the folder where you run this script from and rerun your script with the final desired list of scopes such as SCOPES = ['https://www.googleapis.com/auth/gmail.send', 'https://www.googleapis.com/auth/gmail.labels']. Authorize, at the resulting pop up browser window, the list of scopes for the desired gmail account. Then you should be all set.
Background story: It is likely you had authorized less permissive scopes (such as readonly) via the browser screen that popped up when your run_flow() ran first time. This would have created a file called credentials.json in the folder where you ran the script. This credentials.json is not again regenerated per your script above. It is just frozen with the first time scope. Just for curiosity, you can open and read the credentials.json and look for the key 'scopes'. Of course, you cannot add more scopes in this file manually and expect newer scopes to work. This has to be regenerated for different set of scopes if any.
Unrelated cautionary note: Avoid the too permissive scope https://mail.google.com/ if at all possible:)
None of the above fixed my problem with moving emails from the inbox into another "box/label". I am not sure if things changed with the gmail api, but what I found I had to do was to change the SCOPES line to the desired level i.e. SCOPES= ['https://www.googleapis.com/auth/gmail.modify'], then delete the token.pickle file (in that order). Following those two changes I reran the python script. Those two steps caused the authorization web page to appear reflecting the new permissions being granted and thus after me giving the OK, recreating a new token.pickle file. Everything ran as desired after that. No more 403 errors and the emails moved to the target box/label. I found the credentials.json file just allowed the script to access the emails but did not have any impact on the actions taken on the emails themselves. I found the article on Medium titled "A Beginner’s Guide to the Gmail API and Its Documentation" by Anton Odman to be the best overall guide to python and the gmail api. BTW, The other "gotcha" is the label created is not necessarily the label id. I recommend running code as mentioned at https://stackoverflow.com/a/55713119/4495318 to make sure you get the right label ID, it seems the label IDs and the label names are not the same except for the system generated labels.
Simply deleting the JSON file created using file.Storage('credentials.json') after you change or add SCOPES will help you and run the program once again.

Categories