Skip to content

Commit

Permalink
ENH: Add credentials argument to read_gbq and to_gbq. (#231)
Browse files Browse the repository at this point in the history
* ENH: Add credentials argument to read_gbq and to_gbq.

Deprecates private_key parameter.

* DOC: write files service account example first
  • Loading branch information
tswast authored Oct 26, 2018
1 parent 7c3dbaf commit b8c9051
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 118 deletions.
19 changes: 17 additions & 2 deletions docs/source/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
Changelog
=========

.. _changelog-0.7.1:
.. _changelog-0.8.0:

0.7.1 / unreleased
0.8.0 / unreleased
--------------------

Breaking changes
~~~~~~~~~~~~~~~~

- **Deprecate** ``private_key`` parameter to :func:`pandas_gbq.read_gbq` and
:func:`pandas_gbq.to_gbq` in favor of new ``credentials`` argument. Instead,
create a credentials object using
:func:`google.oauth2.service_account.Credentials.from_service_account_info`
or
:func:`google.oauth2.service_account.Credentials.from_service_account_file`.
See the :doc:`authentication how-to guide <howto/authentication>` for
examples. (:issue:`161`, :issue:`TODO`)

Enhancements
~~~~~~~~~~~~

- Allow newlines in data passed to ``to_gbq``. (:issue:`180`)

.. _changelog-0.7.0:
Expand Down
37 changes: 32 additions & 5 deletions docs/source/howto/authentication.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,38 @@ Create a service account key via the `service account key creation page
the Google Cloud Platform Console. Select the JSON key type and download the
key file.

To use service account credentials, set the ``private_key`` parameter to one
of:

* A file path to the JSON file.
* A string containing the JSON file contents.
To use service account credentials, set the ``credentials`` parameter to the result of a call to:

* :func:`google.oauth2.service_account.Credentials.from_service_account_file`,
which accepts a file path to the JSON file.

.. code:: python
credentials = google.oauth2.service_account.Credentials.from_service_account_file(
'path/to/key.json',
)
df = pandas_gbq.read_gbq(sql, project_id="YOUR-PROJECT-ID", credentials=credentials)
* :func:`google.oauth2.service_account.Credentials.from_service_account_info`,
which accepts a dictionary corresponding to the JSON file contents.

.. code:: python
credentials = google.oauth2.service_account.Credentials.from_service_account_info(
{
"type": "service_account",
"project_id": "YOUR-PROJECT-ID",
"private_key_id": "6747200734a1f2b9d8d62fc0b9414c5f2461db0e",
"private_key": "-----BEGIN PRIVATE KEY-----\nM...I==\n-----END PRIVATE KEY-----\n",
"client_email": "service-account@YOUR-PROJECT-ID.iam.gserviceaccount.com",
"client_id": "12345678900001",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/...iam.gserviceaccount.com"
},
)
df = pandas_gbq.read_gbq(sql, project_id="YOUR-PROJECT-ID", credentials=credentials)
See the `Getting started with authentication on Google Cloud Platform
<https://cloud.google.com/docs/authentication/getting-started>`_ guide for
Expand Down
95 changes: 72 additions & 23 deletions pandas_gbq/gbq.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ def __init__(
dialect="legacy",
location=None,
try_credentials=None,
credentials=None,
):
global context
from google.api_core.exceptions import GoogleAPIError
Expand All @@ -249,11 +250,14 @@ def __init__(
self.private_key = private_key
self.auth_local_webserver = auth_local_webserver
self.dialect = dialect
self.credentials = credentials
self.credentials_path = _get_credentials_file()
default_project = None

# Load credentials from cache.
self.credentials = context.credentials
default_project = context.project
if not self.credentials:
self.credentials = context.credentials
default_project = context.project

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

def delete_and_recreate_table(self, dataset_id, table_id, table_schema):
table = _Table(
self.project_id, dataset_id, private_key=self.private_key
self.project_id, dataset_id, credentials=self.credentials
)
table.delete(table_id)
table.create(table_id, table_schema)
Expand Down Expand Up @@ -621,12 +625,13 @@ def read_gbq(
index_col=None,
col_order=None,
reauth=False,
private_key=None,
auth_local_webserver=False,
dialect=None,
location=None,
configuration=None,
credentials=None,
verbose=None,
private_key=None,
):
r"""Load data from Google BigQuery using google-cloud-python
Expand Down Expand Up @@ -655,10 +660,6 @@ def read_gbq(
reauth : boolean, default False
Force Google BigQuery to re-authenticate the user. This is useful
if multiple accounts are used.
private_key : str, optional
Service account private key in JSON format. Can be file path
or string contents. This is useful for remote server
authentication (eg. Jupyter/IPython notebook on remote host).
auth_local_webserver : boolean, default False
Use the `local webserver flow`_ instead of the `console flow`_
when getting user credentials.
Expand Down Expand Up @@ -699,10 +700,28 @@ def read_gbq(
For more information see `BigQuery REST API Reference
<https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs#configuration.query>`__.
credentials : google.auth.credentials.Credentials, optional
Credentials for accessing Google APIs. Use this parameter to override
default credentials, such as to use Compute Engine
:class:`google.auth.compute_engine.Credentials` or Service Account
:class:`google.oauth2.service_account.Credentials` directly.
.. versionadded:: 0.8.0
verbose : None, deprecated
Deprecated in Pandas-GBQ 0.4.0. Use the `logging module
to adjust verbosity instead
<https://pandas-gbq.readthedocs.io/en/latest/intro.html#logging>`__.
private_key : str, deprecated
Deprecated in pandas-gbq version 0.8.0. Use the ``credentials``
parameter and
:func:`google.oauth2.service_account.Credentials.from_service_account_info`
or
:func:`google.oauth2.service_account.Credentials.from_service_account_file`
instead.
Service account private key in JSON format. Can be file path
or string contents. This is useful for remote server
authentication (eg. Jupyter/IPython notebook on remote host).
Returns
-------
Expand Down Expand Up @@ -736,10 +755,11 @@ def read_gbq(
connector = GbqConnector(
project_id,
reauth=reauth,
private_key=private_key,
dialect=dialect,
auth_local_webserver=auth_local_webserver,
location=location,
credentials=credentials,
private_key=private_key,
)
schema, rows = connector.run_query(query, configuration=configuration)
final_df = _parse_data(schema, rows)
Expand Down Expand Up @@ -779,12 +799,13 @@ def to_gbq(
chunksize=None,
reauth=False,
if_exists="fail",
private_key=None,
auth_local_webserver=False,
table_schema=None,
location=None,
progress_bar=True,
credentials=None,
verbose=None,
private_key=None,
):
"""Write a DataFrame to a Google BigQuery table.
Expand Down Expand Up @@ -822,10 +843,6 @@ def to_gbq(
If table exists, drop it, recreate it, and insert data.
``'append'``
If table exists, insert data. Create if does not exist.
private_key : str, optional
Service account private key in JSON format. Can be file path
or string contents. This is useful for remote server
authentication (eg. Jupyter/IPython notebook on remote host).
auth_local_webserver : bool, default False
Use the `local webserver flow`_ instead of the `console flow`_
when getting user credentials.
Expand Down Expand Up @@ -861,10 +878,28 @@ def to_gbq(
chunk by chunk.
.. versionadded:: 0.5.0
credentials : google.auth.credentials.Credentials, optional
Credentials for accessing Google APIs. Use this parameter to override
default credentials, such as to use Compute Engine
:class:`google.auth.compute_engine.Credentials` or Service Account
:class:`google.oauth2.service_account.Credentials` directly.
.. versionadded:: 0.8.0
verbose : bool, deprecated
Deprecated in Pandas-GBQ 0.4.0. Use the `logging module
to adjust verbosity instead
<https://pandas-gbq.readthedocs.io/en/latest/intro.html#logging>`__.
private_key : str, deprecated
Deprecated in pandas-gbq version 0.8.0. Use the ``credentials``
parameter and
:func:`google.oauth2.service_account.Credentials.from_service_account_info`
or
:func:`google.oauth2.service_account.Credentials.from_service_account_file`
instead.
Service account private key in JSON format. Can be file path
or string contents. This is useful for remote server
authentication (eg. Jupyter/IPython notebook on remote host).
"""

_test_google_api_imports()
Expand All @@ -889,21 +924,21 @@ def to_gbq(
connector = GbqConnector(
project_id,
reauth=reauth,
private_key=private_key,
auth_local_webserver=auth_local_webserver,
location=location,
# Avoid reads when writing tables.
# https://github.com/pydata/pandas-gbq/issues/202
try_credentials=lambda project, creds: creds,
credentials=credentials,
private_key=private_key,
)
dataset_id, table_id = destination_table.rsplit(".", 1)

table = _Table(
project_id,
dataset_id,
reauth=reauth,
private_key=private_key,
location=location,
credentials=connector.credentials,
)

if not table_schema:
Expand Down Expand Up @@ -980,12 +1015,17 @@ def __init__(
project_id,
dataset_id,
reauth=False,
private_key=None,
location=None,
credentials=None,
private_key=None,
):
self.dataset_id = dataset_id
super(_Table, self).__init__(
project_id, reauth, private_key, location=location
project_id,
reauth,
location=location,
credentials=credentials,
private_key=private_key,
)

def exists(self, table_id):
Expand Down Expand Up @@ -1031,12 +1071,12 @@ def create(self, table_id, schema):
"Table {0} already " "exists".format(table_id)
)

if not _Dataset(self.project_id, private_key=self.private_key).exists(
if not _Dataset(self.project_id, credentials=self.credentials).exists(
self.dataset_id
):
_Dataset(
self.project_id,
private_key=self.private_key,
credentials=self.credentials,
location=self.location,
).create(self.dataset_id)

Expand Down Expand Up @@ -1084,10 +1124,19 @@ def delete(self, table_id):

class _Dataset(GbqConnector):
def __init__(
self, project_id, reauth=False, private_key=None, location=None
self,
project_id,
reauth=False,
location=None,
credentials=None,
private_key=None,
):
super(_Dataset, self).__init__(
project_id, reauth, private_key, location=location
project_id,
reauth,
credentials=credentials,
location=location,
private_key=private_key,
)

def exists(self, dataset_id):
Expand Down
Loading

0 comments on commit b8c9051

Please sign in to comment.