Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: Add credentials argument to read_gbq and to_gbq. #231

Merged
merged 2 commits into from
Oct 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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