diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index fb841b34..dc35067e 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -6,7 +6,7 @@ Changelog - :func:`read_gbq` now raises ``QueryTimeout`` if the request exceeds the ``query.timeoutMs`` value specified in the BigQuery configuration. (:issue:`76`) - Environment variable ``PANDAS_GBQ_CREDENTIALS_FILE`` can now be used to override the default location where the BigQuery user account credentials are stored. (:issue:`86`) - +- BigQuery user account credentials are now stored in an application-specific hidden user folder on the operating system. (:issue:`41`) 0.2.0 / 2017-07-24 ------------------ diff --git a/docs/source/intro.rst b/docs/source/intro.rst index fd64a4fb..3cac399a 100644 --- a/docs/source/intro.rst +++ b/docs/source/intro.rst @@ -37,7 +37,9 @@ is possible with either user or service account credentials. Authentication via user account credentials is as simple as following the prompts in a browser window which will automatically open for you. You authenticate to the specified ``BigQuery`` account using the product name ``pandas GBQ``. -The remote authentication is supported via specifying ``auth_local_webserver`` in ``read_gbq``. +The remote authentication is supported via the ``auth_local_webserver`` in ``read_gbq``. By default, +account credentials are stored in an application-specific hidden user folder on the operating system. You +can override the default credentials location via the ``PANDAS_GBQ_CREDENTIALS_FILE`` environment variable. Additional information on the authentication mechanism can be found `here `__. diff --git a/pandas_gbq/gbq.py b/pandas_gbq/gbq.py index d871c068..9473b082 100644 --- a/pandas_gbq/gbq.py +++ b/pandas_gbq/gbq.py @@ -208,6 +208,7 @@ def __init__(self, project_id, reauth=False, verbose=False, self.private_key = private_key self.auth_local_webserver = auth_local_webserver self.dialect = dialect + self.credentials_path = _get_credentials_file() self.credentials = self.get_credentials() self.service = self.get_service() @@ -279,8 +280,21 @@ def load_user_account_credentials(self): from google_auth_httplib2 import Request from google.oauth2.credentials import Credentials + # Use the default credentials location under ~/.config and the + # equivalent directory on windows if the user has not specified a + # credentials path. + if not self.credentials_path: + self.credentials_path = self.get_default_credentials_path() + + # Previously, pandas-gbq saved user account credentials in the + # current working directory. If the bigquery_credentials.dat file + # exists in the current working directory, move the credentials to + # the new default location. + if os.path.isfile('bigquery_credentials.dat'): + os.rename('bigquery_credentials.dat', self.credentials_path) + try: - with open(_get_credentials_file()) as credentials_file: + with open(self.credentials_path) as credentials_file: credentials_json = json.load(credentials_file) except (IOError, ValueError): return None @@ -301,6 +315,33 @@ def load_user_account_credentials(self): return _try_credentials(self.project_id, credentials) + def get_default_credentials_path(self): + """ + Gets the default path to the BigQuery credentials + + .. versionadded 0.3.0 + + Returns + ------- + Path to the BigQuery credentials + """ + + import os + + if os.name == 'nt': + config_path = os.environ['APPDATA'] + else: + config_path = os.path.join(os.path.expanduser('~'), '.config') + + config_path = os.path.join(config_path, 'pandas_gbq') + + # Create a pandas_gbq directory in an application-specific hidden + # user folder on the operating system. + if not os.path.exists(config_path): + os.makedirs(config_path) + + return os.path.join(config_path, 'bigquery_credentials.dat') + def save_user_account_credentials(self, credentials): """ Saves user account credentials to a local file. @@ -308,7 +349,7 @@ def save_user_account_credentials(self, credentials): .. versionadded 0.2.0 """ try: - with open(_get_credentials_file(), 'w') as credentials_file: + with open(self.credentials_path, 'w') as credentials_file: credentials_json = { 'refresh_token': credentials.refresh_token, 'id_token': credentials.id_token, @@ -793,7 +834,7 @@ def delete_and_recreate_table(self, dataset_id, table_id, table_schema): def _get_credentials_file(): return os.environ.get( - 'PANDAS_GBQ_CREDENTIALS_FILE', 'bigquery_credentials.dat') + 'PANDAS_GBQ_CREDENTIALS_FILE') def _parse_data(schema, rows): diff --git a/pandas_gbq/tests/test_gbq.py b/pandas_gbq/tests/test_gbq.py index f61e4b52..62b72dbc 100644 --- a/pandas_gbq/tests/test_gbq.py +++ b/pandas_gbq/tests/test_gbq.py @@ -1388,6 +1388,7 @@ def setup_method(self, method): # put here any instruction you want to be run *BEFORE* *EVERY* test # is executed. + gbq.GbqConnector(_get_project_id(), auth_local_webserver=True) self.dataset_prefix = _get_dataset_prefix_random() clean_gbq_environment(self.dataset_prefix) self.destination_table = "{0}{1}.{2}".format(self.dataset_prefix, "2",