Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is there a way to authenticate without LocalWebserverAuth() ? #230

Closed
matiasrebori opened this issue Sep 12, 2022 · 12 comments
Closed

Is there a way to authenticate without LocalWebserverAuth() ? #230

matiasrebori opened this issue Sep 12, 2022 · 12 comments

Comments

@matiasrebori
Copy link
Contributor

matiasrebori commented Sep 12, 2022

Hi, is there a way to authenticate to Drive without using the LocalWebserverAuth() method, because it promps a login page from the browser and i dont want this behaviour.
I have a flask application and one feature is to save images to a specific google drive folder (uses a single organization account), this runs in a linux vm.
i have a client_secrets.json file, this is the JSON downloaded from Google Developers Console, also i set the settings.yaml file to create a credentials.json file to automate the consequents authentications.
The problem is i dont want to manually login for the first time if there isnt a credentials.json file.
Im using the next piece of code to login to google drive

from pydrive2.auth import GoogleAuth
from pydrive2.drive import GoogleDrive

CREDENTIALS_NAME = 'credentials.json'
DRIVE_FOLDER = 'xxxxxxxxxxxxxxxxxxxx'


def login():
    GoogleAuth.DEFAULT_SETTINGS['client_config_file'] = CREDENTIALS_NAME
    # create an instance of GoogleAuth.
    gauth = GoogleAuth()
    # loads credentials or create empty credentials if it doesn't exist.
    gauth.LoadCredentialsFile(CREDENTIALS_NAME)

    if gauth.credentials is None:
        # authenticate and authorize from user by creating local web server and retrieving authentication code.
        gauth.LocalWebserverAuth()
    elif gauth.access_token_expired:
        # refreshes the access_token.
        gauth.Refresh()
    else:
        # authorizes and builds service.
        gauth.Authorize()
    # saves credentials to the file in JSON format.
    gauth.SaveCredentialsFile(CREDENTIALS_NAME)
    # create an instance of Google Drive.
    gdrive = GoogleDrive(gauth)
    return gdrive

I have a code to get data from Google Sheets and only uses the JSON file from Google Developers Console, in this code i named this file credentials.json. So i think theres probably a similiar way to authenticate to Google Drive.

def load_sheet(page_name: str) -> list | None:
    data_range: str = page_name + DATA_RANGE
    # service account
    sa = gspread.service_account('credentials.json')
    # credentials
    creds = sa.auth
    # load sheet
    try:
        service = build('sheets', 'v4', credentials=creds)

        # Call the Sheets API
        sheet = service.spreadsheets()
        result = sheet.values().get(spreadsheetId=SAMPLE_SPREADSHEET_ID,
                                    range=data_range).execute()
        values = result.get('values', [])
        if not values:
            print('No data found.')
            return
        return values

    except HttpError as err:
        print(err)
        quit()
@shcheklein
Copy link
Member

Hey, yes service account - see example here #21 or https://docs.iterative.ai/PyDrive2/pydrive2/#pydrive2.auth.GoogleAuth.LocalWebserverAuth . Please give one of those a try and let us know if something doesn't work.

Also, see how fsspec - https://github.com/iterative/PyDrive2/blob/main/pydrive2/fs/spec.py#L132

@matiasrebori
Copy link
Contributor Author

Thanks you for your help, the example you provided is working. One last question i have a function to upload files like this

def upload_file(filepath, filename, id_drive_folder=DRIVE_FOLDER):
    """
    upload a file to desired folder.

    :param filepath: ubicacion del archivo
    :param filename: nombre del archivo
    :param id_drive_folder: id de la carpeta del drive
    :return:
    """
    gdrive = login()
    metadata = {
        'parents': [
            {"kind": "drive#fileLink", "id": id_drive_folder}
        ],
        'title': f'{filename}'
    }
    # create file
    archivo = gdrive.CreateFile(metadata=metadata)
    # set the content of the file
    archivo.SetContentFile(filepath)
    # upload the file to google drive
    archivo.Upload()

this creates a new instance of GoogleDrive() for every call when doing gdrive = login(), is this approach good or is better to have only one instance of GoogleDrive() authenticated when the application starts i.g. instanciate only one time gdrive = login() and use this instance to all the consequent calls

@matiasrebori
Copy link
Contributor Author

@shcheklein im trying to upload a base64 image stored in memory, but SetContentFile doesnt accepts io.BytesIO(). Exists a way to uploads files with io.BytesIO?

@shcheklein
Copy link
Member

is this approach good or is better to have only one instance of GoogleDrive() authenticated when the application starts i.g. instanciate only one time gdrive = login() and use this instance to all the consequent calls

One instance should be enough. You don't need to do the whole workflow every time (it'll be cached, but anyway you don't need this)

@shcheklein
Copy link
Member

im trying to upload a base64 image stored in memory, but SetContentFile doesnt accepts io.BytesIO(). Exists a way to uploads files with io.BytesIO?

yes, you could do SetContentString, or you could do even something like:

file.content = io.BytesIO(...)
files.Upload()

@matiasrebori
Copy link
Contributor Author

im trying to upload a base64 image stored in memory, but SetContentFile doesnt accepts io.BytesIO(). Exists a way to uploads files with io.BytesIO?

yes, you could do SetContentString, or you could do even something like:

file.content = io.BytesIO(...)
files.Upload()

thanks for your help. I couldn't make it work with SetContentString(). It worked with the second recommendation.

a function receives the param bytes_file

      # create file
      archivo = gdrive.CreateFile(metadata=metadata)
      # set the content of the file
      archivo.content = bytes_file
      # upload the file to google drive
      archivo.Upload()

then to execute

    import base64
    import io

    file = open(r'C:\Users\xxx\Downloads\base64.txt', 'r')
    # read base64 string
    base64_string: str = file.read()
    # decode to data bytes
    image_bytes: bytes = base64.b64decode(base64_string)
    # Buffered I/O implementation using an in-memory bytes buffer.
    image_file = io.BytesIO(image_bytes)
    # upload file
    # file, filename, mimetype 
    upload_media(image_bytes, 'test2_4', 'image/jpeg')

@matiasrebori
Copy link
Contributor Author

matiasrebori commented Sep 14, 2022

@shcheklein if you want i can contribute to the docs with how to authenticate with a Service Account and how to upload media.
Also i think SetContentFile can easily support io.BytesIO(), or we could do another method like SetMediaFile() or something like this.

@shcheklein
Copy link
Member

@matiasrebori that would very helpful, thanks.

Also i think SetContentFile can easily support io.BytesIO(),

I'm not sure about this tbh. It's quite explicit and does one thing. SetContentString or we may introduce another wrapper SetContentStream or something. But also, I think it's fine that you could do self.content = - for now. We can document it also.

@matiasrebori
Copy link
Contributor Author

Ok, I'm not advanced on GitHub, should I open a new issue to link later with a PR?

@shcheklein
Copy link
Member

Creating a PR if fine an enough!

@matiasrebori
Copy link
Contributor Author

is this approach good or is better to have only one instance of GoogleDrive() authenticated when the application starts i.g. instanciate only one time gdrive = login() and use this instance to all the consequent calls

One instance should be enough. You don't need to do the whole workflow every time (it'll be cached, but anyway you don't need this)

Hi, do you know if the low level API googleapiclient also caches or is this a feature of pyDrive ?`
Another question, i made a function to get a specific file by name and mimetype

archivo = gdrive.ListFile(query).GetList()
query = {'q': f"title = '{filename}' and mimeType='{mimetype}'"}

the name param in q search files using the low level API v3 works but it doesn't in pyDrive HttpError 400 Invalid query idk if this is wrong or right, instead I find that the param title worked with pyDrive as exact replacement for name.
When i have a little more time i'll work in the PR :)

@shcheklein
Copy link
Member

Hi, do you know if the low level API googleapiclient also caches or is this a feature of pyDrive ?`

I don't think googleapiclient has any mechanism to cache credentials, refresh tokens, etc.

My point was that, even if you do multiple instances it should be also fine if you use some default settings since PyDrive will create a files with credentials that it'll try to reuse next time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants