Skip to content

Commit b74d2a8

Browse files
fix: client_info default values (#681)
1 parent b1d7d29 commit b74d2a8

File tree

6 files changed

+147
-3
lines changed

6 files changed

+147
-3
lines changed

google/cloud/logging_v2/_gapic.py

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
from google.cloud.logging_v2.sink import Sink
3636
from google.cloud.logging_v2.metric import Metric
3737

38+
from google.api_core import client_info
39+
from google.api_core import gapic_v1
40+
3841

3942
class _LoggingAPI(object):
4043
"""Helper mapping logging-related APIs."""
@@ -562,6 +565,22 @@ def _log_entry_mapping_to_pb(mapping):
562565
return LogEntryPB(entry_pb)
563566

564567

568+
def _client_info_to_gapic(input_info):
569+
"""
570+
Helper function to convert api_core.client_info to
571+
api_core.gapic_v1.client_info subclass
572+
"""
573+
return gapic_v1.client_info.ClientInfo(
574+
python_version=input_info.python_version,
575+
grpc_version=input_info.grpc_version,
576+
api_core_version=input_info.api_core_version,
577+
gapic_version=input_info.gapic_version,
578+
client_library_version=input_info.client_library_version,
579+
user_agent=input_info.user_agent,
580+
rest_version=input_info.rest_version,
581+
)
582+
583+
565584
def make_logging_api(client):
566585
"""Create an instance of the Logging API adapter.
567586
@@ -572,9 +591,14 @@ def make_logging_api(client):
572591
Returns:
573592
_LoggingAPI: A metrics API instance with the proper credentials.
574593
"""
594+
info = client._client_info
595+
if type(info) == client_info.ClientInfo:
596+
# convert into gapic-compatible subclass
597+
info = _client_info_to_gapic(info)
598+
575599
generated = LoggingServiceV2Client(
576600
credentials=client._credentials,
577-
client_info=client._client_info,
601+
client_info=info,
578602
client_options=client._client_options,
579603
)
580604
return _LoggingAPI(generated, client)
@@ -590,9 +614,14 @@ def make_metrics_api(client):
590614
Returns:
591615
_MetricsAPI: A metrics API instance with the proper credentials.
592616
"""
617+
info = client._client_info
618+
if type(info) == client_info.ClientInfo:
619+
# convert into gapic-compatible subclass
620+
info = _client_info_to_gapic(info)
621+
593622
generated = MetricsServiceV2Client(
594623
credentials=client._credentials,
595-
client_info=client._client_info,
624+
client_info=info,
596625
client_options=client._client_options,
597626
)
598627
return _MetricsAPI(generated, client)
@@ -608,9 +637,14 @@ def make_sinks_api(client):
608637
Returns:
609638
_SinksAPI: A metrics API instance with the proper credentials.
610639
"""
640+
info = client._client_info
641+
if type(info) == client_info.ClientInfo:
642+
# convert into gapic-compatible subclass
643+
info = _client_info_to_gapic(info)
644+
611645
generated = ConfigServiceV2Client(
612646
credentials=client._credentials,
613-
client_info=client._client_info,
647+
client_info=info,
614648
client_options=client._client_options,
615649
)
616650
return _SinksAPI(generated, client)

google/cloud/logging_v2/client.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ def __init__(
137137
kw_args["api_endpoint"] = api_endpoint
138138

139139
self._connection = Connection(self, **kw_args)
140+
if client_info is None:
141+
# if client info not passed in, use the discovered
142+
# client info from _connection object
143+
client_info = self._connection._client_info
140144

141145
self._client_info = client_info
142146
self._client_options = client_options

tests/unit/gapic/logging_v2/test_config_service_v2.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,22 @@ def test__get_default_mtls_endpoint():
9999
)
100100

101101

102+
def test_config_default_client_info_headers():
103+
import re
104+
import pkg_resources
105+
106+
# test that DEFAULT_CLIENT_INFO contains the expected gapic headers
107+
gapic_header_regex = re.compile(
108+
r"gapic\/[0-9]+\.[\w.-]+ gax\/[0-9]+\.[\w.-]+ gl-python\/[0-9]+\.[\w.-]+ grpc\/[0-9]+\.[\w.-]+"
109+
)
110+
detected_info = (
111+
google.cloud.logging_v2.services.config_service_v2.transports.base.DEFAULT_CLIENT_INFO
112+
)
113+
assert detected_info is not None
114+
detected_agent = " ".join(sorted(detected_info.to_user_agent().split(" ")))
115+
assert gapic_header_regex.match(detected_agent)
116+
117+
102118
@pytest.mark.parametrize(
103119
"client_class,transport_name",
104120
[

tests/unit/gapic/logging_v2/test_logging_service_v2.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,22 @@ def modify_default_endpoint(client):
7171
)
7272

7373

74+
def test_logging_default_client_info_headers():
75+
import re
76+
import pkg_resources
77+
78+
# test that DEFAULT_CLIENT_INFO contains the expected gapic headers
79+
gapic_header_regex = re.compile(
80+
r"gapic\/[0-9]+\.[\w.-]+ gax\/[0-9]+\.[\w.-]+ gl-python\/[0-9]+\.[\w.-]+ grpc\/[0-9]+\.[\w.-]+"
81+
)
82+
detected_info = (
83+
google.cloud.logging_v2.services.logging_service_v2.transports.base.DEFAULT_CLIENT_INFO
84+
)
85+
assert detected_info is not None
86+
detected_agent = " ".join(sorted(detected_info.to_user_agent().split(" ")))
87+
assert gapic_header_regex.match(detected_agent)
88+
89+
7490
def test__get_default_mtls_endpoint():
7591
api_endpoint = "example.googleapis.com"
7692
api_mtls_endpoint = "example.mtls.googleapis.com"

tests/unit/gapic/logging_v2/test_metrics_service_v2.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,22 @@ def test__get_default_mtls_endpoint():
9999
)
100100

101101

102+
def test_metrics_default_client_info_headers():
103+
import re
104+
import pkg_resources
105+
106+
# test that DEFAULT_CLIENT_INFO contains the expected gapic headers
107+
gapic_header_regex = re.compile(
108+
r"gapic\/[0-9]+\.[\w.-]+ gax\/[0-9]+\.[\w.-]+ gl-python\/[0-9]+\.[\w.-]+ grpc\/[0-9]+\.[\w.-]+"
109+
)
110+
detected_info = (
111+
google.cloud.logging_v2.services.metrics_service_v2.transports.base.DEFAULT_CLIENT_INFO
112+
)
113+
assert detected_info is not None
114+
detected_agent = " ".join(sorted(detected_info.to_user_agent().split(" ")))
115+
assert gapic_header_regex.match(detected_agent)
116+
117+
102118
@pytest.mark.parametrize(
103119
"client_class,transport_name",
104120
[

tests/unit/test_client.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,16 @@
1616
from datetime import datetime
1717
from datetime import timedelta
1818
from datetime import timezone
19+
import re
1920

2021
import unittest
2122

2223
import mock
2324

25+
VENEER_HEADER_REGEX = re.compile(
26+
r"gapic\/[0-9]+\.[\w.-]+ gax\/[0-9]+\.[\w.-]+ gccl\/[0-9]+\.[\w.-]+ gl-python\/[0-9]+\.[\w.-]+ grpc\/[0-9]+\.[\w.-]+"
27+
)
28+
2429

2530
def _make_credentials():
2631
import google.auth.credentials
@@ -148,6 +153,59 @@ def make_api(client_obj):
148153
again = client.logging_api
149154
self.assertIs(again, api)
150155

156+
def test_veneer_grpc_headers(self):
157+
# test that client APIs have client_info populated with the expected veneer headers
158+
# required for proper instrumentation
159+
creds = _make_credentials()
160+
# ensure client info is set on client object
161+
client = self._make_one(project=self.PROJECT, credentials=creds, _use_grpc=True)
162+
self.assertIsNotNone(client._client_info)
163+
user_agent_sorted = " ".join(
164+
sorted(client._client_info.to_user_agent().split(" "))
165+
)
166+
self.assertTrue(VENEER_HEADER_REGEX.match(user_agent_sorted))
167+
# ensure client info is propagated to gapic wrapped methods
168+
patch = mock.patch("google.api_core.gapic_v1.method.wrap_method")
169+
with patch as gapic_mock:
170+
client.logging_api # initialize logging api
171+
client.metrics_api # initialize metrics api
172+
client.sinks_api # initialize sinks api
173+
wrapped_call_list = gapic_mock.call_args_list
174+
num_api_calls = 37 # expected number of distinct APIs in all gapic services (logging,metrics,sinks)
175+
self.assertGreaterEqual(
176+
len(wrapped_call_list),
177+
num_api_calls,
178+
"unexpected number of APIs wrapped",
179+
)
180+
for call in wrapped_call_list:
181+
client_info = call.kwargs["client_info"]
182+
self.assertIsNotNone(client_info)
183+
wrapped_user_agent_sorted = " ".join(
184+
sorted(client_info.to_user_agent().split(" "))
185+
)
186+
self.assertTrue(VENEER_HEADER_REGEX.match(wrapped_user_agent_sorted))
187+
188+
def test_veneer_http_headers(self):
189+
# test that http APIs have client_info populated with the expected veneer headers
190+
# required for proper instrumentation
191+
creds = _make_credentials()
192+
# ensure client info is set on client object
193+
client = self._make_one(
194+
project=self.PROJECT, credentials=creds, _use_grpc=False
195+
)
196+
self.assertIsNotNone(client._client_info)
197+
user_agent_sorted = " ".join(
198+
sorted(client._client_info.to_user_agent().split(" "))
199+
)
200+
self.assertTrue(VENEER_HEADER_REGEX.match(user_agent_sorted))
201+
# ensure client info is propagated to _connection object
202+
connection_user_agent = client._connection._client_info.to_user_agent()
203+
self.assertIsNotNone(connection_user_agent)
204+
connection_user_agent_sorted = " ".join(
205+
sorted(connection_user_agent.split(" "))
206+
)
207+
self.assertTrue(VENEER_HEADER_REGEX.match(connection_user_agent_sorted))
208+
151209
def test_no_gapic_ctor(self):
152210
from google.cloud.logging_v2._http import _LoggingAPI
153211

0 commit comments

Comments
 (0)