Repository: googleapis/python-api-core
Pull request #411 (2022-07-13 13:30:50)

feat: adds support for audience in client_options in v1 branch
feat: add api_key to client options in v1 branch

Cherry-pick #248 and #379 into v1 branch

These features are needed so that we can continue to support `google-api-core` 1.x in cloud client libraries. 

Towards b/238481913

parthea: Unit tests pass locally for python 3.7 and 3.9
```
partheniou@partheniou-vm-2:~/git/python-api-core$ nox -s unit-3.9
nox > Running session unit-3.9
nox > Creating virtual environment (virtualenv) using python3.9 in .nox/unit-3-9
nox > python -m pip install mock pytest pytest-cov 'grpcio >= 1.0.2'
nox > python -m pip install -e . -c /usr/local/google/home/partheniou/git/python-api-core/testing/constraints-3.9.txt
nox > python -m pip install asyncmock pytest-asyncio proto-plus
nox > python -m py.test --quiet --cov=google.api_core --cov=tests.unit --cov-append --cov-config=.coveragerc --cov-report= --cov-fail-under=0 tests/unit --cov=tests.asyncio tests/asyncio
........................................................................ [ 14%]
........................................................................ [ 28%]
...................s.................................................... [ 43%]
........................................................................ [ 57%]
........................................................................ [ 72%]
........................................................................ [ 86%]
...................................................................      [100%]

```
```
 partheniou@partheniou-vm-2:~/git/python-api-core$ nox -s unit-3.7
nox > Running session unit-3.7
nox > Creating virtual environment (virtualenv) using python3.7 in .nox/unit-3-7
nox > python -m pip install mock pytest pytest-cov 'grpcio >= 1.0.2'
nox > python -m pip install -e . -c /usr/local/google/home/partheniou/git/python-api-core/testing/constraints-3.7.txt
nox > python -m pip install asyncmock pytest-asyncio proto-plus
nox > python -m py.test --quiet --cov=google.api_core --cov=tests.unit --cov-append --cov-config=.coveragerc --cov-report= --cov-fail-under=0 tests/unit --cov=tests.asyncio tests/asyncio
........................................................................ [ 14%]
........................................................................ [ 28%]
...................s.................................................... [ 43%]
........................................................................ [ 57%]
........................................................................ [ 72%]
........................................................................ [ 86%]
...................................................................      [100%]
```

Patch:
diff --git a/google/api_core/client_options.py b/google/api_core/client_options.py
index 57000e9..0d9afc3 100644
--- a/google/api_core/client_options.py
+++ b/google/api_core/client_options.py
@@ -66,11 +66,19 @@ class ClientOptions(object):
         quota_project_id (Optional[str]): A project name that a client's
             quota belongs to.
         credentials_file (Optional[str]): A path to a file storing credentials.
+            ``credentials_file` and ``api_key`` are mutually exclusive.
         scopes (Optional[Sequence[str]]): OAuth access token override scopes.
+        api_key (Optional[str]): Google API key. ``credentials_file`` and
+            ``api_key`` are mutually exclusive.
+        api_audience (Optional[str]): The intended audience for the API calls
+            to the service that will be set when using certain 3rd party
+            authentication flows. Audience is typically a resource identifier.
+            If not set, the service endpoint value will be used as a default.
+            An example of a valid ``api_audience`` is: "https://language.googleapis.com".
 
     Raises:
         ValueError: If both ``client_cert_source`` and ``client_encrypted_cert_source``
-            are provided.
+            are provided, or both ``credentials_file`` and ``api_key`` are provided.
     """
 
     def __init__(
@@ -81,17 +89,23 @@ class ClientOptions(object):
         quota_project_id=None,
         credentials_file=None,
         scopes=None,
+        api_key=None,
+        api_audience=None,
     ):
         if client_cert_source and client_encrypted_cert_source:
             raise ValueError(
                 "client_cert_source and client_encrypted_cert_source are mutually exclusive"
             )
+        if api_key and credentials_file:
+            raise ValueError("api_key and credentials_file are mutually exclusive")
         self.api_endpoint = api_endpoint
         self.client_cert_source = client_cert_source
         self.client_encrypted_cert_source = client_encrypted_cert_source
         self.quota_project_id = quota_project_id
         self.credentials_file = credentials_file
         self.scopes = scopes
+        self.api_key = api_key
+        self.api_audience = api_audience
 
     def __repr__(self):
         return "ClientOptions: " + repr(self.__dict__)


Test patch:
diff --git a/tests/unit/test_client_options.py b/tests/unit/test_client_options.py
index 38b9ad0..40edcc1 100644
--- a/tests/unit/test_client_options.py
+++ b/tests/unit/test_client_options.py
@@ -36,6 +36,7 @@ def test_constructor():
             "https://www.googleapis.com/auth/cloud-platform",
             "https://www.googleapis.com/auth/cloud-platform.read-only",
         ],
+        api_audience="foo2.googleapis.com",
     )
 
     assert options.api_endpoint == "foo.googleapis.com"
@@ -46,6 +47,7 @@ def test_constructor():
         "https://www.googleapis.com/auth/cloud-platform",
         "https://www.googleapis.com/auth/cloud-platform.read-only",
     ]
+    assert options.api_audience == "foo2.googleapis.com"
 
 
 def test_constructor_with_encrypted_cert_source():
@@ -72,6 +74,36 @@ def test_constructor_with_both_cert_sources():
         )
 
 
+def test_constructor_with_api_key():
+
+    options = client_options.ClientOptions(
+        api_endpoint="foo.googleapis.com",
+        client_cert_source=get_client_cert,
+        quota_project_id="quote-proj",
+        api_key="api-key",
+        scopes=[
+            "https://www.googleapis.com/auth/cloud-platform",
+            "https://www.googleapis.com/auth/cloud-platform.read-only",
+        ],
+    )
+
+    assert options.api_endpoint == "foo.googleapis.com"
+    assert options.client_cert_source() == (b"cert", b"key")
+    assert options.quota_project_id == "quote-proj"
+    assert options.api_key == "api-key"
+    assert options.scopes == [
+        "https://www.googleapis.com/auth/cloud-platform",
+        "https://www.googleapis.com/auth/cloud-platform.read-only",
+    ]
+
+
+def test_constructor_with_both_api_key_and_credentials_file():
+    with pytest.raises(ValueError):
+        client_options.ClientOptions(
+            api_key="api-key", credentials_file="path/to/credentials.json",
+        )
+
+
 def test_from_dict():
     options = client_options.from_dict(
         {
@@ -83,6 +115,7 @@ def test_from_dict():
                 "https://www.googleapis.com/auth/cloud-platform",
                 "https://www.googleapis.com/auth/cloud-platform.read-only",
             ],
+            "api_audience": "foo2.googleapis.com",
         }
     )
 
@@ -94,6 +127,8 @@ def test_from_dict():
         "https://www.googleapis.com/auth/cloud-platform",
         "https://www.googleapis.com/auth/cloud-platform.read-only",
     ]
+    assert options.api_key is None
+    assert options.api_audience == "foo2.googleapis.com"
 
 
 def test_from_dict_bad_argument():
@@ -112,6 +147,6 @@ def test_repr():
 
     assert (
         repr(options)
-        == "ClientOptions: {'api_endpoint': 'foo.googleapis.com', 'client_cert_source': None, 'client_encrypted_cert_source': None}"
-        or "ClientOptions: {'client_encrypted_cert_source': None, 'client_cert_source': None, 'api_endpoint': 'foo.googleapis.com'}"
+        == "ClientOptions: {'api_endpoint': 'foo.googleapis.com', 'client_cert_source': None, 'client_encrypted_cert_source': None, 'api_key': None}"
+        or "ClientOptions: {'client_encrypted_cert_source': None, 'client_cert_source': None, 'api_endpoint': 'foo.googleapis.com', 'api_key': None}"
     )