Skip to content

Commit 90da90f

Browse files
committed
Address review comments.
1 parent acee867 commit 90da90f

File tree

8 files changed

+286
-120
lines changed

8 files changed

+286
-120
lines changed

ci/requirements-2.7.pip

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
httplib2>=0.9.1
2-
google-api-python-client>=1.6.2, <2.0.0dev
3-
google-auth>=1.0.0, <2.0.0dev
4-
google-auth-httplib2>=0.0.2, <2.0.0dev
5-
google-auth-oauthlib>=0.1.0, <2.0.0dev
1+
google-api-python-client
2+
google-auth
3+
google-auth-httplib2
4+
google-auth-oauthlib
65
PyCrypto
76
python-gflags==2.0
7+
mock

ci/requirements-3.4.pip

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
httplib2>=0.9.1
2-
google-api-python-client>=1.6.2, <2.0.0dev
3-
google-auth>=1.0.0, <2.0.0dev
4-
google-auth-httplib2>=0.0.2, <2.0.0dev
5-
google-auth-oauthlib>=0.1.0, <2.0.0dev
1+
google-api-python-client
2+
google-auth
3+
google-auth-httplib2
4+
google-auth-oauthlib
5+
mock

ci/requirements-3.5.pip

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
httplib2>=0.9.1
2-
google-api-python-client>=1.6.2, <2.0.0dev
3-
google-auth>=1.0.0, <2.0.0dev
4-
google-auth-httplib2>=0.0.2, <2.0.0dev
5-
google-auth-oauthlib>=0.1.0, <2.0.0dev
1+
google-api-python-client
2+
google-auth
3+
google-auth-httplib2
4+
google-auth-oauthlib
5+
mock

ci/requirements-3.6.pip

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
httplib2>=0.9.1
2-
google-api-python-client>=1.6.2, <2.0.0dev
3-
google-auth>=1.0.0, <2.0.0dev
4-
google-auth-httplib2>=0.0.2, <2.0.0dev
5-
google-auth-oauthlib>=0.1.0, <2.0.0dev
1+
google-api-python-client
2+
google-auth
3+
google-auth-httplib2
4+
google-auth-oauthlib
5+
mock

docs/source/changelog.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Changelog
44
0.1.7 / 2017-??-??
55
------------------
66

7-
- Use the `google-auth <https://google-auth.readthedocs.io/en/latest/>`__ library for authentication because oauth2client is deprecated. :issue:`37`
7+
- Use the `google-auth <https://google-auth.readthedocs.io/en/latest/>`__ library for authentication because oauth2client is deprecated. (:issue:`39`)
88
- ``read_gbq`` now has a ``auth_local_webserver`` boolean argument for controlling whether to use web server or console flow when getting user credentials.
99

1010
0.1.6 / 2017-05-03

pandas_gbq/gbq.py

+131-73
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,73 @@
66
import time
77
import sys
88

9-
import google.auth
10-
import google.oauth2.credentials
11-
from google.oauth2 import service_account
12-
import google_auth_httplib2
13-
from google_auth_oauthlib import flow
14-
import httplib2
159
import numpy as np
1610

1711
from distutils.version import StrictVersion
1812
from pandas import compat, DataFrame, concat
1913
from pandas.compat import lzip, bytes_to_str
2014

2115

16+
def _check_google_client_version():
17+
18+
try:
19+
import pkg_resources
20+
21+
except ImportError:
22+
raise ImportError('Could not import pkg_resources (setuptools).')
23+
24+
# Version 1.6.0 is the first version to support google-auth.
25+
# https://github.com/google/google-api-python-client/blob/master/CHANGELOG
26+
google_api_minimum_version = '1.6.0'
27+
28+
_GOOGLE_API_CLIENT_VERSION = pkg_resources.get_distribution(
29+
'google-api-python-client').version
30+
31+
if (StrictVersion(_GOOGLE_API_CLIENT_VERSION) <
32+
StrictVersion(google_api_minimum_version)):
33+
raise ImportError("pandas requires google-api-python-client >= {0} "
34+
"for Google BigQuery support, "
35+
"current version {1}"
36+
.format(google_api_minimum_version,
37+
_GOOGLE_API_CLIENT_VERSION))
38+
39+
40+
def _test_google_api_imports():
41+
42+
try:
43+
import httplib2 # noqa
44+
from googleapiclient.discovery import build # noqa
45+
from googleapiclient.errors import HttpError # noqa
46+
import google.auth #noqa
47+
from google_auth_oauthlib.flow import InstalledAppFlow #noqa
48+
import google_auth_httplib2 #noqa
49+
except ImportError as e:
50+
raise ImportError("Missing module required for Google BigQuery "
51+
"support: {0}".format(str(e)))
52+
53+
54+
def _try_credentials(project_id, credentials):
55+
import httplib2
56+
from googleapiclient.discovery import build
57+
import googleapiclient.errors
58+
from google_auth_httplib2 import AuthorizedHttp
59+
60+
if credentials is None:
61+
return None
62+
63+
http = httplib2.Http()
64+
try:
65+
http = AuthorizedHttp(credentials, http=http)
66+
bigquery_service = build('bigquery', 'v2', http=http)
67+
# Check if the application has rights to the BigQuery project
68+
jobs = bigquery_service.jobs()
69+
job_data = {'configuration': {'query': {'query': 'SELECT 1'}}}
70+
jobs.insert(projectId=project_id, body=job_data).execute()
71+
return credentials
72+
except googleapiclient.errors.Error:
73+
return None
74+
75+
2276
class InvalidPrivateKeyFormat(ValueError):
2377
"""
2478
Raised when provided private key has invalid format.
@@ -109,7 +163,7 @@ class GbqConnector(object):
109163
scope = 'https://www.googleapis.com/auth/bigquery'
110164

111165
def __init__(self, project_id, reauth=False, verbose=False,
112-
private_key=None, auth_local_webserver=True,
166+
private_key=None, auth_local_webserver=False,
113167
dialect='legacy'):
114168
self.project_id = project_id
115169
self.reauth = reauth
@@ -120,7 +174,7 @@ def __init__(self, project_id, reauth=False, verbose=False,
120174
auth_local_webserver=auth_local_webserver)
121175
self.service = self.get_service()
122176

123-
def get_credentials(self, auth_local_webserver=True):
177+
def get_credentials(self, auth_local_webserver=False):
124178
if self.private_key:
125179
return self.get_service_account_credentials()
126180
else:
@@ -131,27 +185,6 @@ def get_credentials(self, auth_local_webserver=True):
131185
auth_local_webserver=auth_local_webserver)
132186
return credentials
133187

134-
def try_credentials(self, credentials):
135-
try:
136-
from googleapiclient.discovery import build
137-
except:
138-
from apiclient.discovery import build
139-
140-
if credentials is None:
141-
return None
142-
143-
http = httplib2.Http()
144-
try:
145-
http = google_auth_httplib2.AuthorizedHttp(credentials, http=http)
146-
bigquery_service = build('bigquery', 'v2', http=http)
147-
# Check if the application has rights to the BigQuery project
148-
jobs = bigquery_service.jobs()
149-
job_data = {'configuration': {'query': {'query': 'SELECT 1'}}}
150-
jobs.insert(projectId=self.project_id, body=job_data).execute()
151-
return credentials
152-
except:
153-
return None
154-
155188
def get_application_default_credentials(self):
156189
"""
157190
This method tries to retrieve the "default application credentials".
@@ -172,8 +205,15 @@ def get_application_default_credentials(self):
172205
from the environment. Or, the retrieved credentials do not
173206
have access to the project (self.project_id) on BigQuery.
174207
"""
175-
credentials, _ = google.auth.default(scopes=[self.scope])
176-
return self.try_credentials(credentials)
208+
import google.auth
209+
from google.auth.exceptions import DefaultCredentialsError
210+
211+
try:
212+
credentials, _ = google.auth.default(scopes=[self.scope])
213+
except DefaultCredentialsError:
214+
return None
215+
216+
return _try_credentials(self.project_id, credentials)
177217

178218
def load_user_account_credentials(self):
179219
"""
@@ -193,55 +233,68 @@ def load_user_account_credentials(self):
193233
credentials do not have access to the project (self.project_id)
194234
on BigQuery.
195235
"""
196-
with open('bigquery_credentials.dat') as credentials_file:
197-
credentials_json = json.load(credentials_file)
198-
credentials = google.oauth2.credentials.Credentials(
199-
token=credentials_json.get('access_token'),
200-
refresh_token=credentials_json.get('refresh_token'),
201-
id_token=credentials_json.get('id_token'),
202-
token_uri=credentials_json.get('token_uri'),
203-
client_id=credentials_json.get('client_id'),
204-
client_secret=credentials_json.get('client_secret'),
205-
scopes=credentials_json.get('scopes'))
236+
import httplib2
237+
from google_auth_httplib2 import Request
238+
from google.oauth2.credentials import Credentials
206239

207-
# Refresh the token before trying to use it.
208-
http = httplib2.Http()
209-
request = google_auth_httplib2.Request(http)
210-
credentials.refresh(request)
240+
try:
241+
with open('bigquery_credentials.dat') as credentials_file:
242+
credentials_json = json.load(credentials_file)
243+
except (IOError, ValueError):
244+
return None
245+
246+
credentials = Credentials(
247+
token=credentials_json.get('access_token'),
248+
refresh_token=credentials_json.get('refresh_token'),
249+
id_token=credentials_json.get('id_token'),
250+
token_uri=credentials_json.get('token_uri'),
251+
client_id=credentials_json.get('client_id'),
252+
client_secret=credentials_json.get('client_secret'),
253+
scopes=credentials_json.get('scopes'))
211254

212-
return self.try_credentials(credentials)
255+
# Refresh the token before trying to use it.
256+
http = httplib2.Http()
257+
request = Request(http)
258+
credentials.refresh(request)
259+
260+
return _try_credentials(self.project_id, credentials)
213261

214262
def save_user_account_credentials(self, credentials):
215263
"""
216264
Saves user account credentials to a local file.
217265
"""
218-
with open('bigquery_credentials.dat', 'wb') as credentials_file:
219-
credentials_json = {
220-
'refresh_token': credentials.refresh_token,
221-
'id_token': credentials.id_token,
222-
'token_uri': credentials.token_uri,
223-
'client_id': credentials.client_id,
224-
'client_secret': credentials.client_secret,
225-
'scopes': credentials.scopes,
226-
}
227-
json.dump(credentials_file, credentials_json)
228-
229-
def get_user_account_credentials(self, auth_local_webserver=True):
266+
try:
267+
with open('bigquery_credentials.dat', 'w') as credentials_file:
268+
credentials_json = {
269+
'refresh_token': credentials.refresh_token,
270+
'id_token': credentials.id_token,
271+
'token_uri': credentials.token_uri,
272+
'client_id': credentials.client_id,
273+
'client_secret': credentials.client_secret,
274+
'scopes': credentials.scopes,
275+
}
276+
json.dump(credentials_json, credentials_file)
277+
except IOError:
278+
self._print('Unable to save credentials.')
279+
280+
def get_user_account_credentials(self, auth_local_webserver=False):
230281
"""Gets user account credentials.
231282
232283
This method authenticates using user credentials, either loading saved
233284
credentials from a file or by going through the OAuth flow.
234285
235286
Parameters
236287
----------
237-
auth_local_webserver : boolean (default True)
288+
auth_local_webserver : boolean (default False)
238289
Use a local webserver to complete the OAuth flow.
239290
240291
Returns
241292
-------
242293
GoogleCredentials : credentials
243294
Credentials for the user with BigQuery access.
244295
"""
296+
from google_auth_oauthlib.flow import InstalledAppFlow
297+
245298
credentials = self.load_user_account_credentials()
246299

247300
client_config = {
@@ -256,7 +309,7 @@ def get_user_account_credentials(self, auth_local_webserver=True):
256309
}
257310

258311
if credentials is None or self.reauth:
259-
app_flow = flow.InstalledAppFlow.from_client_config(
312+
app_flow = InstalledAppFlow.from_client_config(
260313
client_config, scopes=[self.scope])
261314

262315
if auth_local_webserver:
@@ -269,6 +322,9 @@ def get_user_account_credentials(self, auth_local_webserver=True):
269322
return credentials
270323

271324
def get_service_account_credentials(self):
325+
import httplib2
326+
from google_auth_httplib2 import Request
327+
from google.oauth2.service_account import Credentials
272328
from os.path import isfile
273329

274330
try:
@@ -286,13 +342,12 @@ def get_service_account_credentials(self):
286342
json_key['private_key'] = bytes(
287343
json_key['private_key'], 'UTF-8')
288344

289-
credentials = service_account.Credentials.from_service_account_info(
290-
json_key)
345+
credentials = Credentials.from_service_account_info(json_key)
291346
credentials = credentials.with_scopes([self.scope])
292347

293348
# Refresh the token before trying to use it.
294349
http = httplib2.Http()
295-
request = google_auth_httplib2.Request(http)
350+
request = Request(http)
296351
credentials.refresh(request)
297352

298353
return credentials
@@ -333,13 +388,11 @@ def sizeof_fmt(num, suffix='B'):
333388

334389
def get_service(self):
335390
import httplib2
336-
try:
337-
from googleapiclient.discovery import build
338-
except:
339-
from apiclient.discovery import build
391+
from google_auth_httplib2 import AuthorizedHttp
392+
from googleapiclient.discovery import build
340393

341394
http = httplib2.Http()
342-
http = google_auth_httplib2.AuthorizedHttp(
395+
http = AuthorizedHttp(
343396
self.credentials, http=http)
344397
bigquery_service = build('bigquery', 'v2', http=http)
345398

@@ -650,7 +703,7 @@ def _parse_entry(field_value, field_type):
650703

651704
def read_gbq(query, project_id=None, index_col=None, col_order=None,
652705
reauth=False, verbose=True, private_key=None,
653-
auth_local_webserver=True, dialect='legacy', **kwargs):
706+
auth_local_webserver=False, dialect='legacy', **kwargs):
654707
r"""Load data from Google BigQuery.
655708
656709
The main method a user calls to execute a Query in Google BigQuery
@@ -694,7 +747,7 @@ def read_gbq(query, project_id=None, index_col=None, col_order=None,
694747
Service account private key in JSON format. Can be file path
695748
or string contents. This is useful for remote server
696749
authentication (eg. jupyter iPython notebook on remote host)
697-
auth_local_webserver : boolean (default True)
750+
auth_local_webserver : boolean (default False)
698751
Use a local webserver when getting user credentials to handle
699752
OAuth authorization flow redirects.
700753
@@ -779,7 +832,8 @@ def read_gbq(query, project_id=None, index_col=None, col_order=None,
779832

780833

781834
def to_gbq(dataframe, destination_table, project_id, chunksize=10000,
782-
verbose=True, reauth=False, if_exists='fail', private_key=None):
835+
verbose=True, reauth=False, if_exists='fail', private_key=None,
836+
auth_local_webserver=False):
783837
"""Write a DataFrame to a Google BigQuery table.
784838
785839
The main method a user calls to export pandas DataFrame contents to
@@ -826,6 +880,9 @@ def to_gbq(dataframe, destination_table, project_id, chunksize=10000,
826880
Service account private key in JSON format. Can be file path
827881
or string contents. This is useful for remote server
828882
authentication (eg. jupyter iPython notebook on remote host)
883+
auth_local_webserver : boolean (default False)
884+
Use a local webserver when getting user credentials to handle
885+
OAuth authorization flow redirects.
829886
"""
830887

831888
if if_exists not in ('fail', 'replace', 'append'):
@@ -835,8 +892,9 @@ def to_gbq(dataframe, destination_table, project_id, chunksize=10000,
835892
raise NotFoundException(
836893
"Invalid Table Name. Should be of the form 'datasetId.tableId' ")
837894

838-
connector = GbqConnector(project_id, reauth=reauth, verbose=verbose,
839-
private_key=private_key)
895+
connector = GbqConnector(
896+
project_id, reauth=reauth, verbose=verbose, private_key=private_key,
897+
auth_local_webserver=auth_local_webserver)
840898
dataset_id, table_id = destination_table.rsplit('.', 1)
841899

842900
table = _Table(project_id, dataset_id, reauth=reauth,

0 commit comments

Comments
 (0)