Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 23 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,54 @@
# Google Workspace APIs Intro codelab
# Google Workspace ("GWS") APIs intro codelab

This codelab introduces developers to Google Workspace ([formerly G Suite](https://cloud.google.com/blog/products/workspace/introducing-google-workspace)) APIs (Gmail, Google Drive, Calendar, Sheets, Slides, etc.) by writing a simple Python script that lists the first 100 files/folders in a user's Google Drive. Access it at http://g.co/codelabs/gsuite-apis-intro.
This codelab introduces developers to Google Workspace ([formerly G Suite](https://cloud.google.com/blog/products/workspace/introducing-google-workspace)) APIs (Gmail, Google Drive, Calendar, Sheets, Slides, etc.) by writing a simple Python script that lists the first 100 files/folders in a user's Google Drive. Access it at <http://g.co/codelabs/gsuite-apis-intro>. While that tutorial and the majority of this `README` focuses on Python, this repo contains code samples for both Python (2 and 3) and Node.js.

## Prerequisites

- A Google account (Google Workspace accounts may require administrator approval)
- Familiarity with shell commands on POSIX-compliant systems (Linux, Mac OS X); [Windows users](http://docs.python.org/faq/windows) also welcome
- Ability to create source files with either a code editor or shell commands.
- Basic skills in [Python](http://python.org) (2 or 3), but you use [any supported language](http://developers.google.com/api-client-library)
- Basic skills in [Python](http://python.org) (2 or 3) or [Node.js](http://nodejs.org)
- Some files and/or folders in your [Google Drive](http://drive.google.com)


## Description

This repo is part of the [codelab](http://g.co/codelabs/gsuite-apis-intro) introducing developers to using Google Workspace (REST/HTTP) APIs. The example will be done in Python for brevity and wide availability, but [all common languages are supported](http://developers.google.com/api-client-library). The tutorial shows how to use the developer console to create and manage projects, including obtaining the credentials needed in your apps, then moves on to the primary code sample that displays the first 100 files & folders in your Google Drive by using the Drive API.
This repo is part of the [codelab](http://g.co/codelabs/gsuite-apis-intro) introducing developers to using Google Workspace ("GWS") RESTful HTTP APIs. The code examples are in Python and Node.js for brevity and wide availability, but [all common languages are supported](http://developers.google.com/api-client-library). The tutorial shows how to use the developer console to create and manage projects, including obtaining the credentials needed in your apps, then moves on to the primary code sample that displays the first 100 files & folders in your Google Drive by using the Drive API.

### Caveat
> `oauth2client` **library deprecated**
>
> The [`oauth2client`](https://github.com/googleapis/oauth2client) library was [deprecated](https://google-auth.readthedocs.io/en/latest/oauth2client-deprecation.html) in [2017](https://github.com/googleapis/oauth2client/commit/00926f2058e23da7f6772ad6477e64d7506415e5) in favor of newer replacements. However the newer libraries [do not yet support](https://google-auth.readthedocs.io/en/latest/oauth2client-deprecation.html#replacement) either user authorization nor user credentials storage, two features that are required in this codelab. When those features become available, we will migrate to the newer libraries. For now, `oauth2client` still works, even in maintenance mode, and provides automated, threadsafe, and 2.x/3.x-compatible storage of and access to OAuth2 tokens for users whereas the newer libraries do not (yet).

**`oauth2client` library deprecated**:
The [`oauth2client`](https://github.com/googleapis/oauth2client) library was [deprecated](https://google-auth.readthedocs.io/en/latest/oauth2client-deprecation.html) in [2017](https://github.com/googleapis/oauth2client/commit/00926f2058e23da7f6772ad6477e64d7506415e5) in favor of newer replacements. However the newer libraries [do not yet support](https://google-auth.readthedocs.io/en/latest/oauth2client-deprecation.html#replacement) either user authorization nor user credentials storage, two features that are required in this codelab. When those features become available, we will migrate to the newer libraries. For now, `oauth2client` still works, even in maintenance mode, and provides automated, threadsafe, and 2.x/3.x-compatible storage of and access to OAuth2 tokens for users whereas the newer libraries do not (yet).

### Cost

Use of the Google Drive API (and most Google Workspace APIs) are covered by a [monthly subscription fee](http://gsuite.google.com/pricing.html), including the free consumer Google/Gmail accounts (monthly fee of $0USD), meaning you can use the APIs as long as you stay within each API's daily/monthly limits. Exceeding the limits will result in failed requests/exceptions. Some APIs have a [support page to request additional quota](https://developers.google.com/drive/api/v3/handle-errors#quota).
Use of the Google Drive API (and most GWS APIs) are covered by a [monthly subscription fee](http://gsuite.google.com/pricing.html), including the free consumer Google/Gmail accounts (monthly fee of $0USD), meaning you can use the APIs as long as you stay within each API's daily/monthly limits. Exceeding the limits will result in failed requests/exceptions. Not all of the quotas are published, but some general quotas for all GWS APIs can be found on the [Google Services quotas page](https://developers.google.com/apps-script/guides/services/quotas). Some APIs have a [support page where developers can request additional quota](https://developers.google.com/drive/api/v3/handle-errors#quota).


## Repo files
## Source files

Filename | Description
--- | ---
`drive_list.py` | The original sample app as featured in codelab
`drive_list-new.py` | Same as `drive_list.py` but uses newer auth libraries (not threadsafe nor easily Python 2-3 compatible nor handles auth tokens [extra JSON storage code])
[`python/drive_list.py`](/python/drive_list.py) | The original sample app as featured in codelab (uses older, deprecated auth libraries but matches much of the code that's still online)
[`python/drive_list-new.py`](/python/drive_list-new.py) | Same as `drive_list.py` but uses newer (current) auth libraries (must manage auth tokens [see extra JSON storage code])
[`nodejs/drive_list.js`](/nodejs/drive_list.js) | The Node.js/JavaScript version of the script (also have to manage auth token storage)
[`nodejs/drive_list.mjs`](/nodejs/drive_list.mjs) | Same as `drive_list.js` but with ES module `import`s instead of `require`s.


## Support
## Resources

- [Codelab to build code sample](https://g.co/codelabs/gsuite-apis-intro)
- [Code deep dive blogpost](https://goo.gl/cdm3kZ)
- [Code deep dive video](https://goo.gl/ZIgf8k)
- [Google Drive API home page](https://developers.google.com/drive)
- [Drive API home page](https://developers.google.com/drive)
- [Drive API Python QuickStart](https://developers.google.com/drive/api/quickstart/python)
- [Drive API Node.js QuickStart](https://developers.google.com/drive/api/quickstart/nodejs)
- [Other Google Drive API videos](https://developers.google.com/drive/api/v3/videos)
- [Google Workspace REST APIs intro video](https://goo.gle/3ateIIQ)
- [Google Workspace REST APIs "world tour" video](https://youtu.be/kkp0aNGlynw)
- [GWS REST APIs intro video](https://goo.gle/3ateIIQ)
- [GWS REST APIs 50-minute "world tour" video](https://youtu.be/kkp0aNGlynw)
- [Drive Service on Apps Script](https://developers.google.com/apps-script/reference/drive)
- [Advanced Drive Service on Apps Script](https://developers.google.com/apps-script/advanced/drive)
- [Google Workspace developers home page](https://developers.google.com/workspace)
- [GWS developers home page](https://developers.google.com/workspace)
- [Stack Overflow](https://stackoverflow.com/questions/tagged/google-drive-sdk)

If you've found an error in the codelab or the sample app, check the [Issues](https://github.com/googlecodelabs/gsuite-apis-intro/issues) tab to see if there's an open issue or file a new one. Patches are encouraged; please refer to [CONTRIBUTING](CONTRIBUTING.md) for details.
89 changes: 89 additions & 0 deletions nodejs/drive_list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
POST: ...
FILE: drive_list.js
COPYRIGHT: (c)2023 CyberWeb Consulting LLC
LICENSE: apache.org/licenses/LICENSE-2.0
*/

const fs = require('fs').promises;
const path = require('path');
const process = require('process');
const {authenticate} = require('@google-cloud/local-auth');
const {google} = require('googleapis');

const CREDENTIALS_PATH = path.join(process.cwd(), 'client_secret.json');
const TOKEN_PATH = path.join(process.cwd(), 'storage.json');
const SCOPES = ['https://www.googleapis.com/auth/drive.metadata.readonly'];


/**
* Load any saved credentials or null
*
* @return {(Promise<JSONClient|null>)}
*/
async function loadSavedCredentialsIfExist() {
try {
const content = await fs.readFile(TOKEN_PATH);
const credentials = JSON.parse(content);
return google.auth.fromJSON(credentials);
} catch (err) {
return null;
}
}


/**
* Save credentials
*
* @param {JSONClient} client credentials
* @return {Promise<void>}
*/
async function saveCredentials(client) {
const content = await fs.readFile(CREDENTIALS_PATH);
const keys = JSON.parse(content);
const key = keys.installed || keys.web;
const payload = JSON.stringify({
type: 'authorized_user',
client_id: key.client_id,
client_secret: key.client_secret,
refresh_token: client.credentials.refresh_token,
access_token: client.credentials.access_token,
token_expiry: client.credentials.token_expiry,
scopes: client.credentials.scopes,
});
await fs.writeFile(TOKEN_PATH, payload);
}


/**
* Authorize user credentials, authenticating user first if necessary
*
* @return {JSONClient} client credentials
*/
async function authorize() {
var client = await loadSavedCredentialsIfExist();
if (client) return client;
client = await authenticate({
scopes: SCOPES,
keyfilePath: CREDENTIALS_PATH,
});
if (client.credentials) await saveCredentials(client);
return client;
}


/**
* Request file lookup via Google Drive API
*
* @param {JSONClient} authClient (client credentials)
*/
async function listFiles(authClient) {
const drive = google.drive({version: 'v3', auth: authClient});
const res = await drive.files.list();
const files = res.data.files || [];
for (let file of files) {
console.log(`${file.name} (${file.mimeType})`);
}
}

authorize().then(listFiles).catch(console.error);
89 changes: 89 additions & 0 deletions nodejs/drive_list.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
POST: ...
FILE: drive_list.js
COPYRIGHT: (c)2023 CyberWeb Consulting LLC
LICENSE: apache.org/licenses/LICENSE-2.0
*/

import fs from 'node:fs/promises';
import path from 'node:path';
import process from 'node:process';
import {authenticate} from '@google-cloud/local-auth';
import {google} from 'googleapis';

const CREDENTIALS_PATH = path.join(process.cwd(), 'client_secret.json');
const TOKEN_PATH = path.join(process.cwd(), 'storage.json');
const SCOPES = ['https://www.googleapis.com/auth/drive.metadata.readonly'];


/**
* Load any saved credentials or null
*
* @return {(Promise<JSONClient|null>)}
*/
async function loadSavedCredentialsIfExist() {
try {
const content = await fs.readFile(TOKEN_PATH);
const credentials = JSON.parse(content);
return google.auth.fromJSON(credentials);
} catch (err) {
return null;
}
}


/**
* Save credentials
*
* @param {JSONClient} client credentials
* @return {Promise<void>}
*/
async function saveCredentials(client) {
const content = await fs.readFile(CREDENTIALS_PATH);
const keys = JSON.parse(content);
const key = keys.installed || keys.web;
const payload = JSON.stringify({
type: 'authorized_user',
client_id: key.client_id,
client_secret: key.client_secret,
refresh_token: client.credentials.refresh_token,
access_token: client.credentials.access_token,
token_expiry: client.credentials.token_expiry,
scopes: client.credentials.scopes,
});
await fs.writeFile(TOKEN_PATH, payload);
}


/**
* Authorize user credentials, authenticating user first if necessary
*
* @return {JSONClient} client credentials
*/
async function authorize() {
var client = await loadSavedCredentialsIfExist();
if (client) return client;
client = await authenticate({
scopes: SCOPES,
keyfilePath: CREDENTIALS_PATH,
});
if (client.credentials) await saveCredentials(client);
return client;
}


/**
* Request file lookup via Google Drive API
*
* @param {JSONClient} authClient (client credentials)
*/
async function listFiles(authClient) {
const drive = google.drive({version: 'v3', auth: authClient});
const res = await drive.files.list();
const files = res.data.files || [];
for (let file of files) {
console.log(`${file.name} (${file.mimeType})`);
}
}

authorize().then(listFiles).catch(console.error);
12 changes: 12 additions & 0 deletions nodejs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"dependencies": {
"@google-cloud/local-auth": "^2.1.1",
"googleapis": "^118.0.0"
},
"description": "Google Drive API Node.js samples",
"license": "Apache-2.0",
"devDependencies": {
"eslint": "^8.41.0",
"eslint-config-google": "^0.14.0"
}
}
11 changes: 5 additions & 6 deletions drive_list-new.py → python/drive_list-new.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2018-2020 Google LLC
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -16,26 +16,25 @@
import os.path

from google.auth.transport.requests import Request
from google.oauth2 import credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient import discovery
from google.oauth2 import credentials

creds = None
SCOPES = 'https://www.googleapis.com/auth/drive.metadata.readonly'
TOKENS = 'tokens.json' # where to store access & refresh tokens
TOKENS = 'storage.json'
if os.path.exists(TOKENS):
creds = credentials.Credentials.from_authorized_user_file(TOKENS)
if not (creds and creds.valid):
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'client_secret.json', SCOPES)
flow = InstalledAppFlow.from_client_secrets_file('client_secret.json', SCOPES)
creds = flow.run_local_server()
with open(TOKENS, 'w') as token:
token.write(creds.to_json())

DRIVE = discovery.build('drive', 'v3', credentials=creds)
files = DRIVE.files().list().execute().get('files', [])
for f in files: # 4 fields returned: mimeType, kind, id, name
for f in files: # 4 fields returned: mimeType, kind, id, name
print(f['name'], f['mimeType'])
6 changes: 3 additions & 3 deletions drive_list.py → python/drive_list.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2018-2020 Google LLC
# Copyright 2018 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -22,10 +22,10 @@
store = file.Storage('storage.json')
creds = store.get()
if not creds or creds.invalid:
flow = client.flow_from_clientsecrets('client_id.json', SCOPES)
flow = client.flow_from_clientsecrets('client_secret.json', SCOPES)
creds = tools.run_flow(flow, store)

DRIVE = discovery.build('drive', 'v3', http=creds.authorize(Http()))
files = DRIVE.files().list().execute().get('files', [])
for f in files:
for f in files: # 4 fields returned: mimeType, kind, id, name
print(f['name'], f['mimeType'])