Access spreadsheets inside shared folder - python-3.x

Overview
I am creating a folder and spreadsheets using API I can access all files inside that folder using google spreadsheet API
(problem) But then I upload the file from the front, Now this file can not be accessed via spreadsheet API, I have given full permission(owner) to the user.
So basically the problem is that I want to let other user drop spreadsheets in my shared drive and want to access these files via python API
error message "This operation is not supported for this document"
JSON RESPONSE
{
"error": {
"code": 403,
"message": "The request is missing a valid API key.",
"status": "PERMISSION_DENIED"
}
}
Background:
I am using python to create folders and sheets, and I wanted to share the folder for other users to update the spreadsheet files so I gave permissions to other users, but when they dropped the files in the drive I can access the file metadata but not the contents of the spreadsheets
Relevant Code:
`SCOPES =
['https://www.googleapis.com/auth/drive.appdata','https://www.googleapis.com/auth/spreadsheets', 'https://www.googleapis.com/auth/drive']
sheet_service = build('sheets', 'v4', credentials=get_Service())
drive_service = build('drive', 'v3',credentials=get_Service())
def get_values(self,SAMPLE_SPREADSHEET_ID, SAMPLE_RANGE_NAME):
sheet = sheet_service.spreadsheets()
result = sheet.values().get(spreadsheetId=SAMPLE_SPREADSHEET_ID,
range=SAMPLE_RANGE_NAME).execute()`
Expected help > bypass these permission restrictions
I have noticed something when I drop the file the sheet id is different from when I create it with API or from google drive

{ "error": { "code": 403, "message": "The request is missing a valid API key.", "status": "PERMISSION_DENIED" } }
Means that you are not sending any authorization with your request. Your request needs to be authorized before you can do anything. Notice how the credentials have been added to the service variable in the code below.
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()
Code ripped from Python quickstart
Also note that you cant create a folder in Google drive with the Sheets api you will need to use the Google drive api to create files. Sheets just lets you edit them.
converting files
When you upload the files to Google drive you need to convert them to sheet at that time Import doc types
file_metadata = {
'name': 'My Report',
'mimeType': 'application/vnd.google-apps.spreadsheet'
}
media = MediaFileUpload('files/report.csv',
mimetype='text/csv',
resumable=True)
file = drive_service.files().create(body=file_metadata,
media_body=media,
fields='id').execute()
print 'File ID: %s' % file.get('id')

Related

HttpError 403 when requesting None returned "Insufficient Permission

Uploading from e-mail's attachments from Gmail to Google Drive:
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
import io
import base64
from googleapiclient.http import MediaIoBaseUpload
from time import sleep
q='has:attachment'
maxResults=int(input("Please specify the number of emails with attachments that you would like to see:"))
#for drive api---------------------------------------------------------------------------------
# If modifying these scopes, delete the file token.pickle.
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_drive.pickle'):
with open('token_drive.pickle', 'rb') as token_drive:
creds = pickle.load(token_drive)
# 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_drive.json', 'https://www.googleapis.com/auth/drive.metadata.readonly')
creds1 = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token_drive.pickle', 'wb') as token_drive:
pickle.dump(creds, token_drive)
drive_service= build('drive', 'v3', credentials=creds1)
sleep(5)
# for gmail api---------------------------------------------------------------------------------
# If modifying these scopes, delete the file token.pickle.
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', 'https://www.googleapis.com/auth/gmail.readonly')
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('gmail', 'v1', credentials=creds)
# Call the Gmail API
results = service.users().labels().list(userId='me').execute()
#Get Messages
results = service.users().messages().list(userId='me',q=q, maxResults=maxResults ,labelIds=['INBOX']).execute()
messages = results.get('messages', [])
def create_folder_in_drive(service,folder_name,parent_folder=[]):
file_metadata ={
'name': folder_name,
'parents': parent_folder,
'mimeType':'application/vnd.google-apps.folder'
}
for message in messages:
msg = service.users().messages().get(userId='me',metadataHeaders=['parts'], id=message['id']).execute()
messageID=msg['threadId']
messageSubject='(No Subject)({0})'.format(messageID)
msgdetail=msg.get('payload')
for item in msgdetail['headers']:
if item['name']=='Subject':
if item['value']:
messageSubject='{0} ({1})'.format(item['value'],messageID)
else:
messageSubject='(No Subject)({0})'.format(messageID)
print("messagesubject:" , messageSubject )
#create drive folder
folder_id=create_folder_in_drive(drive_service,messageSubject)
if 'parts' in msgdetail:
for msgPayload in msgdetail['parts']:
mime_type=msgPayload['mimeType']
file_name=msgPayload['filename']
body=msgPayload['body']
print(body)
if 'attachmentId' in body:
attachment_id=body['attachmentId']
response=service.users().messages().attachments().get(
userId='me',
messageId=msg['id'],
id=attachment_id
).execute()
file_data=base64.urlsafe_b64decode(
response.get('data').encode('UTF-8'))
fh=io.BytesIO(file_data)
file_metadata= {
'name':file_name,
'parents':[folder_id]
}
media_body=MediaIoBaseUpload(fh,mimetype=mime_type,chunksize=1024*1024,resumable=True)
file=drive_service.files().create(
body= file_metadata,
media_body=media_body,
fields='id'
).execute()
Hello friends, if I delete the token.pickle and token_drive.pickle files (these files are created separately from google cloud) in the file directory and run the code:
"ResumableUploadError: <HttpError 403 when requesting None returned "Insufficient Permission: Request had insufficient authentication scopes.". Details: "[{'domain': 'global', 'reason': 'insufficientPermissions', 'message': 'Insufficient Permission: Request had insufficient authentication scopes.'}]"> error", when I run the code without deleting the pickle files, I get the error which is "NameError: name 'service' is not defined."
It seems like a problem with authentication of Gmail and Drive at the same time because media_body and file_metadata return a value, but I couldn't solve the problem.
I'm on a phone and eye-balling your code.
You should be able to get a single token with scopes sufficient for Gmail and Drive rather than juggle multiple tokens.
Unpickling the objects may be causing some sort of collision too. Even though it's just for you, I'd recommend avoiding pickling as much as possible.
Here's a Google sample that shows the OAuth flow flow for Gmail using Python. The sample writes|reads tokens to disk as a file. Try this code with the 2 scopes.
#for gmail and drive api------------------------------------------------------------
# If modifying these scopes, delete the file token.pickle.
SCOPES=['https://www.googleapis.com/auth/gmail.readonly','https://www.googleapis.com/auth/drive','https://www.googleapis.com/auth/drive.file','https://www.googleapis.com/auth/drive.metadata']
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('gmail', 'v1', credentials=creds)
drive_service = build('drive', 'v3', credentials=creds)

Getting 401 error when trying to delete gmail messages through API

I have gotten inspiration from
https://developers.google.com/gmail/api/quickstart/python and https://developers.google.com/gmail/api/v1/reference/users/labels/list#examples to get to the following code
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
from apiclient import errors
import requests as req
# If modifying these scopes, delete the file token.pickle.
SCOPES = ['https://mail.google.com/']
def chunks(l, n):
for i in range(0, len(l), n):
yield l[i:i + n]
def ListMessagesWithLabels(service, user_id, label_ids=[]):
"""List all Messages of the user's mailbox with label_ids applied.
Args:
service: Authorized Gmail API service instance.
user_id: User's email address. The special value "me"
can be used to indicate the authenticated user.
label_ids: Only return Messages with these labelIds applied.
Returns:
List of Messages that have all required Labels applied. Note that the
returned list contains Message IDs, you must use get with the
appropriate id to get the details of a Message.
"""
try:
response = service.users().messages().list(userId=user_id,
labelIds=label_ids).execute()
messages = []
if 'messages' in response:
messages.extend(response['messages'])
while 'nextPageToken' in response:
page_token = response['nextPageToken']
response = service.users().messages().list(userId=user_id,
labelIds=label_ids,
pageToken=page_token).execute()
messages.extend(response['messages'])
return messages
except errors.HttpError as error:
print
'An error occurred: %s' % error
def main():
"""Shows basic usage of the Gmail API.
Lists the user's Gmail labels.
"""
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('gmail', 'v1', credentials=creds)
# 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'])
if (label["id"].upper() != label["id"]):
messages = ListMessagesWithLabels(service, 'me', [label['id']])
if messages is not None:
usr_id = 'me'
ids = [message["id"] for message in messages]
for x in chunks(ids, 1000):
post_req = req.post(f"https://www.googleapis.com/gmail/v1/users/{usr_id}/messages/batchDelete", data={"ids":x})
if post_req.status_code == 200:
print("all good")
if __name__ == '__main__':
main()
The objective is to go through each label and delete all the messages.
All my POST requests got denied because I am not 'authorized' even though when I start the program I go through the Athentication in the browser etc.
How am I supposed to construct my POSTs to be able to achieve what I want to do ?
When trying to delete the messages, you should be using the service you previously built. That's how you authenticated in the first place. When trying to use batchDelete here:
post_req = req.post(f"https://www.googleapis.com/gmail/v1/users/{usr_id}/messages/batchDelete", data={"ids":x})
You're just doing a basic request to the specified endpoint, you're not following the OAuth process. And because you're not accessing a public resource, you are getting an authorization error.
You should be using something along the following lines instead:
messagesToDelete = {
"ids": [
"messageID1",
"messageID2",
# ... rest of messages to delete
]
}
service.users().messages().batchDelete(userId="me", body=messagesToDelete).execute()
Reference:
Users.messages: batchDelete

Gmail API Reading credentials 'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte

I think there is an issue with Gmail API and Python3.
The original code in the documentation is in Python2, but for several reasons, including that my application is already working with Python3, I was passing the code to python3.
So... after sorting several issues, including a 400 request error, (which was apparently that the auth I provided to google wasn't corretly done) I'm facing (I hope) the final issue that apparently I'm trying to read a file but
Even just doing token.read() generates the same issue.
The token.pickle file is autogenerated once you've authorized google to access your email account and that the app can send emails automatically.
I know the credentials.json file is correct, as that's the point to tell google who you are and it always is reading my credentials correctly, redirecting me to give authorization to my app.
Here's the app to send emails, I think it's pretty straightforward, I followed the documentation and looked at other issues like this one and finally got this far:
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
from googleapiclient.errors import HttpError
# from httplib2 import Http
# from oauth2client import client, tools, file
import base64
from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import mimetypes
from apiclient import errors
SCOPES = 'https://mail.google.com/'
def SendMessage(service, user_id, message):
"""Send an email message.
Args:
service: Authorized Gmail API service instance.
user_id: User's email address. The special value "me"
can be used to indicate the authenticated user.
message: Message to be sent.
Returns:
Sent Message.
"""
try:
message = (service.users().messages().send(userId=user_id, body=message)
.execute())
print(f'Message Id: {message["id"]}')
return message
except errors.HttpError as error:
print(f'An error occurred: {error}')
def CreateMessage(sender, to, subject, message_text):
"""Create a message for an email.
Args:
sender: Email address of the sender.
to: Email address of the receiver.
subject: The subject of the email message.
message_text: The text of the email message.
Returns:
An object containing a base64url encoded email object.
"""
message = MIMEText(message_text)
message['to'] = to
message['from'] = sender
message['subject'] = subject
message_bytes = message.as_string().encode('utf-8')
# return { 'raw': base64.urlsafe_b64encode(message.as_string()) }
# return { 'raw': base64.urlsafe_b64encode(message_bytes) }
raw = base64.urlsafe_b64encode(message_bytes)
return raw.decode()
def main():
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', 'r') 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('gmail', 'v1', credentials=creds)
# Create
message = CreateMessage('my-email#gmail.com', 'destination#gmail.com', 'Testing Gmail API', 'Hi GMAIL API')
# Send
SendMessage(service, "me", message)
if __name__ == '__main__':
main()
I truly don't know what to do here, if someone has already solved this issue and how.
THANKS!
In your script, when token.pickle is not created, Gmail API can be used by creating token.pickle at 1st authorization. But an error of 'raw' RFC822 payload message string or uploading message via /upload/* URL required occurs at message = (service.users().messages().send(userId=user_id, body=message).execute()). So in this case, please modify as follows.
From:
raw = base64.urlsafe_b64encode(message_bytes)
return raw.decode()
To:
raw = base64.urlsafe_b64encode(message_bytes).decode('utf-8')
return {'raw': raw}
After above modification, when your script is run as the 2nd run, when token.pickle, which has already been created, is read, an error occurs. I think that this error is in your title. In this case, please modify as follows.
From:
with open('token.pickle', 'r') as token:
To:
with open('token.pickle', 'rb') as token:
By this, I think that the script works.
By the way, if an error occurs when the code is retrieved, also please modify as follows.
From:
creds = flow.run_local_server(port=0)
To:
creds = flow.run_local_server()
Note:
About the authorization, you can see the sample script at the Quickstart of the official document.
If this was not the direction of your issue, I apologize.

I am trying to figure out how to use the google drive api along with python to update file permissions as well as ownership

I took the code from the Google Drive API page for managing shares and did some modifications to it to be able to transfer the ownership of a specific file but I keep getting back the following error
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
SCOPES = 'https://www.googleapis.com/auth/drive'
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()
# Save the credentials for the next run
with open('token.pickle', 'wb') as token:
pickle.dump(creds, token)
file_id = 'XXXXXX'
drive_service = build('drive', 'v3', credentials=creds)
def callback(request_id, response, exception):
if exception:
# Handle error
print(exception)
else:
print("Permission Id: %s" % response.get('id'))
batch = drive_service.new_batch_http_request(callback=callback)
user_permission = {
'type': 'user',
'role': 'writer',
'emailAddress': 'XXX#gmail.com'
}
batch.add(drive_service.permissions().create(
fileId=file_id,
body=user_permission,
fields='id',
))
domain_permission = {
'type': 'domain',
'role': 'reader',
'domain': 'example.com'
}
batch.add(drive_service.permissions().create(
fileId=file_id,
body=domain_permission,
fields='id',
))
batch.execute()
The error I get is that
<HttpError 403 when requesting https://www.googleapis.com/drive/v3/files/1w43iXVO04QhDl-eWHi75ImSdxu-_V5xp/permissions?fields=id&alt=json returned "Insufficient Permission: Request had insufficient authentication scopes.">
<HttpError 403 when requesting https://www.googleapis.com/drive/v3/files/1w43iXVO04QhDl-eWHi75ImSdxu-_V5xp/permissions?fields=id&alt=json returned "Insufficient Permission: Request had insufficient authentication scopes.">
But i am not sure if its an issue with the code itself or with how I setup the credentials for the API. does someone have experience with the Google Drive API for Python and can give me a pointer on what I am doing wrong?
Modify scopes:
SCOPES = ['https://www.googleapis.com/auth/drive.file', 'https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/drive.appdata']
Delete the generated token.pickle file.
Then rerun the code.
Specific permissions can be viewed here: https://developers.google.com/drive/api/v3/reference/files/create?authuser=3#auth
May be obvious but your credentials don't have authorisation.
Have you made sure your loading the correct credentials.json and scope?
You can get the same error using the quickstart credentials for different api products.
try commenting out this out when you try to load the correct credentials and scope.
if os.path.exists('token.pickle'):
with open('token.pickle', 'rb') as token:
creds = pickle.load(token)

How do you programmatically get a list of Google Forms (with ids) using Google Forms API?

I would like to know if it is possible to get the list of a Google Drive user's Forms (not the Docs, Spreadsheets, ...) using the Forms Service or the Google Drive API.
The reason why I am not sure is because there is no indication on how to do this on the Forms Service page, and on the Drive API's page the indications are very general. They don't take into account the fact that I will be using a OAuth2 token with a 'forms' scope and not a 'drive' scope.
/* List all Google Forms in your Google Drive */
function getGoogleFormsList() {
var files = DriveApp.getFilesByType(MimeType.GOOGLE_FORMS);
while (files.hasNext()) {
var file = files.next();
Logger.log("%s %s %s", file.getName(), file.getId(), file.getUrl());
}
}
Python solution:
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/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(
'client_secrets.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:
page_token = None
drive_service = build('drive', 'v3', credentials=creds)
while True:
response = drive_service.files().list(
# You can use MIME types to filter query results
q="mimeType='application/vnd.google-apps.form'",
spaces='drive',
fields='nextPageToken, files(id, name)',
pageToken=page_token
).execute()
for file in response.get('files', []):
# Process change
print ('Found file: %s (%s)' % (file.get('name'), file.get('id')))
page_token = response.get('nextPageToken', None)
if page_token is None:
break
except HttpError as error:
# TODO(developer) - Handle errors from drive API.
print(f'An error occurred: {error}')
if __name__ == '__main__':
main()

Resources