Skip to content
This repository was archived by the owner on Mar 26, 2026. It is now read-only.

Commit b2e5274

Browse files
authored
feat: allow user-provided client info (#573)
Fix for googleapis/python-kms#37, #566, and similar.
1 parent 7c2bab7 commit b2e5274

10 files changed

Lines changed: 145 additions & 36 deletions

File tree

gapic/ads-templates/%namespace/%name/%version/%sub/services/%service/client.py.j2

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ from google.oauth2 import service_account # type: ignore
2323
{% endfor -%}
2424
{% endfor -%}
2525
{% endfilter %}
26-
from .transports.base import {{ service.name }}Transport
26+
from .transports.base import {{ service.name }}Transport, DEFAULT_CLIENT_INFO
2727
from .transports.grpc import {{ service.name }}GrpcTransport
2828

2929

@@ -135,6 +135,7 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
135135
credentials: credentials.Credentials = None,
136136
transport: Union[str, {{ service.name }}Transport] = None,
137137
client_options: ClientOptions = None,
138+
client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
138139
) -> None:
139140
"""Instantiate the {{ (service.client_name|snake_case).replace('_', ' ') }}.
140141

@@ -160,6 +161,11 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
160161
(2) The ``client_cert_source`` property is used to provide client
161162
SSL credentials for mutual TLS transport. If not provided, the
162163
default SSL credentials will be used if present.
164+
client_info (google.api_core.gapic_v1.client_info.ClientInfo):
165+
The client info used to send a user-agent string along with
166+
API requests. If ``None``, then default info will be used.
167+
Generally, you only need to set this if you're developing
168+
your own client library.
163169

164170
Raises:
165171
google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
@@ -209,6 +215,7 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
209215
host=client_options.api_endpoint,
210216
api_mtls_endpoint=client_options.api_endpoint,
211217
client_cert_source=client_options.client_cert_source,
218+
client_info=client_info,
212219
)
213220

214221

gapic/ads-templates/%namespace/%name/%version/%sub/services/%service/transports/base.py.j2

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ from google.auth import credentials # type: ignore
2121
{% endfilter %}
2222

2323
try:
24-
_client_info = gapic_v1.client_info.ClientInfo(
24+
DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
2525
gapic_version=pkg_resources.get_distribution(
2626
'{{ api.naming.warehouse_package_name }}',
2727
).version,
2828
)
2929
except pkg_resources.DistributionNotFound:
30-
_client_info = gapic_v1.client_info.ClientInfo()
30+
DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo()
3131

3232

3333
class {{ service.name }}Transport(metaclass=abc.ABCMeta):
@@ -43,6 +43,7 @@ class {{ service.name }}Transport(metaclass=abc.ABCMeta):
4343
self, *,
4444
host: str{% if service.host %} = '{{ service.host }}'{% endif %},
4545
credentials: credentials.Credentials = None,
46+
client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
4647
) -> None:
4748
"""Instantiate the transport.
4849

@@ -54,6 +55,11 @@ class {{ service.name }}Transport(metaclass=abc.ABCMeta):
5455
credentials identify the application to the service; if none
5556
are specified, the client will attempt to ascertain the
5657
credentials from the environment.
58+
client_info (google.api_core.gapic_v1.client_info.ClientInfo):
59+
The client info used to send a user-agent string along with
60+
API requests. If ``None``, then default info will be used.
61+
Generally, you only need to set this if you're developing
62+
your own client library.
5763
"""
5864
# Save the hostname. Default to port 443 (HTTPS) if none is specified.
5965
if ':' not in host:
@@ -69,9 +75,9 @@ class {{ service.name }}Transport(metaclass=abc.ABCMeta):
6975
self._credentials = credentials
7076

7177
# Lifted into its own function so it can be stubbed out during tests.
72-
self._prep_wrapped_messages()
78+
self._prep_wrapped_messages(client_info)
7379

74-
def _prep_wrapped_messages(self):
80+
def _prep_wrapped_messages(self, client_info):
7581
# Precomputed wrapped methods
7682
self._wrapped_methods = {
7783
{% for method in service.methods.values() -%}
@@ -92,7 +98,7 @@ class {{ service.name }}Transport(metaclass=abc.ABCMeta):
9298
),
9399
{%- endif %}
94100
default_timeout={{ method.timeout }},
95-
client_info=_client_info,
101+
client_info=client_info,
96102
),
97103
{% endfor %} {# precomputed wrappers loop #}
98104
}

gapic/ads-templates/%namespace/%name/%version/%sub/services/%service/transports/grpc.py.j2

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ from google.api_core import grpc_helpers # type: ignore
77
{%- if service.has_lro %}
88
from google.api_core import operations_v1 # type: ignore
99
{%- endif %}
10+
from google.api_core import gapic_v1 # type: ignore
1011
from google import auth # type: ignore
1112
from google.auth import credentials # type: ignore
1213
from google.auth.transport.grpc import SslCredentials # type: ignore
@@ -20,7 +21,7 @@ import grpc # type: ignore
2021
{{ method.output.ident.python_import }}
2122
{% endfor -%}
2223
{% endfilter %}
23-
from .base import {{ service.name }}Transport
24+
from .base import {{ service.name }}Transport, DEFAULT_CLIENT_INFO
2425

2526

2627
class {{ service.name }}GrpcTransport({{ service.name }}Transport):
@@ -40,7 +41,9 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport):
4041
credentials: credentials.Credentials = None,
4142
channel: grpc.Channel = None,
4243
api_mtls_endpoint: str = None,
43-
client_cert_source: Callable[[], Tuple[bytes, bytes]] = None) -> None:
44+
client_cert_source: Callable[[], Tuple[bytes, bytes]] = None,
45+
client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
46+
) -> None:
4447
"""Instantiate the transport.
4548

4649
Args:
@@ -62,6 +65,11 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport):
6265
callback to provide client SSL certificate bytes and private key
6366
bytes, both in PEM format. It is ignored if ``api_mtls_endpoint``
6467
is None.
68+
client_info (google.api_core.gapic_v1.client_info.ClientInfo):
69+
The client info used to send a user-agent string along with
70+
API requests. If ``None``, then default info will be used.
71+
Generally, you only need to set this if you're developing
72+
your own client library.
6573

6674
Raises:
6775
google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
@@ -101,7 +109,11 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport):
101109
self._stubs = {} # type: Dict[str, Callable]
102110

103111
# Run the base constructor.
104-
super().__init__(host=host, credentials=credentials)
112+
super().__init__(
113+
host=host,
114+
credentials=credentials,
115+
client_info=client_info,
116+
)
105117

106118

107119
@classmethod

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

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,7 @@ from google.api_core import future
2424
from google.api_core import operations_v1
2525
from google.longrunning import operations_pb2
2626
{% endif -%}
27-
{% if service.has_pagers -%}
2827
from google.api_core import gapic_v1
29-
{% endif -%}
3028
{% for method in service.methods.values() -%}
3129
{% for ref_type in method.ref_types
3230
if not ((ref_type.ident.python_import.package == ('google', 'api_core') and ref_type.ident.python_import.module == 'operation')
@@ -109,6 +107,7 @@ def test_{{ service.client_name|snake_case }}_client_options():
109107
client_cert_source=None,
110108
credentials=None,
111109
host="squid.clam.whelk",
110+
client_info=transports.base.DEFAULT_CLIENT_INFO,
112111
)
113112

114113
# Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is
@@ -122,6 +121,7 @@ def test_{{ service.client_name|snake_case }}_client_options():
122121
client_cert_source=None,
123122
credentials=None,
124123
host=client.DEFAULT_ENDPOINT,
124+
client_info=transports.base.DEFAULT_CLIENT_INFO,
125125
)
126126

127127
# Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is
@@ -135,6 +135,7 @@ def test_{{ service.client_name|snake_case }}_client_options():
135135
client_cert_source=None,
136136
credentials=None,
137137
host=client.DEFAULT_MTLS_ENDPOINT,
138+
client_info=transports.base.DEFAULT_CLIENT_INFO,
138139
)
139140

140141
# Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is
@@ -149,6 +150,7 @@ def test_{{ service.client_name|snake_case }}_client_options():
149150
client_cert_source=client_cert_source_callback,
150151
credentials=None,
151152
host=client.DEFAULT_MTLS_ENDPOINT,
153+
client_info=transports.base.DEFAULT_CLIENT_INFO,
152154
)
153155

154156
# Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is
@@ -163,6 +165,7 @@ def test_{{ service.client_name|snake_case }}_client_options():
163165
client_cert_source=None,
164166
credentials=None,
165167
host=client.DEFAULT_MTLS_ENDPOINT,
168+
client_info=transports.base.DEFAULT_CLIENT_INFO,
166169
)
167170

168171
# Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is
@@ -177,6 +180,7 @@ def test_{{ service.client_name|snake_case }}_client_options():
177180
client_cert_source=None,
178181
credentials=None,
179182
host=client.DEFAULT_ENDPOINT,
183+
client_info=transports.base.DEFAULT_CLIENT_INFO,
180184
)
181185

182186
# Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS has
@@ -197,6 +201,7 @@ def test_{{ service.client_name|snake_case }}_client_options_from_dict():
197201
client_cert_source=None,
198202
credentials=None,
199203
host="squid.clam.whelk",
204+
client_info=transports.base.DEFAULT_CLIENT_INFO,
200205
)
201206

202207

@@ -769,4 +774,23 @@ def test_parse_{{ message.resource_type|snake_case }}_path():
769774
{% endwith -%}
770775
{% endfor -%}
771776

777+
def test_client_withDEFAULT_CLIENT_INFO():
778+
client_info = gapic_v1.client_info.ClientInfo()
779+
780+
with mock.patch.object(transports.{{ service.name }}Transport, '_prep_wrapped_messages') as prep:
781+
client = {{ service.client_name }}(
782+
credentials=credentials.AnonymousCredentials(),
783+
client_info=client_info,
784+
)
785+
prep.assert_called_once_with(client_info)
786+
787+
with mock.patch.object(transports.{{ service.name }}Transport, '_prep_wrapped_messages') as prep:
788+
transport_class = {{ service.client_name }}.get_transport_class()
789+
transport = transport_class(
790+
credentials=credentials.AnonymousCredentials(),
791+
client_info=client_info,
792+
)
793+
prep.assert_called_once_with(client_info)
794+
795+
772796
{% endblock %}

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

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ from google.iam.v1 import iam_policy_pb2 as iam_policy # type: ignore
2525
from google.iam.v1 import policy_pb2 as policy # type: ignore
2626
{% endif %}
2727
{% endfilter %}
28-
from .transports.base import {{ service.name }}Transport
28+
from .transports.base import {{ service.name }}Transport, DEFAULT_CLIENT_INFO
2929
from .transports.grpc_asyncio import {{ service.grpc_asyncio_transport_name }}
3030
from .client import {{ service.client_name }}
3131

@@ -52,6 +52,7 @@ class {{ service.async_client_name }}:
5252
credentials: credentials.Credentials = None,
5353
transport: Union[str, {{ service.name }}Transport] = 'grpc_asyncio',
5454
client_options: ClientOptions = None,
55+
client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
5556
) -> None:
5657
"""Instantiate the {{ (service.client_name|snake_case).replace('_', ' ') }}.
5758

@@ -87,6 +88,8 @@ class {{ service.async_client_name }}:
8788
credentials=credentials,
8889
transport=transport,
8990
client_options=client_options,
91+
client_info=client_info,
92+
9093
)
9194

9295
{% for method in service.methods.values() -%}
@@ -202,7 +205,7 @@ class {{ service.async_client_name }}:
202205
),
203206
{%- endif %}
204207
default_timeout={{ method.timeout }},
205-
client_info=_client_info,
208+
client_info=DEFAULT_CLIENT_INFO,
206209
)
207210
{%- if method.field_headers %}
208211

@@ -352,7 +355,7 @@ class {{ service.async_client_name }}:
352355
rpc = gapic_v1.method_async.wrap_method(
353356
self._client._transport.set_iam_policy,
354357
default_timeout=None,
355-
client_info=_client_info,
358+
client_info=DEFAULT_CLIENT_INFO,
356359
)
357360

358361
# Certain fields should be provided within the metadata header;
@@ -459,7 +462,7 @@ class {{ service.async_client_name }}:
459462
rpc = gapic_v1.method_async.wrap_method(
460463
self._client._transport.get_iam_policy,
461464
default_timeout=None,
462-
client_info=_client_info,
465+
client_info=DEFAULT_CLIENT_INFO,
463466
)
464467

465468
# Certain fields should be provided within the metadata header;
@@ -510,7 +513,7 @@ class {{ service.async_client_name }}:
510513
rpc = gapic_v1.method_async.wrap_method(
511514
self._client._transport.test_iam_permissions,
512515
default_timeout=None,
513-
client_info=_client_info,
516+
client_info=DEFAULT_CLIENT_INFO,
514517
)
515518

516519
# Certain fields should be provided within the metadata header;
@@ -527,13 +530,13 @@ class {{ service.async_client_name }}:
527530
{% endif %}
528531

529532
try:
530-
_client_info = gapic_v1.client_info.ClientInfo(
533+
DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
531534
gapic_version=pkg_resources.get_distribution(
532535
'{{ api.naming.warehouse_package_name }}',
533536
).version,
534537
)
535538
except pkg_resources.DistributionNotFound:
536-
_client_info = gapic_v1.client_info.ClientInfo()
539+
DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo()
537540

538541

539542
__all__ = (

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

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ from google.iam.v1 import iam_policy_pb2 as iam_policy # type: ignore
2727
from google.iam.v1 import policy_pb2 as policy # type: ignore
2828
{% endif %}
2929
{% endfilter %}
30-
from .transports.base import {{ service.name }}Transport
30+
from .transports.base import {{ service.name }}Transport, DEFAULT_CLIENT_INFO
3131
from .transports.grpc import {{ service.grpc_transport_name }}
3232
from .transports.grpc_asyncio import {{ service.grpc_asyncio_transport_name }}
3333

@@ -141,6 +141,7 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
141141
credentials: credentials.Credentials = None,
142142
transport: Union[str, {{ service.name }}Transport] = None,
143143
client_options: ClientOptions = None,
144+
client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
144145
) -> None:
145146
"""Instantiate the {{ (service.client_name|snake_case).replace('_', ' ') }}.
146147

@@ -166,7 +167,12 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
166167
(2) The ``client_cert_source`` property is used to provide client
167168
SSL credentials for mutual TLS transport. If not provided, the
168169
default SSL credentials will be used if present.
169-
170+
client_info (google.api_core.gapic_v1.client_info.ClientInfo):
171+
The client info used to send a user-agent string along with
172+
API requests. If ``None``, then default info will be used.
173+
Generally, you only need to set this if you're developing
174+
your own client library.
175+
170176
Raises:
171177
google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
172178
creation failed for any reason.
@@ -219,6 +225,7 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
219225
api_mtls_endpoint=client_options.api_endpoint,
220226
client_cert_source=client_options.client_cert_source,
221227
quota_project_id=client_options.quota_project_id,
228+
client_info=client_info,
222229
)
223230

224231

@@ -471,7 +478,7 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
471478
rpc = gapic_v1.method.wrap_method(
472479
self._transport.set_iam_policy,
473480
default_timeout=None,
474-
client_info=_client_info,
481+
client_info=DEFAULT_CLIENT_INFO,
475482
)
476483

477484
# Certain fields should be provided within the metadata header;
@@ -578,7 +585,7 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
578585
rpc = gapic_v1.method.wrap_method(
579586
self._transport.get_iam_policy,
580587
default_timeout=None,
581-
client_info=_client_info,
588+
client_info=DEFAULT_CLIENT_INFO,
582589
)
583590

584591
# Certain fields should be provided within the metadata header;
@@ -629,7 +636,7 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
629636
rpc = gapic_v1.method.wrap_method(
630637
self._transport.test_iam_permissions,
631638
default_timeout=None,
632-
client_info=_client_info,
639+
client_info=DEFAULT_CLIENT_INFO,
633640
)
634641

635642
# Certain fields should be provided within the metadata header;
@@ -647,13 +654,13 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
647654

648655

649656
try:
650-
_client_info = gapic_v1.client_info.ClientInfo(
657+
DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
651658
gapic_version=pkg_resources.get_distribution(
652659
'{{ api.naming.warehouse_package_name }}',
653660
).version,
654661
)
655662
except pkg_resources.DistributionNotFound:
656-
_client_info = gapic_v1.client_info.ClientInfo()
663+
DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo()
657664

658665

659666
__all__ = (

0 commit comments

Comments
 (0)