Cannot transfer google calendar events using Google API Python SDK - python

I have created function that is supposed to move all events from one Google calendar to another. Here is how it looks like:
def merge_calendar(email_from, email_to, service):
off_board_user_calendar = service.events().list(calendarId=email_from).execute()
off_board_user_events = off_board_user_calendar.get('items', [])
# I tried to use this code, to resolve this "You need to have reader access to this calendar." error,
# but it didn't work
#
# rule = {
# 'scope': {
# 'type': 'user',
# 'value': email_from,
# },
# 'role': 'reader'
# }
#
# created_rule = service.acl().insert(calendarId=email_from, body=rule).execute()
# print(f'Updated ACL rule {created_rule}')
for event in off_board_user_events:
updated_event = service.events().move(
calendarId=email_from,
eventId=event['id'],
destination=email_to
).execute()
print(f'Event has been transferred: {updated_event["updated"]}')
print('All events have been transferred successfully.')
Right after execution I get this error - "You need to have reader access to this calendar.". And so, as see from comment, I tried to resolve this error, but this commented code brings me another error - just "Forbidden".
I am not quite sure what I am doing wrong. How can I transfer all events from on calendar to another
Also I think it is important to mention how I create service entity. I was trying to do this using 2 methods:
Normal credentials:
creds = None
if os.path.exists('token.json'):
creds = Credentials.from_authorized_user_file('token.json', SCOPES[api_name])
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_secret_file, SCOPES[api_name])
creds = flow.run_local_server()
with open('token.json', 'w') as token:
token.write(creds.to_json())
and using Google Service Account
if delegated_user is not None:
credentials = service_account.Credentials.from_service_account_file(
'service.json', scopes=SCOPES[api_name])
creds = credentials.with_subject(delegated_user)
Both didn't work.
PS.
Calendar scope I have is 'https://www.googleapis.com/auth/calendar'.
Thanks in advance!

Some things that you might look at:
Check the Domain Wide Delegation in your admin console and make sure that the service account ID is the same service account that you are using in your code.
Add the scope that you mentioned in your question 'https://www.googleapis.com/auth/calendar' which is the most restricted scope on the Calendar API.
Try to delegate the user with credentials.create_delegated(email) instead of credentials.with_subject(delegated_user).

Actually, there is no need to transfer event by event. It'll be enough just to update ACL, just like this:
def merge_calendar(email_from, email_to, service):
rule = {
'scope': {
'type': 'user',
'value': email_to,
},
'role': 'owner'
}
service.acl().insert(calendarId=email_from, body=rule).execute()
You will just get an email with proposition to add this calendar to your Google Calendar.
Talking about authentication I had this user delegation:
credentials = service_account.Credentials.from_service_account_file(
'service.json', scopes=['https://www.googleapis.com/auth/calendar'])
creds = credentials.with_subject(email_from)
References
Google Service Account

Related

How to check if google sheet exist? Python

"""
BEFORE RUNNING:
---------------
1. If not already done, enable the Google Sheets API
and check the quota for your project at
https://console.developers.google.com/apis/api/sheets
2. Install the Python client library for Google APIs by running
`pip install --upgrade google-api-python-client`
"""
# TODO: Change placeholder below to generate authentication credentials. See
# https://developers.google.com/sheets/quickstart/python#step_3_set_up_the_sample
#
# Authorize using one of the following scopes:
# 'https://www.googleapis.com/auth/drive'
# 'https://www.googleapis.com/auth/drive.file'
# 'https://www.googleapis.com/auth/spreadsheets'
SCOPES = ['https://www.googleapis.com/auth/spreadsheets']
creds = None
if os.path.exists('google.json'):
creds = Credentials.from_authorized_user_file('google.json', SCOPES)
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.json',SCOPES)
creds = flow.run_local_server(port=0)
with open('google.json', 'w') as token:
token.write(creds.to_json())
service = discovery.build('sheets', 'v4', credentials=creds)
spreadsheet_body = {
'sheets': [{
'properties': {
'title': str(files[0])
}
}]
}
request = service.spreadsheets().create(body=spreadsheet_body)
if request == str(files[0]):
pass
else:
response = request.execute()
pprint(response)
How can I create condition? if google sheet name exist if TRUE then don't proceed to create. I read the documentation and I didn't see any possible answer or I am just mistaken to understand the documentation please help thank you.
I believe your goal is as follows.
You want to check whether a file (Google Spreadsheet) is existing in Google Drive using a filename.
You want to achieve this using googleapis for python.
In this case, how about the following sample script? In this case, in order to search the file using the filename, Drive API is used.
Sample script:
filename = str(files[0])
service = build("drive", "v3", credentials=creds)
results = service.files().list(pageSize=1, fields="files(id, name)", q="name='" + filename + "' and mimeType='application/vnd.google-apps.spreadsheet' and trashed=false",).execute()
files = results.get("files", [])
if not files:
# When the file of filename is not found, this script is run.
print("No files were found.")
else:
# When the file of filename is found, this script is run.
print("Files were found.")
When this script is run, you can check whether the file is existing in Google Drive in the filename.
In this case, please add a scope of "https://www.googleapis.com/auth/drive.metadata.readonly" as follows. And, please reauthorize the scopes. So, please remove the file of google.json and run the script again.
SCOPES = [
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/drive.metadata.readonly",
]
From your question, I couldn't know whether you are trying to use the script in the shared Drive. So, in this modification, the script cannot be used for the shared Drive. But, if you want to use this script in the shared Drive, please include corpora="allDrives", includeItemsFromAllDrives=True, supportsAllDrives=True to the request.
Reference:
Files: list

SDK, Trying to call Members from Group Gmail and Update

I am trying to create a call that gets all the group Gmail emails so that I can update those that aren't there and delete those that shouldn't be. I am currently trying the below code and I'm getting a scope error.
# If modifying these scopes, delete the file token.json.
SCOPES = ['https://www.googleapis.com/auth/admin.directory.group.members', 'https://www.googleapis.com/auth/admin.directory.group']
def main():
"""Shows basic usage of the Admin SDK Directory API.
Prints the emails and names of the first 10 users in the domain.
"""
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())
service = build('admin', 'directory_v1', credentials=creds)
# Call the Admin SDK Directory API
print('Getting the members of Hospitality Team')
response_group = service.groups().list(customer='my_customer').execute()
for group in response_group['groups']:
print(group['email'])
Solution:
You could do the following:
List all your groups via Groups: list.
For each group, check whether it has members.
If the group has members, retrieve its members via Members: list.
For each desired member coming from the other API, check if it already exists in the group. If it doesn't exist, add it to your group via Members: insert.
For each current member in the group, check if it's one of the desired members. If it's not, delete it via Members: delete.
If the group does not have members, add all desired members to the group via Members: insert.
Code snippet:
def updateGroupMembers(service):
ideal_member_emails = ["member_1#example.com", "member_2#example.com", "member_3#example.com"]
response_group = service.groups().list(customer='my_customer').execute()
for group in response_group['groups']:
group_email = group['email']
response_members = service.members().list(groupKey=group_email).execute()
if "members" in response_members:
current_member_emails = list(map((lambda member : member["email"]), response_members["members"]))
for ideal_member_email in ideal_member_emails:
if ideal_member_email not in current_member_emails:
payload = {
"email": ideal_member_email
}
service.members().insert(groupKey=group_email, body=payload).execute()
for current_member_email in current_member_emails:
if current_member_email not in ideal_member_emails:
service.members().delete(groupKey=group_email, memberKey=current_member_email).execute()
else:
for ideal_member_email in ideal_member_emails:
payload = {
"email": ideal_member_email
}
service.members().insert(groupKey=group_email, body=payload).execute()
Notes:
The scopes you are providing should be enough for these calls. If you edited those scopes after last authenticating, remove the old token.json and authenticate again.
Make sure the authenticated user has edit access to these groups.
Here I'm assuming the desired list of members is the same for all groups. I'm also assuming you have a list of these emails (currently ideal_member_emails). If that's not the case, please edit the provided script according to your preferences.
If your list of groups and members is large enough, you should iteratively fetch the different page results for your list requests. See this related answer (regarding Users: list, but the process is identical) for more information on how to do this.
Reference:
Python library: members

Can not retrieve thumbnailLink from google drive API

I am trying to get the thumbnailLink from files from a shared drive using python and the google Drive API, however, the file information does not include the thumbnailLink (although for most files the hasThumbnail field, which i do get as a field for the file, has a value of true)
I have looked around a lot and none of the solutions i have found seem to work (although this is my first python project as well as my first google drive api project, so i might just be ignorant of what i am doing)
What i have tried:
- setting the scope to 'https://www.googleapis.com/auth/drive' (was ..drive.metadata.readonly before)
- using a wildcard as such: results = drive.files().list(pageSize=10, fields="*",blablabla...). If i for instance try fields="thumbnailLink" it doesn't find any files.
- after getting the list, i tried using the id of each file from that list to do file = service.files().get(fileId=item_id, supportsAllDrives=True, fields="*").execute() but the same happens, i have many fields including the hasThumbnail field which is set to true, yet no thumbnail link.
- i tried using the "Try this API" console on the official website, where i did in fact get the thumbnailLink!! (with the same parameters as above). So i do not understand why this is missing when requested from my application.
Edit (code):
i have one method like so
SCOPES = ['https://www.googleapis.com/auth/drive']
def getDrive():
"""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.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=53209)
# Save the credentials for the next run
with open('token.pickle', 'wb') as token:
pickle.dump(creds, token)
service = build('drive', 'v3', credentials=creds)
return service
then i call it from here and also get the files:
def getFiles(request):
drive = getDrive()
# Call the Drive v3 API
results = drive.files().list(
pageSize=10, fields="*", driveId="blabla", includeItemsFromAllDrives=True, corpora="drive", supportsAllDrives=True).execute()
items = results.get('files', [])
getItems = []
for item in items:
item_id = item['id']
getItems.append(drive.files().get(fileId=item_id, supportsAllDrives=True, fields="*").execute())
if not items:
print('No files found.')
else:
print('Files:')
print(getItems)
for item in items:
# print(u'{0} ({1})'.format(item['name'], item['id']))
print(item)
return render(request, "index.html", {'files': getItems})
Also, yes, i do use a service account, i can retrieve all the files i need, just not the thumbnailLink.
I don't think it makes sense to call list() and then also get() but i had read that the problem could be solved through the get() method, which in my case did not work.
The issue is in the structure of the response
If you specify fields="*", the response would be something like
{
"kind": "drive#fileList",
...
"files": [
{
"kind": "drive#file",
...
"hasThumbnail": true,
"thumbnailLink": "XXX",
"thumbnailVersion": "XXX"
...
}
..
]
}
So, thumbnailLink is nested inside of files.
In order to retrieve it specify:
fields='files(id, thumbnailLink)'

How can I change the owner of a Google Sheets spreadsheet?

With the following, I can programmatically create a spreadsheet in Google sheets, but the owner of the sheet is the developer account (a crazy string ending in "gserviceaccount.com"), and my normal account can't view the spreadsheet. What else do I need to do in order to add Google users to the read/write permissions?
from oauth2client.service_account import ServiceAccountCredentials
from googleapiclient import discovery
# ... json_key is the json blob that has the credentials
scope = ['https://spreadsheets.google.com/feeds']
credentials = ServiceAccountCredentials.from_json_keyfile_dict(json_key, scope)
service = discovery.build('sheets', 'v4', credentials=credentials)
spreadsheet = {
"properties": {"title": "my test spreadsheet"}
}
service.spreadsheets().create(body=spreadsheet).execute()
Edit:
I tried changing the scope to ['https://www.googleapis.com/auth/drive'] but the answer below still doesn't work for me. When I run
print [xx for xx in dir(service) if not xx.startswith('_')]
I get
['new_batch_http_request', u'spreadsheets']
In other words, permissions() isn't a method in service as I have service defined. What should I be doing differently?
I figured it out from reading the comment left by Chris. All that was missing from his comments is you do in fact need to use particular scopes in his drive_service. Notice the changes in scope I use to build the different objects:
from oauth2client.service_account import ServiceAccountCredentials
from googleapiclient.discovery import build
key = '/path/to/service_account.json'
# Build 'Spreadsheet' object
spreadsheets_scope = [ 'https://www.googleapis.com/auth/spreadsheets' ]
sheets_credentials = ServiceAccountCredentials.from_json_keyfile_name(key, spreadsheets_scope)
sheets_service = build('sheets', 'v4', credentials=sheets_credentials)
# returns 'Spreadsheet' dict
# https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets#resource-spreadsheet
spreadsheet = sheets_service.spreadsheets().create(
body={
"properties": {
'title': 'spreadsheets test',
},
"sheets": [],
}
).execute()
# id for the created file
spreadsheetId = spreadsheet['spreadsheetId']
# url of your file
spreadsheetUrl = spreadsheet['spreadsheetUrl']
# Build 'Permissions' object
drive_scope = [ 'https://www.googleapis.com/auth/drive' ]
drive_credentials = ServiceAccountCredentials.from_json_keyfile_name(key, drive_scope)
drive_service = build('drive', 'v3', credentials=drive_credentials)
# returns 'Permissions' dict
permissions = drive_service.permissions().create(
fileId=spreadsheetId,
transferOwnership=True,
body={
'type': 'user',
'role': 'owner',
'emailAddress': 'example#email.com',
}
).execute()
# apply permission
drive_service.files().update(
fileId=spreadsheetId,
body={'permissionIds': [permissions['id']]}
).execute()
print ('\nOpen me:\n\n%s\n' % spreadsheetUrl)
So the logic is, a 'Spreadsheet Resource' is made from build with all its properties and sheet data, with the owner set to your service account. Next, a 'Drive Resource' is made, this is the Resource with the permissions() method. execute() returns a newly created permissions id used to update() the spreadsheet file.
Service is just a generic name for the result of a discovery.build call. In this case, not having the 'permissions' method is just that its not available on the same service. The following code should be sufficient if changing owner isn't required. To add someone with read and write access, the following works for me:
def grant_permissions(spreadsheet_id, writer):
drive_service = discovery.build('drive', 'v3')
permission = drive_service.permissions().create(
fileId=spreadsheet_id,
body={
'type': 'user',
'role': 'writer',
'emailAddress': writer,
}
).execute()
drive_service.files().update(
fileId=spreadsheet_id,
body={'permissionIds': [permission['id']]}
).execute()
To actually change the owner, the transfer ownership flag must be set:
def change_owner(spreadsheet_id, writer):
drive_service = discovery.build('drive', 'v3')
permission = drive_service.permissions().create(
fileId=spreadsheet_id,
transferOwnership=True,
body={
'type': 'user',
'role': 'owner',
'emailAddress': writer,
}
).execute()
drive_service.files().update(
fileId=spreadsheet_id,
body={'permissionIds': [permission['id']]}
).execute()
The service account being used must have the right permissions though. I believe the ones that worked for me was checking the g suite box when first creating the service account.
Try to use the method Permissions: insert from the documentation. You will be able to insert a permission for a file or a Team Drive.
Here is the sample code provided from the documentation:
from apiclient import errors
# ...
def insert_permission(service, file_id, value, perm_type, role):
"""Insert a new permission.
Args:
service: Drive API service instance.
file_id: ID of the file to insert permission for.
value: User or group e-mail address, domain name or None for 'default'
type.
perm_type: The value 'user', 'group', 'domain' or 'default'.
role: The value 'owner', 'writer' or 'reader'.
Returns:
The inserted permission if successful, None otherwise.
"""
new_permission = {
'value': value,
'type': perm_type,
'role': role
}
try:
return service.permissions().insert(
fileId=file_id, body=new_permission).execute()
except errors.HttpError, error:
print 'An error occurred: %s' % error
return None
Use Try it now to test live data and see the API request and response.
For further reading, check this SO post.

Python Google Calendar API v3 does not return the "summary" field when listing event items

I have a python script that is going to run a cron operation.
It needs to find a list of events for the current day, and then perform some other action depending on the event.
So far, I am able to make a request to the Calendar API, and I am getting a list of events for the current day.
When I loop through the list of event items, the "summary" key for the item is missing.
I need this field so that I can determine what the event is for.
The data in each event item is coming back like this with no "summary" key
{
"status": "confirmed",
"kind": "calendar#event",
"end": {
"date": "2017-07-29"
},
"iCalUID": "0000000000000#google.com",
"updated": "2017-06-20T00:00:00.000Z",
"start": {
"date": "2017-07-24"
},
"etag": "\"0000000000000000\"",
"id": "0000000000000"
}
In the Google docs found here https://developers.google.com/google-apps/calendar/v3/reference/events#resource it shows the "summary" key should be returned with the event.
Since this script is going to run automatically, I setup a Google Service account to authorize the request to the API so that a user doesn't have to authorize it manually. Here is a sample of the script that I'm using
# -*- coding: utf-8 -*-
from oauth2client.service_account import ServiceAccountCredentials
from httplib2 import Http
from apiclient.discovery import build
import datetime
try:
scopes = ['https://www.googleapis.com/auth/calendar']
credentials = ServiceAccountCredentials.from_json_keyfile_name(
'/path/to/credentials/filename.json', scopes=scopes)
http_auth = credentials.authorize(Http())
startTime = str(datetime.datetime.now().date()) + 'T00:00:00-07:00'
endTime = str(datetime.datetime.now().date()) + 'T23:59:00-07:00'
calendar = build('calendar', 'v3', http=http_auth)
calId = 'calendar_id_string'
response = calendar.events().list(calendarId=calId,
timeMin=startTime, timeMax=endTime).execute()
items = response.get('items',[])
for item in items:
summary = item.get('summary','') # summary is blank!!
except Exception, ex:
print ex
Thank you for the help
The reason why the event "summary" was not returned to the client is because of a calendar permission that was set for an email account.
The email account came from the credentials JSON file that was created when I made the Service Account.
When the calendar was shared with the email account, the permission was set to "See only free/busy (hide details)" instead of "See all event details" by the admin.
With the "See only free/busy (hide details)" permission, a user can only see if the calendar is free or busy at a given time, but no event details are returned.
By changing this permission to "See all event details" then all of the event details will be returned back to the client, including the "summary" key which is what I need.
See here for more details:
https://developers.google.com/google-apps/calendar/concepts/sharing
https://developers.google.com/api-client-library/python/auth/service-accounts
Only the Calendar Owner can change the permissions on a calendar. If you are the owner then,
Log into Google Calendar
Click on the Settings Gear button
Click Settings > Calendars > [Your Calendar Name] > Share this Calendar
Find the email account and change the permission setting
Click Save
Otherwise, you will need to contact the owner about changing the permission.
If you have a G suite account, and have your own domain name, then the administrator of the domain must authorize the client following these instructions here.
https://developers.google.com/identity/protocols/OAuth2ServiceAccount#delegatingauthority
Note - In order to authorize the client, you must have already created a service account with the "Enable G Suite Domain-wide Delegation" checked. Then give the client_id to the administrator along with the scope, so it can be authorized by the administrator.
https://developers.google.com/identity/protocols/OAuth2ServiceAccount#authorizingrequests
After the Service account was enabled with the "G Suite Domain-wide Delegation," and the client and scope was authorized by the domain administrator, then you should be able to see all of the event details after making the api call. Here is an example below. Hope this helps others
# -*- coding: utf-8 -*-
from oauth2client.service_account import ServiceAccountCredentials
from httplib2 import Http
from apiclient.discovery import build
import datetime
try:
# updated the scopes with "calendar.readonly"
scopes = ['https://www.googleapis.com/auth/calendar.readonly']
credentials = ServiceAccountCredentials.from_json_keyfile_name(
'/path/to/credentials/filename.json', scopes=scopes)
# create a credential object with an authorized user with read access to the calendar
delegated_credentials = credentials.create_delegated('authorized_user_email#your_domain.com')
http_auth = delegated_credentials.authorize(Http())
startTime = str(datetime.datetime.now().date()) + 'T00:00:00-07:00'
endTime = str(datetime.datetime.now().date()) + 'T23:59:00-07:00'
calendar = build('calendar', 'v3', http=http_auth)
calId = 'calendar_id_string'
response = calendar.events().list(calendarId=calId, timeMin=startTime, timeMax=endTime).execute()
items = response.get('items',[])
for item in items:
summary = item.get('summary','') # Event summary exists!! Yea!!
except Exception, ex:
print ex

Categories

Resources