Trouble getting python Google Calendar API to work with service account - python

I have built this e-ink google calendar project which adapts the Google python quickstart code. I have OAuth working with a private account, but have found that the token expires every 7 days. I wanted to pursue a service account over making the app public because of the annoying steps with verifying this app that only I will use. I successfully created a google workspace service account, and created the private key but Im not sure if the key is the same as client_secrets.json which i cant seem to find.
My code comes from the suggestions from a similar question on stack overflow but I'm getting the error: Client secrets must be for a web or installed app. (traceback below)
The private key generated from the service account "ecalendar-#####.json" (replaced the real values with ###s) is in the working directory.
Here is the Calendar API part of my code:
#Calendar API part
import datetime
from datetime import date, timedelta
import os.path
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google.oauth2 import service_account
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']
#SERVICE_ACCOUNT_FILE = '/path/to/service.json'
SERVICE_ACCOUNT_FILE = 'ecalendar-#####.json'
try:
#Calendar API part
creds = None
if os.path.exists('token.json'):
credentials = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES)
# creds = Credentials.from_authorized_user_file('token.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(
SERVICE_ACCOUNT_FILE, SCOPES)
creds = flow.run_local_server(port=0)
with open('token.json', 'w') as token:
token.write(creds.to_json())
service = build('calendar', 'v3', credentials=creds)
now = datetime.datetime.utcnow().isoformat() + 'Z' # 'Z' indicates UTC time
print('Getting the upcoming 10 events')
events_result = service.events().list(calendarId='primary', timeMin=now,
maxResults=30, singleEvents=True,
orderBy='startTime').execute()
events = events_result.get('items', [])
if not events:
print('No upcoming events found.')
When I run this I get the following Traceback:
pi#raspberrypi:~/calendar/prog $ python3 ecalendar4.py
Traceback (most recent call last):
File "/home/pi/calendar/prog/ecalendar4.py", line 52, in <module>
flow = InstalledAppFlow.from_client_secrets_file(
File "/home/pi/.local/lib/python3.9/site-packages/google_auth_oauthlib/flow.py", line 207, in from_client_secrets_file
return cls.from_client_config(client_config, scopes=scopes, **kwargs)
File "/home/pi/.local/lib/python3.9/site-packages/google_auth_oauthlib/flow.py", line 165, in from_client_config
raise ValueError("Client secrets must be for a web or installed app.")
ValueError: Client secrets must be for a web or installed app.
Am I missing the correct client secrets file? Isnt that my private key json file? If not, where do I find them and where do they need to be?

Related

Google drive api authorization returns 404 not found error, after authorization accepted

from googleapiclient.http import MediaFileUpload
from Google import Create_Service
from googleapiclient.http import MediaIoBaseDownload
import google_auth_oauthlib
import pypandoc
import random
import discord
from discord.ext import commands
intents = discord.Intents.all()
bot = commands.Bot(command_prefix='!',intents=intents)
#bot.command()
async def gif(ctx):
CLIENT_SECRET_FILE = 'credentials.json'
API_NAME = 'drive'
API_VERSION = 'v3'
SCOPES = ['https://www.googleapis.com/auth/drive']
service = Create_Service(CLIENT_SECRET_FILE, API_NAME, API_VERSION, SCOPES)
file_id = '16f4MOIxs3yGS3oIXUIGbt3Tj6LLVYhxCMHl3XLFu4bY'
byteData = service.files().export_media(
fileId = file_id,
mimeType = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
I wanted to make google API work with server but I can't figure out how to make google API allow transfer data to server.
The issue is that you have create an installed application. the redirect uri you are using is that of localhost. Yet you do not have a web server running on your machine so its giving you a 404 when returning to you the authorization code.
The solution would be to do what they do in the python quickstart for google drive
Here they use creds = flow.run_local_server(port=0) to spawn a local web server to accept the authorization code response from the authorization process.
Code
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(
'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()

Trying to access Google calendar's API but get <<FileNotFoundError: [Errno 2] No such file or directory: 'credentials.json'>> error

I want to use google calendar's API. However, I get the following error:
FileNotFoundError: [Errno 2] No such file or directory: 'credentials.json'
Here's my code at this point of time:
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.oauth2.credentials import Credentials
import requests
import os
import datetime
import json
from requests.structures import CaseInsensitiveDict
scopes = ['https://www.googleapis.com/auth/calendar']
#flow = InstalledAppFlow.from_client_secrets_file("client_secrets_file.json", scopes=scopes) from previous attempts
#credentials = flow.run_console() this too
def main():
creds=None
if os.path.exists('token.json'):
creds=Credentials.from_authorized_user_file('token.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('credentials.json',scopes)
creds=flow.run_local_server(port=0)
with open('token.json','w') as token:
token.write(creds.to_json())
if __name__ == '__main__':
main()
This is from a more recent tutorial thought that may help as in some previous attempts the tutorials seemed outdated.
The line
InstalledAppFlow.from_client_secrets_file('credentials.json',scopes)
tries to load a 'credentials.json', which should contain the API credentials for your Google Cloud application. The file should be in the same folder than your output .exe file.
Check out the "Creating your client credentials" section here or the documentation here for how to create credentials for your end users (which is what should be inside the credentials.json file)

Google Drive API in Python (What is the authorization code?)

the code was working but I had to revoke the temporary token because my app was still in testing, but now it shows on console:
"Enter the authorization code:" But What is the authorization code??
CLIENT_SECRET_FILE = 'client_secret_xxxxxxx.json'
API_NAME = 'drive'
API_VERSION = 'v3'
SCOPES = ['https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/drive.file',
'https://www.googleapis.com/auth/drive.readonly',
'https://www.googleapis.com/auth/drive.metadata.readonly',
'https://www.googleapis.com/auth/drive.appdata',
'https://www.googleapis.com/auth/drive.metadata',
'https://www.googleapis.com/auth/drive.photos.readonly',]
service = Create_Service(CLIENT_SECRET_FILE, API_NAME, API_VERSION, SCOPES)
The authorization code is the response from the authorization as part of the fist step in the authorization process.
When your application runs you should ask the user to navigate to a web site where they will be shown the consent screen.
When the user consents to the request for authorization. Then a new browser window will open and the authorization code will appear in the url of the browser.
This code is then used by your application to exchange for an access token which will give your application access to request data from the users account.
I think that you should have a look at the official google drive sample. python quickstart authorizaotn code you will find here will work better then what you have now.
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(
'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()

YouTube Update Banner Automation

I am trying to set up a program that will automatically change my YouTube banner. I thought it would be really cool to have if swap between multiple different banner images that people might make for me. But the implementation I found requires the user to input the authorization code from.
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.http import MediaFileUpload
CLIENT_SECRET_FILE = 'client_secret.json'
SCOPES = ['https://www.googleapis.com/auth/youtube.force-ssl']
flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRET_FILE, SCOPES)
credentials = flow.run_console()
youtube = build('youtube', 'v3', api_key=apikey)
request = youtube.channelBanners().insert(
channelId="",
body={},
media_body=MediaFileUpload("image.jpg")
)
response = request.execute()
print(response)
Short hand: Is there a good way to update a YouTube banner with a basic script running on a server?
If you run something like this locally once and authorize it. it will store the user credentials in token.json. As long as you upload that along with your code to your server the next time it runs it should just load the authorization from that.
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/youtube.force-ssl']
def main():
"""Shows basic usage of the YouTube 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('youtube', 'v3', credentials=creds)
# Call the Youtube v3 API
request = service.channelBanners().insert(
channelId="",
body={},
media_body=MediaFileUpload("image.jpg")
)
response = request.execute()
print(response)
if __name__ == '__main__':
main()

Google calender API getting started

I'm trying to get familiar with the google calendar api. In the getting started guide they have this code example:
from __future__ import print_function
import datetime
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/calendar.readonly']
def main():
"""Shows basic usage of the Google Calendar API.
Prints the start and name of the next 10 events on the user's calendar.
"""
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('calendar', 'v3', credentials=creds)
# Call the Calendar API
now = datetime.datetime.utcnow().isoformat() + 'Z' # 'Z' indicates UTC time
print('Getting the upcoming 10 events')
events_result = service.events().list(calendarId='primary', timeMin=now,
maxResults=10, singleEvents=True,
orderBy='startTime').execute()
events = events_result.get('items', [])
if not events:
print('No upcoming events found.')
for event in events:
start = event['start'].get('dateTime', event['start'].get('date'))
print(start, event['summary'])
if __name__ == '__main__':
main()
In this example we automatically open a window to ask the user to access their calendar, if we don't already have access through the pickle file. The thing is I don't want this window to open automatically, I want to print a link instead that the user can click to authenticate. I have looked around in the documentation but can't seem to find anything usefull. I would appriciate any help i could get, thanks!
For authorizing process, you want to show only the URL. You don't want to automatically open the browser.
You want to achieve this using googleapis with python.
If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
In this case, please use Flow.from_client_secrets_file instead of InstalledAppFlow.from_client_secrets_file.
Modified script:
When your script is modified, please modify as follows.
From:
from google_auth_oauthlib.flow import InstalledAppFlow
To:
from google_auth_oauthlib.flow import Flow
and
From:
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('calendar', 'v3', credentials=creds)
To:
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:
# Create the flow using the client secrets file from the Google API
# Console.
flow = Flow.from_client_secrets_file('client_secret.json', SCOPES, redirect_uri='urn:ietf:wg:oauth:2.0:oob')
# Tell the user to go to the authorization URL.
auth_url, _ = flow.authorization_url(prompt='consent')
print('Please go to this URL: {}'.format(auth_url))
# The user will get an authorization code. This code is used to get the
# access token.
code = input('Enter the authorization code: ')
flow.fetch_token(code=code)
creds = flow.credentials
# Save the credentials for the next run
with open('token.pickle', 'wb') as token:
pickle.dump(creds, token)
service = build('calendar', 'v3', credentials=creds)
In this case, when you run the script under token.pickle is not existing, the URL for the authorization is displayed to the console. The browser is not opened. So please access to the URL by opening the browser and authorize the scopes. Then, please the copied authorization code to the console and input enter key. By this, the access token is retrieved and the file of token.pickle is created.
Note:
If an error related to the redirect uri occurs, please modify it to http://localhost and test it again.
Reference:
google_auth_oauthlib.flow module
If I misunderstood your question and this was not the direction you want, I apologize.
Added:
From I want to print a link instead that the user can click to authenticate in your question, I proposed above sample script.
From some way not to manually confirm authorization codes in your replying, I think that that above sample script is not suitable.
In this case, how about using the service account? When the service account is used, no authorization code is required. The script for using the service account is as follows.
Sample script:
from google.oauth2 import service_account
from googleapiclient.discovery import build
SERVICE_ACCOUNT_FILE = 'service-account-credentials.json' # Here, please set the creadential file of the service account.
SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']
creds = service_account.Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES)
service = build('calendar', 'v3', credentials=creds)
Note:
In order to access to the Google calendar using the service account, at first, please share the Google calendar with the email of the service account. Please be careful this.
Reference:
Creating a service account

Categories

Resources