How do I use google.auth instead of oauth2client in Python to get access to my Google Calendar
Asked Answered
R

2

9

Several years ago I created a small Python program which were able to maintain my calendar using oauth2client which is now deprecated and replaced with google.auth - but I cannot find any useful documentation and my program stopped working complaining about a _module KeyError which nobody appear to have solved except by upgrading.

I cannot figure out how to replace the oauth2client with google.auth:

import datetime
import httplib2
import os

from apiclient import discovery
import oauth2client
from oauth2client import client
from oauth2client import tools

...

credentials = get_credentials()
http = credentials.authorize(httplib2.Http())
service = discovery.build('calendar', 'v3', http=http)
Renault answered 29/8, 2018 at 19:46 Comment(7)
Have you checked this developers.google.com/calendar/quickstart/python?Semirigid
Are you using a service account or the user credentials?Disassociate
@AkshayMaldhure: The link still show the obsolete way of doing it!Renault
@LorenzoPersichetti: User credentials (I think). I log in the first time and get some code which I store in a file which the program use in the future to get access.Renault
Where does it say so?Semirigid
@LorenzoPersichetti Yes, I follow a similar way for the Google Sheets API v4.Semirigid
@AkshayMaldhure look at my answer for deprecation detailsDisassociate
D
6

According to the oauth2client deprecation notes, the replacement to be used to manage Google user credentials is google-auth-oauthlib. Below a snippet working on my PC (python 3.6).

As the documentation highlights the new library does not save the credentials, that's why I am using pickle to save them. Maybe, depending on your application requirements, you want to have a more robust solution (like a database).

import os
import pickle

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/calendar.readonly', ]


# we check if the file to store the credentials exists
if not os.path.exists('credentials.dat'):

    flow = InstalledAppFlow.from_client_secrets_file('client_id.json', SCOPES)
    credentials = flow.run_local_server()

    with open('credentials.dat', 'wb') as credentials_dat:
        pickle.dump(credentials, credentials_dat)
else:
    with open('credentials.dat', 'rb') as credentials_dat:
        credentials = pickle.load(credentials_dat)

if credentials.expired:
    credentials.refresh(Request())

calendar_sdk = build('calendar', 'v3', credentials=credentials)

calendars_get_params = {
        'calendarId': 'primary',
    }

test = calendar_sdk.calendars().get(**calendars_get_params).execute()
print(test)
Disassociate answered 30/8, 2018 at 16:14 Comment(2)
OSError: [Errno 98] Address already in use. What does it mean? I use Service Account client ID.Livonia
This is not related to my script, but to something that is probably pending on your side. Be sure you don't have previous script runs stuck.Disassociate
D
3

I haven't robustly tested this, but it works for testing snippets with my personal account. I'm sure there are changes that could and/or should be made to it for enterprise applications, such as passing auth'd Http() instances, detecting scope changes, and so on.

You can review the full code on my GitHub repo:

requirements:

  • google-api-python-client
  • google-auth
  • google-auth-oauthlib
  • whatever deps the above pull in

I use the InstalledAppFlow class, and generally followed the instructions on Google's Python auth guide.

Code (Python 3.6)

# Google API imports
from googleapiclient.discovery import build
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow

SCOPES = ['your scopes', 'here']

def get_saved_credentials(filename='creds.json'):
    '''Read in any saved OAuth data/tokens
    '''
    fileData = {}
    try:
        with open(filename, 'r') as file:
            fileData: dict = json.load(file)
    except FileNotFoundError:
        return None
    if fileData and 'refresh_token' in fileData and 'client_id' in fileData and 'client_secret' in fileData:
        return Credentials(**fileData)
    return None

def store_creds(credentials, filename='creds.json'):
    if not isinstance(credentials, Credentials):
        return
    fileData = {'refresh_token': credentials.refresh_token,
                'token': credentials.token,
                'client_id': credentials.client_id,
                'client_secret': credentials.client_secret,
                'token_uri': credentials.token_uri}
    with open(filename, 'w') as file:
        json.dump(fileData, file)
    print(f'Credentials serialized to {filename}.')

def get_credentials_via_oauth(filename='client_secret.json', scopes=SCOPES, saveData=True) -> Credentials:
    '''Use data in the given filename to get oauth data
    '''
    iaflow: InstalledAppFlow = InstalledAppFlow.from_client_secrets_file(filename, scopes)
    iaflow.run_local_server()
    if saveData:
        store_creds(iaflow.credentials)
    return iaflow.credentials

def get_service(credentials, service='sheets', version='v4'):
    return build(service, version, credentials=credentials)

Usage is then:

creds = get_saved_credentials()
if not creds:
    creds = get_credentials_via_oauth()
sheets = get_service(creds)
Deserve answered 30/8, 2018 at 15:14 Comment(2)
OSError: [Errno 98] Address already in use. What does it mean? I use Service Account client ID.Livonia
@Livonia If you are using service account credentials, you do not need the google.auth.oauthlib helper package - your use case is handled effectively with just google.auth: google-auth.readthedocs.io/en/stable/… Anyway, comments are not where you ask new questions.Deserve

© 2022 - 2024 — McMap. All rights reserved.