feat: add quota_project_id to service accounts; add with_quota_project methods (#519)
Adds quota_project_id to service account credentials, making it possible to set quota_project_id on OAuth2 credentials and service account credentials.
This PR also adds the method with_quota_project to both classes.
diff --git a/google/oauth2/service_account.py b/google/oauth2/service_account.py
index af86588..54630d3 100644
--- a/google/oauth2/service_account.py
+++ b/google/oauth2/service_account.py
@@ -112,6 +112,10 @@
scoped_credentials = credentials.with_scopes(['email'])
delegated_credentials = credentials.with_subject(subject)
+
+ To add a quota project, use :meth:`with_quota_project`::
+
+ credentials = credentials.with_quota_project('myproject-123')
"""
def __init__(
@@ -122,6 +126,7 @@
scopes=None,
subject=None,
project_id=None,
+ quota_project_id=None,
additional_claims=None,
):
"""
@@ -135,6 +140,8 @@
user to for which to request delegated access.
project_id (str): Project ID associated with the service account
credential.
+ quota_project_id (Optional[str]): The project ID used for quota and
+ billing.
additional_claims (Mapping[str, str]): Any additional claims for
the JWT assertion used in the authorization grant.
@@ -150,6 +157,7 @@
self._service_account_email = service_account_email
self._subject = subject
self._project_id = project_id
+ self._quota_project_id = quota_project_id
self._token_uri = token_uri
if additional_claims is not None:
@@ -230,6 +238,11 @@
return self._project_id
@property
+ def quota_project_id(self):
+ """Project ID to use for quota and billing purposes."""
+ return self._quota_project_id
+
+ @property
def requires_scopes(self):
"""Checks if the credentials requires scopes.
@@ -247,6 +260,7 @@
token_uri=self._token_uri,
subject=self._subject,
project_id=self._project_id,
+ quota_project_id=self._quota_project_id,
additional_claims=self._additional_claims.copy(),
)
@@ -267,6 +281,7 @@
token_uri=self._token_uri,
subject=subject,
project_id=self._project_id,
+ quota_project_id=self._quota_project_id,
additional_claims=self._additional_claims.copy(),
)
@@ -292,9 +307,32 @@
token_uri=self._token_uri,
subject=self._subject,
project_id=self._project_id,
+ quota_project_id=self._quota_project_id,
additional_claims=new_additional_claims,
)
+ def with_quota_project(self, quota_project_id):
+ """Returns a copy of these credentials with a modified quota project.
+
+ Args:
+ quota_project_id (str): The project to use for quota and
+ billing purposes
+
+ Returns:
+ google.auth.service_account.Credentials: A new credentials
+ instance.
+ """
+ return self.__class__(
+ self._signer,
+ service_account_email=self._service_account_email,
+ scopes=self._scopes,
+ token_uri=self._token_uri,
+ subject=self._subject,
+ project_id=self._project_id,
+ quota_project_id=quota_project_id,
+ additional_claims=self._additional_claims.copy(),
+ )
+
def _make_authorization_grant_assertion(self):
"""Create the OAuth 2.0 assertion.
@@ -335,6 +373,12 @@
self.token = access_token
self.expiry = expiry
+ @_helpers.copy_docstring(credentials.Credentials)
+ def apply(self, headers, token=None):
+ super(Credentials, self).apply(headers, token=token)
+ if self.quota_project_id is not None:
+ headers["x-goog-user-project"] = self.quota_project_id
+
@_helpers.copy_docstring(credentials.Signing)
def sign_bytes(self, message):
return self._signer.sign(message)