Skip to content

Commit 9ec2a68

Browse files
committed
Issue #13577
1 parent 5701c69 commit 9ec2a68

File tree

3 files changed

+42
-3
lines changed

3 files changed

+42
-3
lines changed

doc/source/whatsnew/v0.19.0.txt

+2
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@ Using the anchoring suffix, you can also specify the day of month to use instead
185185
Other enhancements
186186
^^^^^^^^^^^^^^^^^^
187187

188+
- The ``.get_credentials()`` method of ``GbqConnector`` can now first try to fetch the default credentials for Google Compute Engine without the need to run ``OAuth2WebServerFlow`` - if private_key is not provided (:issue:`13577`)
189+
188190
- The ``.tz_localize()`` method of ``DatetimeIndex`` and ``Timestamp`` has gained the ``errors`` keyword, so you can potentially coerce nonexistent timestamps to ``NaT``. The default behaviour remains to raising a ``NonExistentTimeError`` (:issue:`13057`)
189191

190192
- ``Index`` now supports ``.str.extractall()`` which returns a ``DataFrame``, see :ref:`documentation here <text.extractall>` (:issue:`10008`, :issue:`13156`)

pandas/io/gbq.py

+33-3
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,33 @@ def get_credentials(self):
159159
if self.private_key:
160160
return self.get_service_account_credentials()
161161
else:
162-
return self.get_user_account_credentials()
162+
# Try to retrieve Application Default Credentials
163+
credentials = self.get_application_default_credentials()
164+
if not credentials:
165+
credentials = self.get_user_account_credentials()
166+
return credentials
167+
168+
def get_application_default_credentials(self):
169+
from oauth2client.client import GoogleCredentials
170+
from oauth2client.client import AccessTokenRefreshError
171+
from oauth2client.client import ApplicationDefaultCredentialsError
172+
from apiclient.discovery import build
173+
from apiclient.errors import HttpError
174+
175+
credentials = None
176+
try:
177+
credentials = GoogleCredentials.get_application_default()
178+
except ApplicationDefaultCredentialsError:
179+
return None
180+
# Check if the application has rights to the BigQuery project
181+
bigquery_service = build('bigquery', 'v2', credentials=credentials)
182+
job_collection = bigquery_service.jobs()
183+
job_data = {'configuration': {'query': {'query': 'SELECT 1'}}}
184+
try:
185+
job_collection.insert(projectId=self.project_id, body=job_data).execute()
186+
except (AccessTokenRefreshError, HttpError):
187+
return None
188+
return credentials
163189

164190
def get_user_account_credentials(self):
165191
from oauth2client.client import OAuth2WebServerFlow
@@ -576,7 +602,9 @@ def read_gbq(query, project_id=None, index_col=None, col_order=None,
576602
https://developers.google.com/api-client-library/python/apis/bigquery/v2
577603
578604
Authentication to the Google BigQuery service is via OAuth 2.0.
579-
By default user account credentials are used. You will be asked to
605+
By default "application default credentials" are used.
606+
If default application credentials are not found or are restrictive -
607+
User account credentials are used. You will be asked to
580608
grant permissions for product name 'pandas GBQ'. It is also posible
581609
to authenticate via service account credentials by using
582610
private_key parameter.
@@ -672,7 +700,9 @@ def to_gbq(dataframe, destination_table, project_id, chunksize=10000,
672700
https://developers.google.com/api-client-library/python/apis/bigquery/v2
673701
674702
Authentication to the Google BigQuery service is via OAuth 2.0.
675-
By default user account credentials are used. You will be asked to
703+
By default "application default credentials" are used.
704+
If default application credentials are not found or are restrictive -
705+
User account credentials are used. You will be asked to
676706
grant permissions for product name 'pandas GBQ'. It is also posible
677707
to authenticate via service account credentials by using
678708
private_key parameter.

pandas/io/tests/test_gbq.py

+7
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,10 @@ def _test_imports():
8080
from apiclient.discovery import build # noqa
8181
from apiclient.errors import HttpError # noqa
8282

83+
from oauth2client.client import GoogleCredentials # noqa
8384
from oauth2client.client import OAuth2WebServerFlow # noqa
8485
from oauth2client.client import AccessTokenRefreshError # noqa
86+
from oauth2client.client import ApplicationDefaultCredentialsError # noqa
8587

8688
from oauth2client.file import Storage # noqa
8789
from oauth2client.tools import run_flow # noqa
@@ -217,6 +219,11 @@ def test_should_be_able_to_get_results_from_query(self):
217219
schema, pages = self.sut.run_query('SELECT 1')
218220
self.assertTrue(pages is not None)
219221

222+
def test_should_be_able_to_get_a_bigquery_service_from_default_credentials(self):
223+
from oauth2client.client import GoogleCredentials
224+
credentials = self.sut.get_application_default_credentials()
225+
self.assertTrue(isinstance(credentials, (type(None), GoogleCredentials)))
226+
220227

221228
class TestGBQConnectorServiceAccountKeyPathIntegration(tm.TestCase):
222229
def setUp(self):

0 commit comments

Comments
 (0)