Skip to content

Commit b8c9051

Browse files
authored
ENH: Add credentials argument to read_gbq and to_gbq. (#231)
* ENH: Add credentials argument to read_gbq and to_gbq. Deprecates private_key parameter. * DOC: write files service account example first
1 parent 7c3dbaf commit b8c9051

File tree

4 files changed

+213
-118
lines changed

4 files changed

+213
-118
lines changed

docs/source/changelog.rst

+17-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,26 @@
11
Changelog
22
=========
33

4-
.. _changelog-0.7.1:
4+
.. _changelog-0.8.0:
55

6-
0.7.1 / unreleased
6+
0.8.0 / unreleased
77
--------------------
88

9+
Breaking changes
10+
~~~~~~~~~~~~~~~~
11+
12+
- **Deprecate** ``private_key`` parameter to :func:`pandas_gbq.read_gbq` and
13+
:func:`pandas_gbq.to_gbq` in favor of new ``credentials`` argument. Instead,
14+
create a credentials object using
15+
:func:`google.oauth2.service_account.Credentials.from_service_account_info`
16+
or
17+
:func:`google.oauth2.service_account.Credentials.from_service_account_file`.
18+
See the :doc:`authentication how-to guide <howto/authentication>` for
19+
examples. (:issue:`161`, :issue:`TODO`)
20+
21+
Enhancements
22+
~~~~~~~~~~~~
23+
924
- Allow newlines in data passed to ``to_gbq``. (:issue:`180`)
1025

1126
.. _changelog-0.7.0:

docs/source/howto/authentication.rst

+32-5
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,38 @@ Create a service account key via the `service account key creation page
1818
the Google Cloud Platform Console. Select the JSON key type and download the
1919
key file.
2020

21-
To use service account credentials, set the ``private_key`` parameter to one
22-
of:
23-
24-
* A file path to the JSON file.
25-
* A string containing the JSON file contents.
21+
To use service account credentials, set the ``credentials`` parameter to the result of a call to:
22+
23+
* :func:`google.oauth2.service_account.Credentials.from_service_account_file`,
24+
which accepts a file path to the JSON file.
25+
26+
.. code:: python
27+
28+
credentials = google.oauth2.service_account.Credentials.from_service_account_file(
29+
'path/to/key.json',
30+
)
31+
df = pandas_gbq.read_gbq(sql, project_id="YOUR-PROJECT-ID", credentials=credentials)
32+
33+
* :func:`google.oauth2.service_account.Credentials.from_service_account_info`,
34+
which accepts a dictionary corresponding to the JSON file contents.
35+
36+
.. code:: python
37+
38+
credentials = google.oauth2.service_account.Credentials.from_service_account_info(
39+
{
40+
"type": "service_account",
41+
"project_id": "YOUR-PROJECT-ID",
42+
"private_key_id": "6747200734a1f2b9d8d62fc0b9414c5f2461db0e",
43+
"private_key": "-----BEGIN PRIVATE KEY-----\nM...I==\n-----END PRIVATE KEY-----\n",
44+
"client_email": "[email protected]",
45+
"client_id": "12345678900001",
46+
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
47+
"token_uri": "https://accounts.google.com/o/oauth2/token",
48+
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
49+
"client_x509_cert_url": "https://www.googleapis.com/...iam.gserviceaccount.com"
50+
},
51+
)
52+
df = pandas_gbq.read_gbq(sql, project_id="YOUR-PROJECT-ID", credentials=credentials)
2653
2754
See the `Getting started with authentication on Google Cloud Platform
2855
<https://cloud.google.com/docs/authentication/getting-started>`_ guide for

pandas_gbq/gbq.py

+72-23
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ def __init__(
236236
dialect="legacy",
237237
location=None,
238238
try_credentials=None,
239+
credentials=None,
239240
):
240241
global context
241242
from google.api_core.exceptions import GoogleAPIError
@@ -249,11 +250,14 @@ def __init__(
249250
self.private_key = private_key
250251
self.auth_local_webserver = auth_local_webserver
251252
self.dialect = dialect
253+
self.credentials = credentials
252254
self.credentials_path = _get_credentials_file()
255+
default_project = None
253256

254257
# Load credentials from cache.
255-
self.credentials = context.credentials
256-
default_project = context.project
258+
if not self.credentials:
259+
self.credentials = context.credentials
260+
default_project = context.project
257261

258262
# Credentials were explicitly asked for, so don't use the cache.
259263
if private_key or reauth or not self.credentials:
@@ -563,7 +567,7 @@ def schema_is_subset(self, dataset_id, table_id, schema):
563567

564568
def delete_and_recreate_table(self, dataset_id, table_id, table_schema):
565569
table = _Table(
566-
self.project_id, dataset_id, private_key=self.private_key
570+
self.project_id, dataset_id, credentials=self.credentials
567571
)
568572
table.delete(table_id)
569573
table.create(table_id, table_schema)
@@ -621,12 +625,13 @@ def read_gbq(
621625
index_col=None,
622626
col_order=None,
623627
reauth=False,
624-
private_key=None,
625628
auth_local_webserver=False,
626629
dialect=None,
627630
location=None,
628631
configuration=None,
632+
credentials=None,
629633
verbose=None,
634+
private_key=None,
630635
):
631636
r"""Load data from Google BigQuery using google-cloud-python
632637
@@ -655,10 +660,6 @@ def read_gbq(
655660
reauth : boolean, default False
656661
Force Google BigQuery to re-authenticate the user. This is useful
657662
if multiple accounts are used.
658-
private_key : str, optional
659-
Service account private key in JSON format. Can be file path
660-
or string contents. This is useful for remote server
661-
authentication (eg. Jupyter/IPython notebook on remote host).
662663
auth_local_webserver : boolean, default False
663664
Use the `local webserver flow`_ instead of the `console flow`_
664665
when getting user credentials.
@@ -699,10 +700,28 @@ def read_gbq(
699700
700701
For more information see `BigQuery REST API Reference
701702
<https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs#configuration.query>`__.
703+
credentials : google.auth.credentials.Credentials, optional
704+
Credentials for accessing Google APIs. Use this parameter to override
705+
default credentials, such as to use Compute Engine
706+
:class:`google.auth.compute_engine.Credentials` or Service Account
707+
:class:`google.oauth2.service_account.Credentials` directly.
708+
709+
.. versionadded:: 0.8.0
702710
verbose : None, deprecated
703711
Deprecated in Pandas-GBQ 0.4.0. Use the `logging module
704712
to adjust verbosity instead
705713
<https://pandas-gbq.readthedocs.io/en/latest/intro.html#logging>`__.
714+
private_key : str, deprecated
715+
Deprecated in pandas-gbq version 0.8.0. Use the ``credentials``
716+
parameter and
717+
:func:`google.oauth2.service_account.Credentials.from_service_account_info`
718+
or
719+
:func:`google.oauth2.service_account.Credentials.from_service_account_file`
720+
instead.
721+
722+
Service account private key in JSON format. Can be file path
723+
or string contents. This is useful for remote server
724+
authentication (eg. Jupyter/IPython notebook on remote host).
706725
707726
Returns
708727
-------
@@ -736,10 +755,11 @@ def read_gbq(
736755
connector = GbqConnector(
737756
project_id,
738757
reauth=reauth,
739-
private_key=private_key,
740758
dialect=dialect,
741759
auth_local_webserver=auth_local_webserver,
742760
location=location,
761+
credentials=credentials,
762+
private_key=private_key,
743763
)
744764
schema, rows = connector.run_query(query, configuration=configuration)
745765
final_df = _parse_data(schema, rows)
@@ -779,12 +799,13 @@ def to_gbq(
779799
chunksize=None,
780800
reauth=False,
781801
if_exists="fail",
782-
private_key=None,
783802
auth_local_webserver=False,
784803
table_schema=None,
785804
location=None,
786805
progress_bar=True,
806+
credentials=None,
787807
verbose=None,
808+
private_key=None,
788809
):
789810
"""Write a DataFrame to a Google BigQuery table.
790811
@@ -822,10 +843,6 @@ def to_gbq(
822843
If table exists, drop it, recreate it, and insert data.
823844
``'append'``
824845
If table exists, insert data. Create if does not exist.
825-
private_key : str, optional
826-
Service account private key in JSON format. Can be file path
827-
or string contents. This is useful for remote server
828-
authentication (eg. Jupyter/IPython notebook on remote host).
829846
auth_local_webserver : bool, default False
830847
Use the `local webserver flow`_ instead of the `console flow`_
831848
when getting user credentials.
@@ -861,10 +878,28 @@ def to_gbq(
861878
chunk by chunk.
862879
863880
.. versionadded:: 0.5.0
881+
credentials : google.auth.credentials.Credentials, optional
882+
Credentials for accessing Google APIs. Use this parameter to override
883+
default credentials, such as to use Compute Engine
884+
:class:`google.auth.compute_engine.Credentials` or Service Account
885+
:class:`google.oauth2.service_account.Credentials` directly.
886+
887+
.. versionadded:: 0.8.0
864888
verbose : bool, deprecated
865889
Deprecated in Pandas-GBQ 0.4.0. Use the `logging module
866890
to adjust verbosity instead
867891
<https://pandas-gbq.readthedocs.io/en/latest/intro.html#logging>`__.
892+
private_key : str, deprecated
893+
Deprecated in pandas-gbq version 0.8.0. Use the ``credentials``
894+
parameter and
895+
:func:`google.oauth2.service_account.Credentials.from_service_account_info`
896+
or
897+
:func:`google.oauth2.service_account.Credentials.from_service_account_file`
898+
instead.
899+
900+
Service account private key in JSON format. Can be file path
901+
or string contents. This is useful for remote server
902+
authentication (eg. Jupyter/IPython notebook on remote host).
868903
"""
869904

870905
_test_google_api_imports()
@@ -889,21 +924,21 @@ def to_gbq(
889924
connector = GbqConnector(
890925
project_id,
891926
reauth=reauth,
892-
private_key=private_key,
893927
auth_local_webserver=auth_local_webserver,
894928
location=location,
895929
# Avoid reads when writing tables.
896930
# https://github.com/pydata/pandas-gbq/issues/202
897931
try_credentials=lambda project, creds: creds,
932+
credentials=credentials,
933+
private_key=private_key,
898934
)
899935
dataset_id, table_id = destination_table.rsplit(".", 1)
900936

901937
table = _Table(
902938
project_id,
903939
dataset_id,
904-
reauth=reauth,
905-
private_key=private_key,
906940
location=location,
941+
credentials=connector.credentials,
907942
)
908943

909944
if not table_schema:
@@ -980,12 +1015,17 @@ def __init__(
9801015
project_id,
9811016
dataset_id,
9821017
reauth=False,
983-
private_key=None,
9841018
location=None,
1019+
credentials=None,
1020+
private_key=None,
9851021
):
9861022
self.dataset_id = dataset_id
9871023
super(_Table, self).__init__(
988-
project_id, reauth, private_key, location=location
1024+
project_id,
1025+
reauth,
1026+
location=location,
1027+
credentials=credentials,
1028+
private_key=private_key,
9891029
)
9901030

9911031
def exists(self, table_id):
@@ -1031,12 +1071,12 @@ def create(self, table_id, schema):
10311071
"Table {0} already " "exists".format(table_id)
10321072
)
10331073

1034-
if not _Dataset(self.project_id, private_key=self.private_key).exists(
1074+
if not _Dataset(self.project_id, credentials=self.credentials).exists(
10351075
self.dataset_id
10361076
):
10371077
_Dataset(
10381078
self.project_id,
1039-
private_key=self.private_key,
1079+
credentials=self.credentials,
10401080
location=self.location,
10411081
).create(self.dataset_id)
10421082

@@ -1084,10 +1124,19 @@ def delete(self, table_id):
10841124

10851125
class _Dataset(GbqConnector):
10861126
def __init__(
1087-
self, project_id, reauth=False, private_key=None, location=None
1127+
self,
1128+
project_id,
1129+
reauth=False,
1130+
location=None,
1131+
credentials=None,
1132+
private_key=None,
10881133
):
10891134
super(_Dataset, self).__init__(
1090-
project_id, reauth, private_key, location=location
1135+
project_id,
1136+
reauth,
1137+
credentials=credentials,
1138+
location=location,
1139+
private_key=private_key,
10911140
)
10921141

10931142
def exists(self, dataset_id):

0 commit comments

Comments
 (0)