diff --git a/docs/source/howto/authentication.rst b/docs/source/howto/authentication.rst index e75e8777..a44a61c7 100644 --- a/docs/source/howto/authentication.rst +++ b/docs/source/howto/authentication.rst @@ -25,7 +25,10 @@ To use service account credentials, set the ``credentials`` parameter to the res .. code:: python - credentials = google.oauth2.service_account.Credentials.from_service_account_file( + from google.oauth2 import service_account + import pandas_gbq + + credentials = service_account.Credentials.from_service_account_file( 'path/to/key.json', ) df = pandas_gbq.read_gbq(sql, project_id="YOUR-PROJECT-ID", credentials=credentials) @@ -35,7 +38,10 @@ To use service account credentials, set the ``credentials`` parameter to the res .. code:: python - credentials = google.oauth2.service_account.Credentials.from_service_account_info( + from google.oauth2 import service_account + import pandas_gbq + + credentials = service_account.Credentials.from_service_account_info( { "type": "service_account", "project_id": "YOUR-PROJECT-ID", diff --git a/pandas_gbq/gbq.py b/pandas_gbq/gbq.py index bf02bdad..c71b6cc5 100644 --- a/pandas_gbq/gbq.py +++ b/pandas_gbq/gbq.py @@ -14,6 +14,13 @@ BIGQUERY_INSTALLED_VERSION = None SHOW_VERBOSE_DEPRECATION = False +SHOW_PRIVATE_KEY_DEPRECATION = False +PRIVATE_KEY_DEPRECATION_MESSAGE = ( + "private_key is deprecated and will be removed in a future version." + "Use the credentials argument instead. See " + "https://pandas-gbq.readthedocs.io/en/latest/howto/authentication.html " + "for examples on using the credentials argument with service account keys." +) try: import tqdm # noqa @@ -22,7 +29,7 @@ def _check_google_client_version(): - global BIGQUERY_INSTALLED_VERSION, SHOW_VERBOSE_DEPRECATION + global BIGQUERY_INSTALLED_VERSION, SHOW_VERBOSE_DEPRECATION, SHOW_PRIVATE_KEY_DEPRECATION try: import pkg_resources @@ -53,6 +60,10 @@ def _check_google_client_version(): SHOW_VERBOSE_DEPRECATION = ( pandas_installed_version >= pandas_version_wo_verbosity ) + pandas_version_with_credentials_arg = pkg_resources.parse_version("0.24.0") + SHOW_PRIVATE_KEY_DEPRECATION = ( + pandas_installed_version >= pandas_version_with_credentials_arg + ) def _test_google_api_imports(): @@ -805,6 +816,11 @@ def read_gbq( stacklevel=2, ) + if private_key is not None and SHOW_PRIVATE_KEY_DEPRECATION: + warnings.warn( + PRIVATE_KEY_DEPRECATION_MESSAGE, FutureWarning, stacklevel=2 + ) + if dialect not in ("legacy", "standard"): raise ValueError("'{0}' is not valid for dialect".format(dialect)) @@ -969,6 +985,11 @@ def to_gbq( stacklevel=1, ) + if private_key is not None and SHOW_PRIVATE_KEY_DEPRECATION: + warnings.warn( + PRIVATE_KEY_DEPRECATION_MESSAGE, FutureWarning, stacklevel=2 + ) + if if_exists not in ("fail", "replace", "append"): raise ValueError("'{0}' is not valid for if_exists".format(if_exists)) diff --git a/tests/unit/test_gbq.py b/tests/unit/test_gbq.py index 1f3ec9a4..00436028 100644 --- a/tests/unit/test_gbq.py +++ b/tests/unit/test_gbq.py @@ -187,6 +187,57 @@ def test_to_gbq_with_verbose_old_pandas_no_warnings(recwarn, min_bq_version): assert len(recwarn) == 0 +def test_to_gbq_with_private_key_new_pandas_warns_deprecation( + min_bq_version, monkeypatch +): + import pkg_resources + from pandas_gbq import auth + + monkeypatch.setattr(auth, "get_credentials", mock_get_credentials) + + pandas_version = pkg_resources.parse_version("0.24.0") + with pytest.warns(FutureWarning), mock.patch( + "pkg_resources.Distribution.parsed_version", + new_callable=mock.PropertyMock, + ) as mock_version: + mock_version.side_effect = [min_bq_version, pandas_version] + try: + gbq.to_gbq( + DataFrame([[1]]), + "dataset.tablename", + project_id="my-project", + private_key="path/to/key.json", + ) + except gbq.TableCreationError: + pass + + +def test_to_gbq_with_private_key_old_pandas_no_warnings( + recwarn, min_bq_version, monkeypatch +): + import pkg_resources + from pandas_gbq import auth + + monkeypatch.setattr(auth, "get_credentials", mock_get_credentials) + + pandas_version = pkg_resources.parse_version("0.23.4") + with mock.patch( + "pkg_resources.Distribution.parsed_version", + new_callable=mock.PropertyMock, + ) as mock_version: + mock_version.side_effect = [min_bq_version, pandas_version] + try: + gbq.to_gbq( + DataFrame([[1]]), + "dataset.tablename", + project_id="my-project", + private_key="path/to/key.json", + ) + except gbq.TableCreationError: + pass + assert len(recwarn) == 0 + + def test_to_gbq_doesnt_run_query( recwarn, mock_bigquery_client, min_bq_version ): @@ -334,6 +385,48 @@ def test_read_gbq_with_verbose_old_pandas_no_warnings(recwarn, min_bq_version): assert len(recwarn) == 0 +def test_read_gbq_with_private_key_new_pandas_warns_deprecation( + min_bq_version, monkeypatch +): + import pkg_resources + from pandas_gbq import auth + + monkeypatch.setattr(auth, "get_credentials", mock_get_credentials) + + pandas_version = pkg_resources.parse_version("0.24.0") + with pytest.warns(FutureWarning), mock.patch( + "pkg_resources.Distribution.parsed_version", + new_callable=mock.PropertyMock, + ) as mock_version: + mock_version.side_effect = [min_bq_version, pandas_version] + gbq.read_gbq( + "SELECT 1", project_id="my-project", private_key="path/to/key.json" + ) + + +def test_read_gbq_with_private_key_old_pandas_no_warnings( + recwarn, min_bq_version, monkeypatch +): + import pkg_resources + from pandas_gbq import auth + + monkeypatch.setattr(auth, "get_credentials", mock_get_credentials) + + pandas_version = pkg_resources.parse_version("0.23.4") + with mock.patch( + "pkg_resources.Distribution.parsed_version", + new_callable=mock.PropertyMock, + ) as mock_version: + mock_version.side_effect = [min_bq_version, pandas_version] + gbq.read_gbq( + "SELECT 1", + project_id="my-project", + dialect="standard", + private_key="path/to/key.json", + ) + assert len(recwarn) == 0 + + def test_read_gbq_with_invalid_dialect(): with pytest.raises(ValueError) as excinfo: gbq.read_gbq("SELECT 1", dialect="invalid")