Skip to content

Commit e1d4c9f

Browse files
authored
ENH: Add load_service_account_credentials function (#40)
* ENH: Add load_service_account_credentials function * explicitly accept scopes=
1 parent c7f7459 commit e1d4c9f

File tree

6 files changed

+114
-0
lines changed

6 files changed

+114
-0
lines changed

Diff for: docs/source/api.rst

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ API Reference
1111
get_user_credentials
1212
load_user_credentials
1313
save_user_credentials
14+
load_service_account_credentials
1415
cache.CredentialsCache
1516
cache.READ_WRITE
1617
cache.REAUTH

Diff for: docs/source/changelog.rst

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

4+
Unreleased / TBD
5+
----------------
6+
7+
- Adds :func:`pydata_google_auth.load_service_account_credentials` function to
8+
get service account credentials from the specified JSON path. (:issue:`39`)
9+
410
.. _changelog-1.1.0:
511

612
1.1.0 / (2020-04-23)

Diff for: pydata_google_auth/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from .auth import get_user_credentials
33
from .auth import load_user_credentials
44
from .auth import save_user_credentials
5+
from .auth import load_service_account_credentials
56
from ._version import get_versions
67

78
versions = get_versions()
@@ -20,4 +21,5 @@
2021
"get_user_credentials",
2122
"load_user_credentials",
2223
"save_user_credentials",
24+
"load_service_account_credentials",
2325
]

Diff for: pydata_google_auth/auth.py

+48
Original file line numberDiff line numberDiff line change
@@ -402,3 +402,51 @@ def load_user_credentials(path):
402402
if not credentials:
403403
raise exceptions.PyDataCredentialsError("Could not load credentials.")
404404
return credentials
405+
406+
407+
def load_service_account_credentials(path, scopes=None):
408+
"""
409+
Gets service account credentials from JSON file at ``path``.
410+
411+
Parameters
412+
----------
413+
path : str
414+
Path to credentials JSON file.
415+
scopes : list[str], optional
416+
A list of scopes to use when authenticating to Google APIs. See the
417+
`list of OAuth 2.0 scopes for Google APIs
418+
<https://developers.google.com/identity/protocols/googlescopes>`_.
419+
420+
Returns
421+
-------
422+
423+
google.oauth2.service_account.Credentials
424+
425+
Raises
426+
------
427+
pydata_google_auth.exceptions.PyDataCredentialsError
428+
If unable to load service credentials.
429+
430+
Examples
431+
--------
432+
433+
Load credentials and use them to construct a BigQuery client.
434+
435+
.. code-block:: python
436+
437+
import pydata_google_auth
438+
import google.cloud.bigquery
439+
440+
credentials = pydata_google_auth.load_service_account_credentials(
441+
"/home/username/keys/google-service-account-credentials.json",
442+
)
443+
client = google.cloud.bigquery.BigQueryClient(
444+
credentials=credentials,
445+
project=credentials.project_id
446+
)
447+
"""
448+
449+
credentials = cache._load_service_account_credentials_from_file(path, scopes=scopes)
450+
if not credentials:
451+
raise exceptions.PyDataCredentialsError("Could not load credentials.")
452+
return credentials

Diff for: pydata_google_auth/cache.py

+29
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import os.path
88

99
import google.oauth2.credentials
10+
from google.oauth2 import service_account
1011

1112

1213
logger = logging.getLogger(__name__)
@@ -123,6 +124,34 @@ def _save_user_account_credentials(credentials, credentials_path):
123124
logger.warning("Unable to save credentials.")
124125

125126

127+
def _load_service_account_credentials_from_file(credentials_path, **kwargs):
128+
try:
129+
with open(credentials_path) as credentials_file:
130+
credentials_json = json.load(credentials_file)
131+
except (IOError, ValueError) as exc:
132+
logger.debug(
133+
"Error loading credentials from {}: {}".format(credentials_path, str(exc))
134+
)
135+
return None
136+
137+
return _load_service_account_credentials_from_info(credentials_json, **kwargs)
138+
139+
140+
def _load_service_account_credentials_from_info(credentials_json, **kwargs):
141+
credentials = service_account.Credentials.from_service_account_info(
142+
credentials_json, **kwargs
143+
)
144+
if not credentials.valid:
145+
request = google.auth.transport.requests.Request()
146+
try:
147+
credentials.refresh(request)
148+
except google.auth.exceptions.RefreshError:
149+
# Credentials could be expired or revoked.
150+
return None
151+
152+
return credentials
153+
154+
126155
class CredentialsCache(object):
127156
"""
128157
Shared base class for crentials classes.

Diff for: tests/unit/test_auth.py

+28
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import google.oauth2.credentials
1111
import pytest
1212

13+
from google.oauth2 import service_account
1314
from pydata_google_auth import exceptions
1415

1516

@@ -56,6 +57,33 @@ def mock_default_credentials(scopes=None, request=None):
5657
assert credentials is mock_user_credentials
5758

5859

60+
class FakeCredentials(object):
61+
@property
62+
def valid(self):
63+
return True
64+
65+
66+
def test_load_service_account_credentials(monkeypatch, tmp_path, module_under_test):
67+
creds_path = str(tmp_path / "creds.json")
68+
with open(creds_path, "w") as stream:
69+
stream.write("{}")
70+
71+
fake_creds = FakeCredentials()
72+
mock_service = mock.create_autospec(service_account.Credentials)
73+
mock_service.from_service_account_info.return_value = fake_creds
74+
monkeypatch.setattr(service_account, "Credentials", mock_service)
75+
76+
creds = module_under_test.load_service_account_credentials(creds_path)
77+
assert creds is fake_creds
78+
79+
5980
def test_load_user_credentials_raises_when_file_doesnt_exist(module_under_test):
6081
with pytest.raises(exceptions.PyDataCredentialsError):
6182
module_under_test.load_user_credentials("path/not/found.json")
83+
84+
85+
def test_load_service_account_credentials_raises_when_file_doesnt_exist(
86+
module_under_test,
87+
):
88+
with pytest.raises(exceptions.PyDataCredentialsError):
89+
module_under_test.load_service_account_credentials("path/not/found.json")

0 commit comments

Comments
 (0)