Skip to content

Commit 7c72739

Browse files
feat: add api key support (googleapis#969)
* feat: add api key support * chore: update integration tests
1 parent 9b66c72 commit 7c72739

File tree

14 files changed

+403
-7
lines changed

14 files changed

+403
-7
lines changed

gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,12 +309,16 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
309309

310310
api_endpoint, client_cert_source_func = self.get_mtls_endpoint_and_cert_source(client_options)
311311

312+
api_key_value = getattr(client_options, "api_key", None)
313+
if api_key_value and credentials:
314+
raise ValueError("client_options.api_key and credentials are mutually exclusive")
315+
312316
# Save or instantiate the transport.
313317
# Ordinarily, we provide the transport, but allowing a custom transport
314318
# instance provides an extensibility point for unusual situations.
315319
if isinstance(transport, {{ service.name }}Transport):
316320
# transport is a {{ service.name }}Transport instance.
317-
if credentials or client_options.credentials_file:
321+
if credentials or client_options.credentials_file or api_key_value:
318322
raise ValueError("When providing a transport instance, "
319323
"provide its credentials directly.")
320324
if client_options.scopes:
@@ -324,6 +328,11 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
324328
)
325329
self._transport = transport
326330
else:
331+
import google.auth._default # type: ignore
332+
333+
if api_key_value and hasattr(google.auth._default, "get_api_key_credentials"):
334+
credentials = google.auth._default.get_api_key_credentials(api_key_value)
335+
327336
Transport = type(self).get_transport_class(transport)
328337
self._transport = Transport(
329338
credentials=credentials,

gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1829,6 +1829,27 @@ def test_credentials_transport_error():
18291829
client_options={"credentials_file": "credentials.json"},
18301830
transport=transport,
18311831
)
1832+
1833+
# It is an error to provide an api_key and a transport instance.
1834+
transport = transports.{{ service.name }}{{ opts.transport[0].capitalize() }}Transport(
1835+
credentials=ga_credentials.AnonymousCredentials(),
1836+
)
1837+
options = client_options.ClientOptions()
1838+
options.api_key = "api_key"
1839+
with pytest.raises(ValueError):
1840+
client = {{ service.client_name }}(
1841+
client_options=options,
1842+
transport=transport,
1843+
)
1844+
1845+
# It is an error to provide an api_key and a credential.
1846+
options = mock.Mock()
1847+
options.api_key = "api_key"
1848+
with pytest.raises(ValueError):
1849+
client = {{ service.client_name }}(
1850+
client_options=options,
1851+
credentials=ga_credentials.AnonymousCredentials()
1852+
)
18321853

18331854
# It is an error to provide scopes and a transport instance.
18341855
transport = transports.{{ service.name }}{{ opts.transport[0].capitalize() }}Transport(
@@ -2897,4 +2918,34 @@ def test_client_ctx():
28972918
pass
28982919
close.assert_called()
28992920

2921+
@pytest.mark.parametrize("client_class,transport_class", [
2922+
{% if 'grpc' in opts.transport %}
2923+
({{ service.client_name }}, transports.{{ service.grpc_transport_name }}),
2924+
({{ service.async_client_name }}, transports.{{ service.grpc_asyncio_transport_name }}),
2925+
{% elif 'rest' in opts.transport %}
2926+
({{ service.client_name }}, transports.{{ service.rest_transport_name }}),
2927+
{% endif %}
2928+
])
2929+
def test_api_key_credentials(client_class, transport_class):
2930+
with mock.patch.object(
2931+
google.auth._default, "get_api_key_credentials", create=True
2932+
) as get_api_key_credentials:
2933+
mock_cred = mock.Mock()
2934+
get_api_key_credentials.return_value = mock_cred
2935+
options = client_options.ClientOptions()
2936+
options.api_key = "api_key"
2937+
with mock.patch.object(transport_class, "__init__") as patched:
2938+
patched.return_value = None
2939+
client = client_class(client_options=options)
2940+
patched.assert_called_once_with(
2941+
credentials=mock_cred,
2942+
credentials_file=None,
2943+
host=client.DEFAULT_ENDPOINT,
2944+
scopes=None,
2945+
client_cert_source_for_mtls=None,
2946+
quota_project_id=None,
2947+
client_info=transports.base.DEFAULT_CLIENT_INFO,
2948+
always_use_jwt_access=True,
2949+
)
2950+
29002951
{% endblock %}

tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/client.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,12 +349,16 @@ def __init__(self, *,
349349

350350
api_endpoint, client_cert_source_func = self.get_mtls_endpoint_and_cert_source(client_options)
351351

352+
api_key_value = getattr(client_options, "api_key", None)
353+
if api_key_value and credentials:
354+
raise ValueError("client_options.api_key and credentials are mutually exclusive")
355+
352356
# Save or instantiate the transport.
353357
# Ordinarily, we provide the transport, but allowing a custom transport
354358
# instance provides an extensibility point for unusual situations.
355359
if isinstance(transport, AssetServiceTransport):
356360
# transport is a AssetServiceTransport instance.
357-
if credentials or client_options.credentials_file:
361+
if credentials or client_options.credentials_file or api_key_value:
358362
raise ValueError("When providing a transport instance, "
359363
"provide its credentials directly.")
360364
if client_options.scopes:
@@ -364,6 +368,11 @@ def __init__(self, *,
364368
)
365369
self._transport = transport
366370
else:
371+
import google.auth._default # type: ignore
372+
373+
if api_key_value and hasattr(google.auth._default, "get_api_key_credentials"):
374+
credentials = google.auth._default.get_api_key_credentials(api_key_value)
375+
367376
Transport = type(self).get_transport_class(transport)
368377
self._transport = Transport(
369378
credentials=credentials,

tests/integration/goldens/asset/tests/unit/gapic/asset_v1/test_asset_service.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3547,6 +3547,27 @@ def test_credentials_transport_error():
35473547
transport=transport,
35483548
)
35493549

3550+
# It is an error to provide an api_key and a transport instance.
3551+
transport = transports.AssetServiceGrpcTransport(
3552+
credentials=ga_credentials.AnonymousCredentials(),
3553+
)
3554+
options = client_options.ClientOptions()
3555+
options.api_key = "api_key"
3556+
with pytest.raises(ValueError):
3557+
client = AssetServiceClient(
3558+
client_options=options,
3559+
transport=transport,
3560+
)
3561+
3562+
# It is an error to provide an api_key and a credential.
3563+
options = mock.Mock()
3564+
options.api_key = "api_key"
3565+
with pytest.raises(ValueError):
3566+
client = AssetServiceClient(
3567+
client_options=options,
3568+
credentials=ga_credentials.AnonymousCredentials()
3569+
)
3570+
35503571
# It is an error to provide scopes and a transport instance.
35513572
transport = transports.AssetServiceGrpcTransport(
35523573
credentials=ga_credentials.AnonymousCredentials(),
@@ -4129,3 +4150,29 @@ def test_client_ctx():
41294150
with client:
41304151
pass
41314152
close.assert_called()
4153+
4154+
@pytest.mark.parametrize("client_class,transport_class", [
4155+
(AssetServiceClient, transports.AssetServiceGrpcTransport),
4156+
(AssetServiceAsyncClient, transports.AssetServiceGrpcAsyncIOTransport),
4157+
])
4158+
def test_api_key_credentials(client_class, transport_class):
4159+
with mock.patch.object(
4160+
google.auth._default, "get_api_key_credentials", create=True
4161+
) as get_api_key_credentials:
4162+
mock_cred = mock.Mock()
4163+
get_api_key_credentials.return_value = mock_cred
4164+
options = client_options.ClientOptions()
4165+
options.api_key = "api_key"
4166+
with mock.patch.object(transport_class, "__init__") as patched:
4167+
patched.return_value = None
4168+
client = client_class(client_options=options)
4169+
patched.assert_called_once_with(
4170+
credentials=mock_cred,
4171+
credentials_file=None,
4172+
host=client.DEFAULT_ENDPOINT,
4173+
scopes=None,
4174+
client_cert_source_for_mtls=None,
4175+
quota_project_id=None,
4176+
client_info=transports.base.DEFAULT_CLIENT_INFO,
4177+
always_use_jwt_access=True,
4178+
)

tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/client.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,12 +345,16 @@ def __init__(self, *,
345345

346346
api_endpoint, client_cert_source_func = self.get_mtls_endpoint_and_cert_source(client_options)
347347

348+
api_key_value = getattr(client_options, "api_key", None)
349+
if api_key_value and credentials:
350+
raise ValueError("client_options.api_key and credentials are mutually exclusive")
351+
348352
# Save or instantiate the transport.
349353
# Ordinarily, we provide the transport, but allowing a custom transport
350354
# instance provides an extensibility point for unusual situations.
351355
if isinstance(transport, IAMCredentialsTransport):
352356
# transport is a IAMCredentialsTransport instance.
353-
if credentials or client_options.credentials_file:
357+
if credentials or client_options.credentials_file or api_key_value:
354358
raise ValueError("When providing a transport instance, "
355359
"provide its credentials directly.")
356360
if client_options.scopes:
@@ -360,6 +364,11 @@ def __init__(self, *,
360364
)
361365
self._transport = transport
362366
else:
367+
import google.auth._default # type: ignore
368+
369+
if api_key_value and hasattr(google.auth._default, "get_api_key_credentials"):
370+
credentials = google.auth._default.get_api_key_credentials(api_key_value)
371+
363372
Transport = type(self).get_transport_class(transport)
364373
self._transport = Transport(
365374
credentials=credentials,

tests/integration/goldens/credentials/tests/unit/gapic/credentials_v1/test_iam_credentials.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1491,6 +1491,27 @@ def test_credentials_transport_error():
14911491
transport=transport,
14921492
)
14931493

1494+
# It is an error to provide an api_key and a transport instance.
1495+
transport = transports.IAMCredentialsGrpcTransport(
1496+
credentials=ga_credentials.AnonymousCredentials(),
1497+
)
1498+
options = client_options.ClientOptions()
1499+
options.api_key = "api_key"
1500+
with pytest.raises(ValueError):
1501+
client = IAMCredentialsClient(
1502+
client_options=options,
1503+
transport=transport,
1504+
)
1505+
1506+
# It is an error to provide an api_key and a credential.
1507+
options = mock.Mock()
1508+
options.api_key = "api_key"
1509+
with pytest.raises(ValueError):
1510+
client = IAMCredentialsClient(
1511+
client_options=options,
1512+
credentials=ga_credentials.AnonymousCredentials()
1513+
)
1514+
14941515
# It is an error to provide scopes and a transport instance.
14951516
transport = transports.IAMCredentialsGrpcTransport(
14961517
credentials=ga_credentials.AnonymousCredentials(),
@@ -2011,3 +2032,29 @@ def test_client_ctx():
20112032
with client:
20122033
pass
20132034
close.assert_called()
2035+
2036+
@pytest.mark.parametrize("client_class,transport_class", [
2037+
(IAMCredentialsClient, transports.IAMCredentialsGrpcTransport),
2038+
(IAMCredentialsAsyncClient, transports.IAMCredentialsGrpcAsyncIOTransport),
2039+
])
2040+
def test_api_key_credentials(client_class, transport_class):
2041+
with mock.patch.object(
2042+
google.auth._default, "get_api_key_credentials", create=True
2043+
) as get_api_key_credentials:
2044+
mock_cred = mock.Mock()
2045+
get_api_key_credentials.return_value = mock_cred
2046+
options = client_options.ClientOptions()
2047+
options.api_key = "api_key"
2048+
with mock.patch.object(transport_class, "__init__") as patched:
2049+
patched.return_value = None
2050+
client = client_class(client_options=options)
2051+
patched.assert_called_once_with(
2052+
credentials=mock_cred,
2053+
credentials_file=None,
2054+
host=client.DEFAULT_ENDPOINT,
2055+
scopes=None,
2056+
client_cert_source_for_mtls=None,
2057+
quota_project_id=None,
2058+
client_info=transports.base.DEFAULT_CLIENT_INFO,
2059+
always_use_jwt_access=True,
2060+
)

tests/integration/goldens/logging/google/cloud/logging_v2/services/config_service_v2/client.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,12 +380,16 @@ def __init__(self, *,
380380

381381
api_endpoint, client_cert_source_func = self.get_mtls_endpoint_and_cert_source(client_options)
382382

383+
api_key_value = getattr(client_options, "api_key", None)
384+
if api_key_value and credentials:
385+
raise ValueError("client_options.api_key and credentials are mutually exclusive")
386+
383387
# Save or instantiate the transport.
384388
# Ordinarily, we provide the transport, but allowing a custom transport
385389
# instance provides an extensibility point for unusual situations.
386390
if isinstance(transport, ConfigServiceV2Transport):
387391
# transport is a ConfigServiceV2Transport instance.
388-
if credentials or client_options.credentials_file:
392+
if credentials or client_options.credentials_file or api_key_value:
389393
raise ValueError("When providing a transport instance, "
390394
"provide its credentials directly.")
391395
if client_options.scopes:
@@ -395,6 +399,11 @@ def __init__(self, *,
395399
)
396400
self._transport = transport
397401
else:
402+
import google.auth._default # type: ignore
403+
404+
if api_key_value and hasattr(google.auth._default, "get_api_key_credentials"):
405+
credentials = google.auth._default.get_api_key_credentials(api_key_value)
406+
398407
Transport = type(self).get_transport_class(transport)
399408
self._transport = Transport(
400409
credentials=credentials,

tests/integration/goldens/logging/google/cloud/logging_v2/services/logging_service_v2/client.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,12 +336,16 @@ def __init__(self, *,
336336

337337
api_endpoint, client_cert_source_func = self.get_mtls_endpoint_and_cert_source(client_options)
338338

339+
api_key_value = getattr(client_options, "api_key", None)
340+
if api_key_value and credentials:
341+
raise ValueError("client_options.api_key and credentials are mutually exclusive")
342+
339343
# Save or instantiate the transport.
340344
# Ordinarily, we provide the transport, but allowing a custom transport
341345
# instance provides an extensibility point for unusual situations.
342346
if isinstance(transport, LoggingServiceV2Transport):
343347
# transport is a LoggingServiceV2Transport instance.
344-
if credentials or client_options.credentials_file:
348+
if credentials or client_options.credentials_file or api_key_value:
345349
raise ValueError("When providing a transport instance, "
346350
"provide its credentials directly.")
347351
if client_options.scopes:
@@ -351,6 +355,11 @@ def __init__(self, *,
351355
)
352356
self._transport = transport
353357
else:
358+
import google.auth._default # type: ignore
359+
360+
if api_key_value and hasattr(google.auth._default, "get_api_key_credentials"):
361+
credentials = google.auth._default.get_api_key_credentials(api_key_value)
362+
354363
Transport = type(self).get_transport_class(transport)
355364
self._transport = Transport(
356365
credentials=credentials,

tests/integration/goldens/logging/google/cloud/logging_v2/services/metrics_service_v2/client.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,12 +337,16 @@ def __init__(self, *,
337337

338338
api_endpoint, client_cert_source_func = self.get_mtls_endpoint_and_cert_source(client_options)
339339

340+
api_key_value = getattr(client_options, "api_key", None)
341+
if api_key_value and credentials:
342+
raise ValueError("client_options.api_key and credentials are mutually exclusive")
343+
340344
# Save or instantiate the transport.
341345
# Ordinarily, we provide the transport, but allowing a custom transport
342346
# instance provides an extensibility point for unusual situations.
343347
if isinstance(transport, MetricsServiceV2Transport):
344348
# transport is a MetricsServiceV2Transport instance.
345-
if credentials or client_options.credentials_file:
349+
if credentials or client_options.credentials_file or api_key_value:
346350
raise ValueError("When providing a transport instance, "
347351
"provide its credentials directly.")
348352
if client_options.scopes:
@@ -352,6 +356,11 @@ def __init__(self, *,
352356
)
353357
self._transport = transport
354358
else:
359+
import google.auth._default # type: ignore
360+
361+
if api_key_value and hasattr(google.auth._default, "get_api_key_credentials"):
362+
credentials = google.auth._default.get_api_key_credentials(api_key_value)
363+
355364
Transport = type(self).get_transport_class(transport)
356365
self._transport = Transport(
357366
credentials=credentials,

0 commit comments

Comments
 (0)