Skip to content

Commit cc47ba6

Browse files
committed
added token-level deduplication
1 parent 049f10b commit cc47ba6

2 files changed

Lines changed: 67 additions & 8 deletions

File tree

packages/google-api-core/google/api_core/gapic_v1/method.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,25 @@ def _apply_decorators(func, decorators):
5858
return func
5959

6060

61+
def _deduplicate_metadata_tokens(*headers: str) -> str:
62+
"""
63+
Given one or more metadata payload strings, create a combined
64+
string with deduplicated tokens, while preserving token order.
65+
66+
Inputs are expected contain a set of metadata tokens separated by spaces
67+
Example: `gl-python/3.14.0 grpc/1.76.0 gax/2.29.0 gapic/3.8.0 pb/6.33.4`
68+
69+
Args:
70+
*headers: one or more metadata payload strings
71+
72+
Returns:
73+
a single combined payload string
74+
"""
75+
# Split all non-empty headers into individual tokens
76+
token_list = " ".join(filter(None, headers)).split()
77+
# Deduplicate while preserving order
78+
return " ".join(dict.fromkeys(token_list))
79+
6180
def _extract_metrics_header(metadata) -> Tuple[str, List[Tuple[str, str]]]:
6281
"""Extract x-google-api-client header from metadata list.
6382
@@ -74,8 +93,9 @@ def _extract_metrics_header(metadata) -> Tuple[str, List[Tuple[str, str]]]:
7493

7594
key_to_find = client_info.METRICS_METADATA_KEY
7695

77-
metric_str = " ".join([v for k, v in metadata if k == key_to_find])
78-
96+
metric_str = _deduplicate_metadata_tokens(
97+
" ".join([v for k, v in metadata if k == key_to_find])
98+
)
7999
if not metric_str:
80100
return "", list(metadata)
81101

@@ -147,14 +167,15 @@ def __call__(
147167
# Apply all applicable decorators.
148168
wrapped_func = _apply_decorators(self._target, [retry, timeout])
149169

150-
# Add the user agent metadata to the call.
151170
if user_metadata := kwargs.get("metadata"):
171+
# Add the user agent metadata to the call.
152172
final_metadata = list(self._static_metadata)
153173
user_x_goog, remaining = _extract_metrics_header(user_metadata)
154-
api_client_tokens = [t for t in [user_x_goog, self._x_goog_api_client] if t]
155-
if api_client_tokens:
174+
175+
merged_header = _deduplicate_metadata_tokens(self._x_goog_api_client, user_x_goog)
176+
if merged_header:
156177
final_metadata.append(
157-
(client_info.METRICS_METADATA_KEY, " ".join(api_client_tokens))
178+
(client_info.METRICS_METADATA_KEY, merged_header)
158179
)
159180
final_metadata.extend(remaining)
160181
kwargs["metadata"] = final_metadata

packages/google-api-core/tests/unit/gapic/test_method.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,21 @@ def test_invoke_wrapped_method_with_metadata_as_none():
119119
assert len(metadata) == 1
120120

121121

122+
def test_extract_metrics_header_duplicate_tokens():
123+
metadata = [
124+
("x-goog-api-client", "token1 token2"),
125+
("x-goog-api-client", "token2 token3 token1"),
126+
("other-header", "value"),
127+
("x-goog-api-client", "token4 token2"),
128+
]
129+
130+
metric_str, arbitrary_metadata = google.api_core.gapic_v1.method._extract_metrics_header(metadata)
131+
132+
# Should maintain order of first appearance and eliminate duplicates
133+
assert metric_str == "token1 token2 token3 token4"
134+
assert arbitrary_metadata == [("other-header", "value")]
135+
136+
122137
def test_invoke_wrapped_method_with_duplicate_x_goog_api_client_metadata():
123138
method = mock.Mock(spec=["__call__"])
124139

@@ -134,11 +149,13 @@ def test_invoke_wrapped_method_with_duplicate_x_goog_api_client_metadata():
134149
method, client_info=client_info
135150
)
136151

137-
# Invoke the wrapped method with an explicit user-provided custom header
152+
# Invoke the wrapped method with an explicit user-provided custom header that contains duplicates
153+
# both within its own items and overlapping with the default client_info
138154
wrapped_method(
139155
mock.sentinel.request,
140156
metadata=[
141157
("x-goog-api-client", "override-client/2.0"),
158+
("x-goog-api-client", "override-client/2.0 grpc/1.76.0 custom-user-agent/1.0"),
142159
("other-header", "value"),
143160
],
144161
)
@@ -156,7 +173,7 @@ def test_invoke_wrapped_method_with_duplicate_x_goog_api_client_metadata():
156173
# Verify both the user-provided override value and the library system telemetry are merged explicitly
157174
assert (
158175
metadata_dict["x-goog-api-client"]
159-
== "override-client/2.0 custom-user-agent/1.0 gl-python/3.14.0 grpc/1.76.0 gax/2.29.0"
176+
== "custom-user-agent/1.0 gl-python/3.14.0 grpc/1.76.0 gax/2.29.0 override-client/2.0"
160177
)
161178

162179

@@ -289,3 +306,24 @@ def test_wrap_method_with_call_not_supported():
289306
with pytest.raises(ValueError) as exc_info:
290307
google.api_core.gapic_v1.method.wrap_method(method, with_call=True)
291308
assert "with_call=True is only supported for unary calls" in str(exc_info.value)
309+
310+
311+
@pytest.mark.parametrize(
312+
"headers,expected",
313+
[
314+
((), ""),
315+
(("",), ""),
316+
((None,), ""),
317+
(("", None, ""), ""),
318+
(("token1",), "token1"),
319+
(("token1 token1",), "token1"),
320+
(("token1", "token1"), "token1"),
321+
(("token1 token2 token1",), "token1 token2"),
322+
(("token1", "token2", "token1"), "token1 token2"),
323+
(("token1 token2", "token2 token3"), "token1 token2 token3"),
324+
(("token1", None, "token2", "", "token1"), "token1 token2"),
325+
],
326+
)
327+
def test__deduplicate_metadata_tokens(headers, expected):
328+
dedup = google.api_core.gapic_v1.method._deduplicate_metadata_tokens
329+
assert dedup(*headers) == expected

0 commit comments

Comments
 (0)