Skip to content

Commit 2897b81

Browse files
authored
CLN: remove deprecated private_key auth logic (#302)
* CLN: remove deprecated private_key auth logic * remove unnecessary deprecation warning for private_key * update docstrings for deprecated private_key arg
1 parent 9fb2464 commit 2897b81

File tree

8 files changed

+114
-308
lines changed

8 files changed

+114
-308
lines changed

docs/source/changelog.rst

+9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
Changelog
22
=========
33

4+
.. _changelog-0.13.0:
5+
6+
0.13.0 / 2019-12-12
7+
-------------------
8+
9+
- Raise ``NotImplementedError`` when the deprecated ``private_key`` argument
10+
is used. (:issue:`301`)
11+
12+
413
.. _changelog-0.12.0:
514

615
0.12.0 / 2019-11-25

noxfile.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def unit(session):
5151
@nox.session
5252
def cover(session, python=latest_python):
5353
session.install("coverage", "pytest-cov")
54-
session.run("coverage", "report", "--show-missing", "--fail-under=74")
54+
session.run("coverage", "report", "--show-missing", "--fail-under=73")
5555
session.run("coverage", "erase")
5656

5757

pandas_gbq/auth.py

+7-48
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
"""Private module for fetching Google BigQuery credentials."""
22

3-
import json
43
import logging
5-
import os
6-
import os.path
7-
8-
import pandas_gbq.exceptions
94

105
logger = logging.getLogger(__name__)
116

@@ -36,7 +31,13 @@ def get_credentials(
3631
import pydata_google_auth
3732

3833
if private_key:
39-
return get_service_account_credentials(private_key)
34+
raise NotImplementedError(
35+
"""The private_key argument is deprecated. Construct a credentials
36+
object, instead, by using the
37+
google.oauth2.service_account.Credentials.from_service_account_file or
38+
google.oauth2.service_account.Credentials.from_service_account_info class
39+
method from the google-auth package."""
40+
)
4041

4142
credentials, default_project_id = pydata_google_auth.default(
4243
SCOPES,
@@ -50,48 +51,6 @@ def get_credentials(
5051
return credentials, project_id
5152

5253

53-
def get_service_account_credentials(private_key):
54-
"""DEPRECATED: Load service account credentials from key data or key path."""
55-
56-
import google.auth.transport.requests
57-
from google.oauth2.service_account import Credentials
58-
59-
is_path = os.path.isfile(private_key)
60-
61-
try:
62-
if is_path:
63-
with open(private_key) as f:
64-
json_key = json.loads(f.read())
65-
else:
66-
# ugly hack: 'private_key' field has new lines inside,
67-
# they break json parser, but we need to preserve them
68-
json_key = json.loads(private_key.replace("\n", " "))
69-
json_key["private_key"] = json_key["private_key"].replace(
70-
" ", "\n"
71-
)
72-
73-
json_key["private_key"] = bytes(json_key["private_key"], "UTF-8")
74-
credentials = Credentials.from_service_account_info(json_key)
75-
credentials = credentials.with_scopes(SCOPES)
76-
77-
# Refresh the token before trying to use it.
78-
request = google.auth.transport.requests.Request()
79-
credentials.refresh(request)
80-
81-
return credentials, json_key.get("project_id")
82-
except (KeyError, ValueError, TypeError, AttributeError):
83-
raise pandas_gbq.exceptions.InvalidPrivateKeyFormat(
84-
"Detected private_key as {}. ".format(
85-
"path" if is_path else "contents"
86-
)
87-
+ "Private key is missing or invalid. It should be service "
88-
"account private key JSON (file path or string contents) "
89-
'with at least two keys: "client_email" and "private_key". '
90-
"Can be obtained from: https://console.developers.google."
91-
"com/permissions/serviceaccounts"
92-
)
93-
94-
9554
def get_credentials_cache(reauth,):
9655
import pydata_google_auth.cache
9756

pandas_gbq/gbq.py

+14-44
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,6 @@
2020
BIGQUERY_INSTALLED_VERSION = None
2121
BIGQUERY_CLIENT_INFO_VERSION = "1.12.0"
2222
HAS_CLIENT_INFO = False
23-
SHOW_VERBOSE_DEPRECATION = False
24-
SHOW_PRIVATE_KEY_DEPRECATION = False
25-
PRIVATE_KEY_DEPRECATION_MESSAGE = (
26-
"private_key is deprecated and will be removed in a future version."
27-
"Use the credentials argument instead. See "
28-
"https://pandas-gbq.readthedocs.io/en/latest/howto/authentication.html "
29-
"for examples on using the credentials argument with service account keys."
30-
)
3123

3224
try:
3325
import tqdm # noqa
@@ -36,7 +28,7 @@
3628

3729

3830
def _check_google_client_version():
39-
global BIGQUERY_INSTALLED_VERSION, HAS_CLIENT_INFO, SHOW_VERBOSE_DEPRECATION, SHOW_PRIVATE_KEY_DEPRECATION
31+
global BIGQUERY_INSTALLED_VERSION, HAS_CLIENT_INFO, SHOW_VERBOSE_DEPRECATION
4032

4133
try:
4234
import pkg_resources
@@ -74,10 +66,6 @@ def _check_google_client_version():
7466
SHOW_VERBOSE_DEPRECATION = (
7567
pandas_installed_version >= pandas_version_wo_verbosity
7668
)
77-
pandas_version_with_credentials_arg = pkg_resources.parse_version("0.24.0")
78-
SHOW_PRIVATE_KEY_DEPRECATION = (
79-
pandas_installed_version >= pandas_version_with_credentials_arg
80-
)
8169

8270

8371
def _test_google_api_imports():
@@ -951,27 +939,12 @@ def read_gbq(
951939
results.
952940
953941
.. versionadded:: 0.12.0
954-
verbose : None, deprecated
955-
Deprecated in Pandas-GBQ 0.4.0. Use the `logging module
956-
to adjust verbosity instead
957-
<https://pandas-gbq.readthedocs.io/en/latest/intro.html#logging>`__.
958-
private_key : str, deprecated
959-
Deprecated in pandas-gbq version 0.8.0. Use the ``credentials``
960-
parameter and
961-
:func:`google.oauth2.service_account.Credentials.from_service_account_info`
962-
or
963-
:func:`google.oauth2.service_account.Credentials.from_service_account_file`
964-
instead.
965-
966-
Service account private key in JSON format. Can be file path
967-
or string contents. This is useful for remote server
968-
authentication (eg. Jupyter/IPython notebook on remote host).
969-
970942
progress_bar_type (Optional[str]):
971-
If set, use the `tqdm <https://tqdm.github.io/>`_ library to
943+
If set, use the `tqdm <https://tqdm.github.io/>`__ library to
972944
display a progress bar while the data downloads. Install the
973945
``tqdm`` package to use this feature.
974946
Possible values of ``progress_bar_type`` include:
947+
975948
``None``
976949
No progress bar.
977950
``'tqdm'``
@@ -983,6 +956,17 @@ def read_gbq(
983956
``'tqdm_gui'``
984957
Use the :func:`tqdm.tqdm_gui` function to display a
985958
progress bar as a graphical dialog box.
959+
verbose : None, deprecated
960+
Deprecated in Pandas-GBQ 0.4.0. Use the `logging module
961+
to adjust verbosity instead
962+
<https://pandas-gbq.readthedocs.io/en/latest/intro.html#logging>`__.
963+
private_key : str, deprecated
964+
Deprecated in pandas-gbq version 0.8.0. Use the ``credentials``
965+
parameter and
966+
:func:`google.oauth2.service_account.Credentials.from_service_account_info`
967+
or
968+
:func:`google.oauth2.service_account.Credentials.from_service_account_file`
969+
instead.
986970
987971
Returns
988972
-------
@@ -1008,11 +992,6 @@ def read_gbq(
1008992
stacklevel=2,
1009993
)
1010994

1011-
if private_key is not None and SHOW_PRIVATE_KEY_DEPRECATION:
1012-
warnings.warn(
1013-
PRIVATE_KEY_DEPRECATION_MESSAGE, FutureWarning, stacklevel=2
1014-
)
1015-
1016995
if dialect not in ("legacy", "standard"):
1017996
raise ValueError("'{0}' is not valid for dialect".format(dialect))
1018997

@@ -1172,10 +1151,6 @@ def to_gbq(
11721151
or
11731152
:func:`google.oauth2.service_account.Credentials.from_service_account_file`
11741153
instead.
1175-
1176-
Service account private key in JSON format. Can be file path
1177-
or string contents. This is useful for remote server
1178-
authentication (eg. Jupyter/IPython notebook on remote host).
11791154
"""
11801155

11811156
_test_google_api_imports()
@@ -1190,11 +1165,6 @@ def to_gbq(
11901165
stacklevel=1,
11911166
)
11921167

1193-
if private_key is not None and SHOW_PRIVATE_KEY_DEPRECATION:
1194-
warnings.warn(
1195-
PRIVATE_KEY_DEPRECATION_MESSAGE, FutureWarning, stacklevel=2
1196-
)
1197-
11981168
if if_exists not in ("fail", "replace", "append"):
11991169
raise ValueError("'{0}' is not valid for if_exists".format(if_exists))
12001170

pandas_gbq/load.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Helper methods for loading data into BigQuery"""
22

3-
import six
3+
import io
4+
45
from google.cloud import bigquery
56

67
import pandas_gbq.schema
@@ -12,7 +13,7 @@ def encode_chunk(dataframe):
1213
Args:
1314
dataframe (pandas.DataFrame): A chunk of a dataframe to encode
1415
"""
15-
csv_buffer = six.StringIO()
16+
csv_buffer = io.StringIO()
1617
dataframe.to_csv(
1718
csv_buffer,
1819
index=False,
@@ -25,10 +26,8 @@ def encode_chunk(dataframe):
2526
# Convert to a BytesIO buffer so that unicode text is properly handled.
2627
# See: https://github.com/pydata/pandas-gbq/issues/106
2728
body = csv_buffer.getvalue()
28-
if isinstance(body, bytes):
29-
body = body.decode("utf-8")
3029
body = body.encode("utf-8")
31-
return six.BytesIO(body)
30+
return io.BytesIO(body)
3231

3332

3433
def encode_chunks(dataframe, chunksize=None):

tests/system/test_auth.py

+3-25
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""System tests for fetching Google BigQuery credentials."""
22

3+
import os
34
from unittest import mock
45

56
import pytest
@@ -57,34 +58,11 @@ def _check_if_can_get_correct_default_credentials():
5758

5859

5960
def test_should_be_able_to_get_valid_credentials(project_id, private_key_path):
60-
credentials, _ = auth.get_credentials(
61-
project_id=project_id, private_key=private_key_path
62-
)
61+
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = private_key_path
62+
credentials, _ = auth.get_credentials(project_id=project_id)
6363
assert credentials.valid
6464

6565

66-
def test_get_service_account_credentials_private_key_path(private_key_path):
67-
from google.auth.credentials import Credentials
68-
69-
credentials, project_id = auth.get_service_account_credentials(
70-
private_key_path
71-
)
72-
assert isinstance(credentials, Credentials)
73-
assert _try_credentials(project_id, credentials) is not None
74-
75-
76-
def test_get_service_account_credentials_private_key_contents(
77-
private_key_contents,
78-
):
79-
from google.auth.credentials import Credentials
80-
81-
credentials, project_id = auth.get_service_account_credentials(
82-
private_key_contents
83-
)
84-
assert isinstance(credentials, Credentials)
85-
assert _try_credentials(project_id, credentials) is not None
86-
87-
8866
@pytest.mark.local_auth
8967
def test_get_credentials_bad_file_returns_user_credentials(
9068
project_id, monkeypatch

tests/unit/test_auth.py

+14-45
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,23 @@
11
# -*- coding: utf-8 -*-
22

33
import json
4-
import os.path
5-
6-
from pandas_gbq import auth
7-
84
from unittest import mock
95

6+
import pytest
107

11-
def test_get_credentials_private_key_contents(monkeypatch):
12-
from google.oauth2 import service_account
8+
from pandas_gbq import auth
139

14-
@classmethod
15-
def from_service_account_info(cls, key_info):
16-
mock_credentials = mock.create_autospec(cls)
17-
mock_credentials.with_scopes.return_value = mock_credentials
18-
mock_credentials.refresh.return_value = mock_credentials
19-
return mock_credentials
2010

21-
monkeypatch.setattr(
22-
service_account.Credentials,
23-
"from_service_account_info",
24-
from_service_account_info,
25-
)
11+
def test_get_credentials_private_key_raises_notimplementederror(monkeypatch):
2612
private_key = json.dumps(
2713
{
2814
"private_key": "some_key",
2915
"client_email": "[email protected]",
3016
"project_id": "private-key-project",
3117
}
3218
)
33-
credentials, project = auth.get_credentials(private_key=private_key)
34-
35-
assert credentials is not None
36-
assert project == "private-key-project"
37-
38-
39-
def test_get_credentials_private_key_path(monkeypatch):
40-
from google.oauth2 import service_account
41-
42-
@classmethod
43-
def from_service_account_info(cls, key_info):
44-
mock_credentials = mock.create_autospec(cls)
45-
mock_credentials.with_scopes.return_value = mock_credentials
46-
mock_credentials.refresh.return_value = mock_credentials
47-
return mock_credentials
48-
49-
monkeypatch.setattr(
50-
service_account.Credentials,
51-
"from_service_account_info",
52-
from_service_account_info,
53-
)
54-
private_key = os.path.join(
55-
os.path.dirname(__file__), "..", "data", "dummy_key.json"
56-
)
57-
credentials, project = auth.get_credentials(private_key=private_key)
58-
59-
assert credentials is not None
60-
assert project is None
19+
with pytest.raises(NotImplementedError, match="private_key"):
20+
auth.get_credentials(private_key=private_key)
6121

6222

6323
def test_get_credentials_default_credentials(monkeypatch):
@@ -101,3 +61,12 @@ def mock_default_credentials(scopes=None, request=None):
10161
credentials, project = auth.get_credentials()
10262
assert project is None
10363
assert credentials is mock_user_credentials
64+
65+
66+
def test_get_credentials_cache_w_reauth():
67+
import pydata_google_auth.cache
68+
69+
cache = auth.get_credentials_cache(True)
70+
assert isinstance(
71+
cache, pydata_google_auth.cache.WriteOnlyCredentialsCache
72+
)

0 commit comments

Comments
 (0)