Skip to content

Commit 6251eab

Browse files
feat: add caching to routing header calculation (#526)
Co-authored-by: Anthonios Partheniou <partheniou@google.com>
1 parent 2c16868 commit 6251eab

File tree

2 files changed

+60
-13
lines changed

2 files changed

+60
-13
lines changed

google/api_core/gapic_v1/routing_header.py

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,45 +20,42 @@
2020
Generally, these headers are specified as gRPC metadata.
2121
"""
2222

23+
import functools
2324
from enum import Enum
2425
from urllib.parse import urlencode
2526

2627
ROUTING_METADATA_KEY = "x-goog-request-params"
28+
# This is the value for the `maxsize` argument of @functools.lru_cache
29+
# https://docs.python.org/3/library/functools.html#functools.lru_cache
30+
# This represents the number of recent function calls to store.
31+
ROUTING_PARAM_CACHE_SIZE = 32
2732

2833

2934
def to_routing_header(params, qualified_enums=True):
3035
"""Returns a routing header string for the given request parameters.
3136
3237
Args:
33-
params (Mapping[str, Any]): A dictionary containing the request
38+
params (Mapping[str, str | bytes | Enum]): A dictionary containing the request
3439
parameters used for routing.
3540
qualified_enums (bool): Whether to represent enum values
3641
as their type-qualified symbol names instead of as their
3742
unqualified symbol names.
3843
3944
Returns:
4045
str: The routing header string.
41-
4246
"""
47+
tuples = params.items() if isinstance(params, dict) else params
4348
if not qualified_enums:
44-
if isinstance(params, dict):
45-
tuples = params.items()
46-
else:
47-
tuples = params
48-
params = [(x[0], x[1].name) if isinstance(x[1], Enum) else x for x in tuples]
49-
return urlencode(
50-
params,
51-
# Per Google API policy (go/api-url-encoding), / is not encoded.
52-
safe="/",
53-
)
49+
tuples = [(x[0], x[1].name) if isinstance(x[1], Enum) else x for x in tuples]
50+
return "&".join([_urlencode_param(*t) for t in tuples])
5451

5552

5653
def to_grpc_metadata(params, qualified_enums=True):
5754
"""Returns the gRPC metadata containing the routing headers for the given
5855
request parameters.
5956
6057
Args:
61-
params (Mapping[str, Any]): A dictionary containing the request
58+
params (Mapping[str, str | bytes | Enum]): A dictionary containing the request
6259
parameters used for routing.
6360
qualified_enums (bool): Whether to represent enum values
6461
as their type-qualified symbol names instead of as their
@@ -69,3 +66,22 @@ def to_grpc_metadata(params, qualified_enums=True):
6966
and value.
7067
"""
7168
return (ROUTING_METADATA_KEY, to_routing_header(params, qualified_enums))
69+
70+
71+
# use caching to avoid repeated computation
72+
@functools.lru_cache(maxsize=ROUTING_PARAM_CACHE_SIZE)
73+
def _urlencode_param(key, value):
74+
"""Cacheable wrapper over urlencode
75+
76+
Args:
77+
key (str): The key of the parameter to encode.
78+
value (str | bytes | Enum): The value of the parameter to encode.
79+
80+
Returns:
81+
str: The encoded parameter.
82+
"""
83+
return urlencode(
84+
{key: value},
85+
# Per Google API policy (go/api-url-encoding), / is not encoded.
86+
safe="/",
87+
)

tests/unit/gapic/test_routing_header.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,34 @@ def test_to_grpc_metadata():
7070
params = [("name", "meep"), ("book.read", "1")]
7171
metadata = routing_header.to_grpc_metadata(params)
7272
assert metadata == (routing_header.ROUTING_METADATA_KEY, "name=meep&book.read=1")
73+
74+
75+
@pytest.mark.parametrize(
76+
"key,value,expected",
77+
[
78+
("book.read", "1", "book.read=1"),
79+
("name", "me/ep", "name=me/ep"),
80+
("\\", "=", "%5C=%3D"),
81+
(b"hello", "world", "hello=world"),
82+
("✔️", "✌️", "%E2%9C%94%EF%B8%8F=%E2%9C%8C%EF%B8%8F"),
83+
],
84+
)
85+
def test__urlencode_param(key, value, expected):
86+
result = routing_header._urlencode_param(key, value)
87+
assert result == expected
88+
89+
90+
def test__urlencode_param_caching_performance():
91+
import time
92+
93+
key = "key" * 100
94+
value = "value" * 100
95+
# time with empty cache
96+
start_time = time.perf_counter()
97+
routing_header._urlencode_param(key, value)
98+
duration = time.perf_counter() - start_time
99+
second_start_time = time.perf_counter()
100+
routing_header._urlencode_param(key, value)
101+
second_duration = time.perf_counter() - second_start_time
102+
# second call should be approximately 10 times faster
103+
assert second_duration < duration / 10

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy