5

I'm using PyDrive to upload files to GoogleDrive from a desktop Python app that I'm packaging with Pyinstaller. I'd like to hide the client_secrets.json as much as possible so I've embedded it within the Pyinstaller .exe file, using this solution: Bundling Data files with PyInstaller 2.1 and MEIPASS error --onefile

Yet, PyDrive is not finding the client_secrets file from the temp directory where Pyinstaller places data file.

How do I get PyDrive to read the json file from another directory, particularly AppData? I was thinking of moving the file from the temp directory to the working directory before authenticating and deleting if after, but some users don't have admin access and can't modify Program Files (where the app is installed)

I see I can use a settings.yaml file that I can refer to another directory, but pyinstaller seems to place the embedded client_secrets.json in a temp folder using a sys._MEIPASS variable, so I don't know where it will be.

I would have to pass that valuable to GoogleAuth() directly, is there a way to do this?

Community
  • 1
  • 1
user2509710
  • 79
  • 1
  • 7

4 Answers4

18

Much easier solution:

from pydrive.auth import GoogleAuth
GoogleAuth.DEFAULT_SETTINGS['client_config_file'] = path_to_secrets_file
Grzegorz Krukar
  • 196
  • 1
  • 3
  • 1
    Is there a similar trick for the settings.yaml file? I wanted to put that in a subfolder... – Xodarap777 Jun 26 '18 at 03:55
  • i would like to hash my secrets file so if someone finds it, they can't use it. So, is there a way to pass the contents of this file to googleauth, instead of the file path? I will try editing the source, if not. – Edo Edo Aug 02 '19 at 17:10
2

Really not the best solution but i just modified the Pydrive auth.py file by myself to do it...

I used the trick i used to add in my code about MEIPASS by adding this method :

 def resource_path(self,relative_path):
        """ Get absolute path to resource, works for dev and for PyInstaller """
        try:
            # PyInstaller creates a temp folder and stores path in _MEIPASS
            base_path = sys._MEIPASS
        except Exception:
            base_path = os.path.abspath(".")
        return os.path.join(base_path, relative_path)

And modifying these methods :

The method to load the credential file : ( I don't care about were it appears, but you can of course not set it as mine )

def LoadCredentialsFile(self, credentials_file=None):
    """Loads credentials or create empty credentials if it doesn't exist.

    Loads credentials file from path in settings if not specified.

    :param credentials_file: path of credentials file to read.
    :type credentials_file: str.
    :raises: InvalidConfigError, InvalidCredentialsError
    """
    if credentials_file is None:
      credentials_file = self.settings.get('save_credentials_file')
      if credentials_file is None:
        raise InvalidConfigError('Please specify credentials file to read')
    try:
      storage = Storage(self.resource_path(credentials_file))
      self.credentials = storage.get()
    except CredentialsFileSymbolicLinkError:
      raise InvalidCredentialsError('Credentials file cannot be symbolic link')

The method to load the client.secrets :

  def LoadClientConfigFile(self, client_config_file=None):
    """Loads client configuration file downloaded from APIs console.

    Loads client config file from path in settings if not specified.

    :param client_config_file: path of client config file to read.
    :type client_config_file: str.
    :raises: InvalidConfigError
    """
    if client_config_file is None:
      client_config_file = self.settings['client_config_file']
    try:
      client_type, client_info = clientsecrets.loadfile(self.resource_path(client_config_file))
...method continue here...

And the init method :

def __init__(self, settings_file='settings.yaml',http_timeout=None):
    """Create an instance of GoogleAuth.

    This constructor just sets the path of settings file.
    It does not actually read the file.

    :param settings_file: path of settings file. 'settings.yaml' by default.
    :type settings_file: str.
    """
    self.http_timeout=http_timeout
    ApiAttributeMixin.__init__(self)
    self.client_config = {}
    try:
      self.settings = LoadSettingsFile(self.resource_path(settings_file))
    except SettingsError:
      self.settings = self.DEFAULT_SETTINGS
    else:
      if self.settings is None:
        self.settings = self.DEFAULT_SETTINGS
      else:
        ValidateSettings(self.settings)

I know it's probably not the good way to do it :) But if someone know a better way, but it works...

So if someone have a better solution, please let me know :)

Avantis
  • 21
  • 2
2

When setting the path, make sure not to have a leading / in your path:

eg: path_to_secrets_file = '/docs/secrets.json' -> this would search from the root and complain file was not found. If you omit the / it merges with current working folder correctly.

from pydrive.auth import GoogleAuth
GoogleAuth.DEFAULT_SETTINGS['client_config_file'] = path_to_secrets_file
1

To change the default settings file you need to set it when instantiating the GAuth instance. Below, I have created a subfolder called config_files in the working directory and I have renamed the pydrive settings yaml as below:

from pydrive2.auth import GoogleAuth

gauth = GoogleAuth(settings_file='config_files/pydrive_settings.yaml')
Avagut
  • 924
  • 3
  • 18
  • 34