Google Drive API appdata scope

Hi there,

Is there any way to easily use the application data API from Google Drive (scope: https://www.googleapis.com/auth/drive.appdata ) using the same simple bindings for Google Drive manipulation that anvil provides (as shown in the docs)?

The example shown in the docs requires full user drive access which is an overkill for my use and an unnecessary security risk…

Thanks!

Bump.

Any pointers? :upside_down_face:

Hi @mglraimundo,

This would be a helpful addition to our Google Drive integration - moving to Feature Requests :slight_smile:

You’re correct that the Google service currently requests permissions all at once rather than incrementally - see related Feature Request

Here’s my implementation until Anvil extends its built in integration. Maybe it will be helpful to someone. It uses the googleapiclient library for python and, while the example pertains to the Drive AppData scope, the logic is easily adaptable to any Google API. Permissions are obtained in a granular scope specific way (so no more outrageous prompts for full drive access and so on).

You need to store the client_id and client_secret in both the Google API Drive section (for google.auth) and App Secrets (for credential building, see below). We also store the tokens in the respective user row in the datatable (in my case a linked [‘preferences’] table).

On the client side we can call this function whenever we need new tokens:

def gdrive_gettoken():
  
    success = False

    anvil.google.auth.login(["https://www.googleapis.com/auth/drive.appdata"])

    access_token = anvil.google.auth.get_user_access_token()
    refresh_token = anvil.google.auth.get_user_refresh_token()

    if access_token:
      anvil.server.call('gdrive_store_tokens', access_token=access_token, refresh_token=refresh_token)
      success = True

    return success

On the server side we store the tokens (gdrive_store_tokens) and build some credentials (gdrive_creds).

# google apis
import google.oauth2.credentials
from google.auth.transport import requests
from googleapiclient.discovery import build
from googleapiclient.http import MediaFileUpload
  
@anvil.server.callable
def gdrive_store_tokens(access_token, refresh_token):
  anvil.users.get_user()['preferences']['gdrive_token'] = access_token
  anvil.users.get_user()['preferences']['gdrive_rtoken'] = refresh_token

def gdrive_creds():
  
  access_token = anvil.users.get_user()['preferences']['gdrive_token']
  refresh_token = refresh_token=anvil.users.get_user()['preferences']['gdrive_rtoken']
  client_id = anvil.secrets.get_secret('google_client_id')
  client_secret = anvil.secrets.get_secret('google_client_secret')
  token_uri = "https://accounts.google.com/o/oauth2/token"
  
  creds = google.oauth2.credentials.Credentials(access_token, 
                                                refresh_token=refresh_token, 
                                                token_uri=token_uri, client_id=client_id, client_secret=client_secret)
  
  if creds.valid:
    pass
    #print('valid credentials')
  else:

    try:
      creds.refresh(requests.Request())
      if creds.valid:
        #print('refreshed credentials')
        anvil.users.get_user()['preferences']['gdrive_token'] = creds.token
    except google.auth.exceptions.RefreshError:
      #print('failed credentials refresh')
      anvil.users.get_user()['preferences']['gdrive_token'] = None
      anvil.users.get_user()['preferences']['gdrive_rtoken'] = None
      return None

  return creds

And an example of using the API (in this case to update an existing database file whose id is stored in a datatable). Wrapping it all together in a while loop on the client side, we can quite graciously get access and refresh tokens, refresh the access token using the refresh token and, if everything fails, prompt the user for a new login (we even give the user three tries before failing):

done = False
limit = 3
rep = 0

while done is False and rep <= limit:
  
  if anvil.users.get_user(allow_remembered=True)['preferences']['gdrive_rtoken']:
    success = True
  else:
    success = gdrive_gettoken()
  
  if success:
    done = anvil.server.call('gdrive_updatedb', db_file)
  
  rep = rep + 1

if done is True:
  self.label_gdrive.text = "Updated your Google Drive successfully!"
else:
  self.label_gdrive.text = "Error connecting to your Google Drive!"

On the server side:

@anvil.server.callable
def gdrive_updatedb(payload):
  
  done = False
  creds = gdrive_creds()
  
  if creds is not None:
    
    drive_service = build('drive', 'v3', credentials=creds)
    
    with anvil.media.TempFile(payload) as db:
        
      media = MediaFileUpload(db,
                        mimetype=payload.content_type,
                        resumable=True)
        
      request = drive_service.files().update(
            fileId=anvil.users.get_user()['preferences']['gdrive_dbid'],
            media_body=media,
            fields='id')
      gdrive_db = request.execute()
    
    done = True
    
  else:
    done = False
    
  return done
4 Likes

Thanks for sharing this @mglraimundo - very easy to follow and reuse. Awsome!