Home » Tutorials » How to Integrate Google Drive API in Python

How to Integrate Google Drive API in Python

In this digital era, we’re all about simplicity and efficiency. Google Drive stands out as a perfect tool, it’s not just for storing files, but also for collaborating and sharing. When we integrate Google Drive with Python, it kicks our productivity up a notch, making everything more accessible.

Today, We’ll use Python to integrate the Google Drive API and create an easy-to-use graphical interface. This setup will enable direct interactions with Google Drive, simplifying file uploads and downloads. I’ll walk you through each step to build this useful tool, ensuring you can manage your files more effectively from your own Python application.

Let’s get started!

Table of Contents

Necessary Libraries

Let’s get everything set up before we dive into the code part, so make sure to install these libraries via the terminal or your command prompt:

$ pip install tk
$ pip install google-api-python-client
$ pip install google-auth
$ pip install google-auth-oauthlib
$ pip install google-auth-openid
$ pip install google-auth-credentials
$ pip install requests
$ pip install oauthlib

Imports

Since we intend to create a graphical user interface, we import tkinter. From this library, we import ttk for themed widgets, filedialog for handling file path directories, and messagebox to display messages. Additionally, we will use google.oauth2.credentials, which handles OAuth 2.0 credentials for Google APIs, google_auth_oauthlib.flow for OAuth 2.0 authorization flows in installed applications, and google.auth.transport.requests to manage HTTP requests for authorization purposes.

In order to interact with Google APIs, we will also import googleapiclient.discovery to build service objects and googleapiclient.http for constructing HTTP requests and responses. Furthermore, we import threading to ensure that the main window remains responsive, and os to interact with the operating system.

import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient.discovery import build
from googleapiclient.http import MediaIoBaseDownload, MediaFileUpload
import threading
import os

Global Variables Initialization

Next, we initialize global variables that we use throughout our code:

  • creds: This variable stores our Google Drive credentials. It’s the first step in connecting our application to Google Drive.
  • CLIENT_SECRET_FILE: Specifies the path to the JSON file that contains our credentials. It’s essential for authenticating our application.
  • SCOPES: A list of OAuth 2.0 scopes that define the level of access we need for Google Drive. This ensures our application can perform the tasks it needs to.
  • items: Contains the metadata of files fetched from Google Drive. We use this to process or display file information within our application.
  • file_tree: Represents the treeview widget in our GUI that displays file information. It helps users navigate their files visually.
  • credentials_label: Displays the path to the selected credentials file. It’s a helpful way for users to verify which credentials file they are using with the application.
# Initialize global variables
creds = None
CLIENT_SECRET_FILE = ''  # Path to your credentials JSON file
SCOPES = ['https://www.googleapis.com/auth/drive']  # Full access to Google Drive
service = None
items = []
file_tree = None
credentials_label = None  # Declare the label as a global variable

Integrating Google Drive API Functions

Now, let’s define our functions – the heart of our code:

Authentication Function

As the name suggests, this function is responsible for Google Drive authentication using OAuth2. Here’s how it works:

First, it checks if a token.json file exists. If it does, the function loads the credentials from the file to see if they are still valid or have expired. If the credentials are valid, authentication proceeds. If they have expired, the function tries to refresh them. If an error occurs during this process, it deletes the token.json file and initiates a new one.

In the scenario where there is no token.json file, the function starts a new OAuth flow to obtain credentials and saves them to the token.json file, and then returns those credentials.

def authenticate():
   global creds
   if os.path.exists('token.json'):
       creds = Credentials.from_authorized_user_file('token.json', SCOPES)
       if not creds.valid:
           try:
               creds.refresh(Request())
           except Exception as e:
               os.remove('token.json')
               flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRET_FILE, SCOPES)
               creds = flow.run_local_server(port=0)
               with open('token.json', 'w') as token:
                   token.write(creds.to_json())
   else:
       flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRET_FILE, SCOPES)
       creds = flow.run_local_server(port=0)
       with open('token.json', 'w') as token:
           token.write(creds.to_json())
   return creds

list_files Function

First, the function calls a previous function to obtain the returned credentials. Then, it uses these credentials to build a Google Drive service instance, which we will refer to as “service”. This service instance interacts with the Google Drive API to send a request that lists files based on specific parameters such as page size, fields to include, and support for all drives.

Next, the function goes to the file_tree widget, which displays the files, and clears it of all existing items. Once this is done, it begins inserting the retrieved files from Drive into the file_tree widget. If there are no files to be displayed, a message indicating this will be shown.

def list_files():
   global service, items
   creds = authenticate()
   service = build('drive', 'v3', credentials=creds)
   results = service.files().list(
       pageSize=100,
       fields="nextPageToken, files(id, name, mimeType)",
       includeItemsFromAllDrives=True,
       supportsAllDrives=True
   ).execute()
   items = results.get('files', [])
   file_tree.delete(*file_tree.get_children())
   if not items:
       file_tree.insert("", 'end', text="No files found.")
   else:
       for item in items:
           file_tree.insert("", 'end', values=(item['name'], item['id'], item['mimeType']))

upload_file Function

It starts by opening a file dialog, allowing the user to select the file they wish to upload. If a file is selected (in other words, file_path is not empty), a new thread is started. This thread handles the upload process using the upload_thread function.

def upload_file():
   file_path = filedialog.askopenfilename()
   if file_path:
       threading.Thread(target=upload_thread, args=(file_path,)).start()

upload_thread Function

This function is triggered by a previous function that starts a new thread. It operates as follows:

  • First, the function authenticates and retrieves credentials. Then, it builds a service instance to interact with the Google Drive API. It extracts the file name from the file_path provided by the user and creates metadata for this file.
  • Next, the function prepares the file for upload using MediaFileUpload, with the upload set as resumable.
  • Finally, it calls service.files().create() to upload the file to the drive, and prints the ID of the uploaded file.
def upload_thread(file_path):
   creds = authenticate()
   service = build('drive', 'v3', credentials=creds)
   file_name = os.path.basename(file_path)
   file_metadata = {'name': file_name}
   media = MediaFileUpload(file_path, resumable=True)
   file = service.files().create(body=file_metadata, media_body=media, fields='id').execute()
   print(f'File uploaded: {file.get("id")}')

download_file Function

This one starts by retrieving the file selected in the file_tree widget. If a file is selected (selected_item is not empty), the function initiates a new thread. This thread runs the download_thread function, passing the selected_item as an argument, to handle the download process.

def download_file():
   selected_item = file_tree.selection()
   if selected_item:
       threading.Thread(target=download_thread, args=(selected_item,)).start()

download_thread Function

This function, download_thread, is called with a selected_item parameter. It starts by obtaining credentials through the authenticate() function and creates a service instance to interact with the Google Drive API. The file ID and file name are extracted from the file_tree widget using selected_item. The function then retrieves the file’s metadata from Google Drive, including its MIME type and name.

For files identified as Google applications (like Docs, Sheets, or Slides), the function maps Google MIME types to corresponding exportable file types (e.g., Google Docs to Word documents). If the file’s MIME type is exportable, it prepares an export request, appending a suitable file extension (.pptx or .docx) based on the MIME type. If the MIME type is not exportable, it raises an exception.

For non-Google app files, it prepares a standard media download request. The user is then prompted to select a directory for downloading the file. If a directory is selected, the file is downloaded there, and the progress of the download is printed, showing the completion percentage. Upon successful download, a message box confirms the success to the user. If any errors occur during this process, they are caught in the exception block, and an error message is displayed and printed.

def download_thread(selected_item):
   global items
   try:
       creds = authenticate()
       service = build('drive', 'v3', credentials=creds)
       file_id = file_tree.item(selected_item, 'values')[1]
       file_metadata = service.files().get(fileId=file_id, fields='mimeType, name').execute()
       mime_type = file_metadata['mimeType']
       file_name = file_tree.item(selected_item, 'values')[0]


       # Handling different file types, including Google Slides
       if 'google-apps' in mime_type:
           export_types = {
               'application/vnd.google-apps.document': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
               'application/vnd.google-apps.spreadsheet': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
               'application/vnd.google-apps.presentation': 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
           }
           if mime_type in export_types:
               request = service.files().export_media(fileId=file_id, mimeType=export_types[mime_type])
               file_name += '.pptx' if 'presentation' in mime_type else '.docx'
           else:
               raise Exception(f"File type '{mime_type}' is not exportable.")
       else:
           request = service.files().get_media(fileId=file_id)


       download_directory = filedialog.askdirectory()  # Prompt user to select download directory
       if download_directory:
           file_path = os.path.join(download_directory, file_name)
           with open(file_path, 'wb') as fh:
               downloader = MediaIoBaseDownload(fh, request)
               done = False
               while not done:
                   status, done = downloader.next_chunk()
                   print(f'Download {int(status.progress() * 100)}% - {file_name}')
           messagebox.showinfo("Download", f"File downloaded successfully! {file_path}")
   except Exception as e:
       print(f"Error downloading file: {e}")
       messagebox.showerror("Error", f"Error downloading file: {e}")

select_credentials_file Function

It begins by opening a file dialog for the user to select the credentials JSON file required for authentication. Once the user selects a file, the function assigns those credentials to the global variable CLIENT_SECRET_FILE and displays the file path on the credentials_label widget.

def select_credentials_file():
   global CLIENT_SECRET_FILE, credentials_label
   file_path = filedialog.askopenfilename(filetypes=[("JSON files", "*.json")])
   if file_path:
       CLIENT_SECRET_FILE = file_path
       credentials_label.config(text=f"Credentials file: {CLIENT_SECRET_FILE}")

setup_gui Function

The last one sets up the graphical user interface. It starts by setting the title of the main window and its geometry. Next, it creates a frame that expands vertically and horizontally to fill the available space, which will contain the file_tree widget and a scrollbar.

After creating the frame, the file_tree widget is set up with three columns: one for the file name, one for the file ID, and one for the MIME type, featuring only headings and no body. A scrollbar is configured using ttk.Scrollbar and linked to the file_tree widget to make it scrollable.

The function then creates four buttons, each triggering a specific function:

‘List Files’ calls list_files() to display files on the widget, ‘Upload File’ triggers upload_file(), ‘Download Selected File’ calls download_file(), and ‘Select Credentials File’ activates select_credentials_file().

Finally, the function creates a label to display information about the selected credentials file.

def setup_gui(root):
   global file_tree, credentials_label
   root.title("Google Drive File Manager - The Pycodes")
   root.geometry("700x450")


   frame = ttk.Frame(root)  # Frame to hold the Treeview and Scrollbar
   frame.pack(expand=True, fill='both', padx=10, pady=10)


   # Setup Treeview
   cols = ('File Name', 'File ID', 'MIME Type')
   file_tree = ttk.Treeview(frame, columns=cols, show='headings')
   for col in cols:
       file_tree.heading(col, text=col)
   file_tree.pack(side=tk.LEFT, expand=True, fill='both')


   # Setup Scrollbar
   scrollbar = ttk.Scrollbar(frame, orient="vertical", command=file_tree.yview)
   scrollbar.pack(side=tk.RIGHT, fill='y')
   file_tree.configure(yscrollcommand=scrollbar.set)


   # Setup Buttons
   ttk.Button(root, text="List Files", command=list_files).pack(side=tk.TOP, pady=5)
   ttk.Button(root, text="Upload File", command=upload_file).pack(side=tk.TOP, pady=5)
   ttk.Button(root, text="Download Selected File", command=download_file).pack(side=tk.TOP, pady=5)
   ttk.Button(root, text="Select Credentials File", command=select_credentials_file).pack(side=tk.TOP, pady=5)


   # Display selected credentials file
   credentials_label = ttk.Label(root, text="Credentials file: ")
   credentials_label.pack(side=tk.TOP, pady=5)

Main Block

This part sets the GUI using the setup_gui() function and ensures that the main window keeps running and is responsive to the users until they quit willingly.

root = tk.Tk()
setup_gui(root)
root.mainloop()

Running the Code

  • Before running the code you need to go to the Google Cloud Console: https://console.cloud.google.com/welcome/new 
  • After that select a project and if you don’t have one then click “New Project”.  
  • Once you have done that, choose a project name and click “Create”.
  • Now that you have created a project all you need to do is select it and then go to the Navigation menu and select “APIs & Services”.
  • Once you’re there click on “ENABLE APIS AND SERVICES”, this should lead you to the API Library.
  • Then search for google drive API and select it.
  • After that click “ENABLE”, then select credentials, click on “CREATE CREDENTIALS” and select OAuth client ID.
  • Next click “CONFIGURE CONSENT SCREEN” and choose external, then fill in the name of the app as well as the support email as well as Developer’s contact information and click “save and continue”.
  • Continue to click “save and continue” until you return to the dashboard. Once you’re on the dashboard make sure to click “PUBLISH THE APP”. 
  • Now that you have configured the consent screen, go back to credentials and select OAuth client ID  on CREATE CREDENTIALS.
  • Once you have done this go to the Application Type, select “Desktop app” and then click “Create”.
  • Once this is over click “DOWNLOAD JSON” to obtain the json credentials.
  • Now you can run the code and click on the “Select Credentials File”.
  • After that choose the json file you just downloaded and then click “List Files”.

As you can see I managed to access my drive after I selected my account and now I can upload or download from it.

  • Also if you get the “Google hasn’t verified this app” page when you run this code all you have to do is click “advanced” and then click “go to your project”.
  • After this click “continue” and the code should work fine.

Full Code

import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient.discovery import build
from googleapiclient.http import MediaIoBaseDownload, MediaFileUpload
import threading
import os


# Initialize global variables
creds = None
CLIENT_SECRET_FILE = ''  # Path to your credentials JSON file
SCOPES = ['https://www.googleapis.com/auth/drive']  # Full access to Google Drive
service = None
items = []
file_tree = None
credentials_label = None  # Declare the label as a global variable




def authenticate():
   global creds
   if os.path.exists('token.json'):
       creds = Credentials.from_authorized_user_file('token.json', SCOPES)
       if not creds.valid:
           try:
               creds.refresh(Request())
           except Exception as e:
               os.remove('token.json')
               flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRET_FILE, SCOPES)
               creds = flow.run_local_server(port=0)
               with open('token.json', 'w') as token:
                   token.write(creds.to_json())
   else:
       flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRET_FILE, SCOPES)
       creds = flow.run_local_server(port=0)
       with open('token.json', 'w') as token:
           token.write(creds.to_json())
   return creds




def list_files():
   global service, items
   creds = authenticate()
   service = build('drive', 'v3', credentials=creds)
   results = service.files().list(
       pageSize=100,
       fields="nextPageToken, files(id, name, mimeType)",
       includeItemsFromAllDrives=True,
       supportsAllDrives=True
   ).execute()
   items = results.get('files', [])
   file_tree.delete(*file_tree.get_children())
   if not items:
       file_tree.insert("", 'end', text="No files found.")
   else:
       for item in items:
           file_tree.insert("", 'end', values=(item['name'], item['id'], item['mimeType']))




def upload_file():
   file_path = filedialog.askopenfilename()
   if file_path:
       threading.Thread(target=upload_thread, args=(file_path,)).start()




def upload_thread(file_path):
   creds = authenticate()
   service = build('drive', 'v3', credentials=creds)
   file_name = os.path.basename(file_path)
   file_metadata = {'name': file_name}
   media = MediaFileUpload(file_path, resumable=True)
   file = service.files().create(body=file_metadata, media_body=media, fields='id').execute()
   print(f'File uploaded: {file.get("id")}')




def download_file():
   selected_item = file_tree.selection()
   if selected_item:
       threading.Thread(target=download_thread, args=(selected_item,)).start()




def download_thread(selected_item):
   global items
   try:
       creds = authenticate()
       service = build('drive', 'v3', credentials=creds)
       file_id = file_tree.item(selected_item, 'values')[1]
       file_metadata = service.files().get(fileId=file_id, fields='mimeType, name').execute()
       mime_type = file_metadata['mimeType']
       file_name = file_tree.item(selected_item, 'values')[0]


       # Handling different file types, including Google Slides
       if 'google-apps' in mime_type:
           export_types = {
               'application/vnd.google-apps.document': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
               'application/vnd.google-apps.spreadsheet': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
               'application/vnd.google-apps.presentation': 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
           }
           if mime_type in export_types:
               request = service.files().export_media(fileId=file_id, mimeType=export_types[mime_type])
               file_name += '.pptx' if 'presentation' in mime_type else '.docx'
           else:
               raise Exception(f"File type '{mime_type}' is not exportable.")
       else:
           request = service.files().get_media(fileId=file_id)


       download_directory = filedialog.askdirectory()  # Prompt user to select download directory
       if download_directory:
           file_path = os.path.join(download_directory, file_name)
           with open(file_path, 'wb') as fh:
               downloader = MediaIoBaseDownload(fh, request)
               done = False
               while not done:
                   status, done = downloader.next_chunk()
                   print(f'Download {int(status.progress() * 100)}% - {file_name}')
           messagebox.showinfo("Download", f"File downloaded successfully! {file_path}")
   except Exception as e:
       print(f"Error downloading file: {e}")
       messagebox.showerror("Error", f"Error downloading file: {e}")




def select_credentials_file():
   global CLIENT_SECRET_FILE, credentials_label
   file_path = filedialog.askopenfilename(filetypes=[("JSON files", "*.json")])
   if file_path:
       CLIENT_SECRET_FILE = file_path
       credentials_label.config(text=f"Credentials file: {CLIENT_SECRET_FILE}")




def setup_gui(root):
   global file_tree, credentials_label
   root.title("Google Drive File Manager - The Pycodes")
   root.geometry("700x450")


   frame = ttk.Frame(root)  # Frame to hold the Treeview and Scrollbar
   frame.pack(expand=True, fill='both', padx=10, pady=10)


   # Setup Treeview
   cols = ('File Name', 'File ID', 'MIME Type')
   file_tree = ttk.Treeview(frame, columns=cols, show='headings')
   for col in cols:
       file_tree.heading(col, text=col)
   file_tree.pack(side=tk.LEFT, expand=True, fill='both')


   # Setup Scrollbar
   scrollbar = ttk.Scrollbar(frame, orient="vertical", command=file_tree.yview)
   scrollbar.pack(side=tk.RIGHT, fill='y')
   file_tree.configure(yscrollcommand=scrollbar.set)


   # Setup Buttons
   ttk.Button(root, text="List Files", command=list_files).pack(side=tk.TOP, pady=5)
   ttk.Button(root, text="Upload File", command=upload_file).pack(side=tk.TOP, pady=5)
   ttk.Button(root, text="Download Selected File", command=download_file).pack(side=tk.TOP, pady=5)
   ttk.Button(root, text="Select Credentials File", command=select_credentials_file).pack(side=tk.TOP, pady=5)


   # Display selected credentials file
   credentials_label = ttk.Label(root, text="Credentials file: ")
   credentials_label.pack(side=tk.TOP, pady=5)




root = tk.Tk()
setup_gui(root)
root.mainloop()

Happy Coding!

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top