Skip to content

Commit e35f3fb

Browse files
committed
Use the current highwater mark for config fetches
1 parent 806c744 commit e35f3fb

File tree

3 files changed

+47
-19
lines changed

3 files changed

+47
-19
lines changed

prefab_cloud_python/_requests.py

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -73,15 +73,18 @@ def __next__(self):
7373
self.index = (self.index + 1) % len(self.hosts)
7474
return host
7575

76+
7677
# --- Simple LRU Cache Implementation ---
7778

79+
7880
@dataclass
7981
class CacheEntry:
8082
data: bytes
8183
etag: str
8284
expires_at: float
8385
url: str # The full URL from the successful response
8486

87+
8588
class LRUCache:
8689
def __init__(self, max_size: int):
8790
self.max_size = max_size
@@ -120,9 +123,11 @@ def __init__(self, options):
120123
self.session = requests.Session()
121124
self.session.mount("https://", requests.adapters.HTTPAdapter())
122125
self.session.mount("http://", requests.adapters.HTTPAdapter())
123-
self.session.headers.update({
124-
"X-PrefabCloud-Client-Version": f"prefab-cloud-python-{getattr(options, 'version', 'development')}"
125-
})
126+
self.session.headers.update(
127+
{
128+
"X-PrefabCloud-Client-Version": f"prefab-cloud-python-{getattr(options, 'version', 'development')}"
129+
}
130+
)
126131
# Initialize a cache (here with a maximum of 2 entries).
127132
self.cache = LRUCache(max_size=2)
128133

@@ -189,12 +194,15 @@ def _update_cache(self, url: str, response: Response) -> None:
189194
max_age = int(m.group(1))
190195
expires_at = time.time() + max_age if max_age > 0 else 0
191196
if (etag is not None or max_age > 0) and expires_at > time.time():
192-
self.cache.set(url, CacheEntry(
193-
data=response.content,
194-
etag=etag,
195-
expires_at=expires_at,
196-
url=response.url
197-
))
197+
self.cache.set(
198+
url,
199+
CacheEntry(
200+
data=response.content,
201+
etag=etag,
202+
expires_at=expires_at,
203+
url=response.url,
204+
),
205+
)
198206
response.headers["X-Cache"] = "MISS"
199207

200208
def _send_request(self, method: str, url: str, **kwargs) -> Response:
@@ -208,7 +216,14 @@ def _send_request(self, method: str, url: str, **kwargs) -> Response:
208216
wait=wait_exponential(multiplier=1, min=0.05, max=2),
209217
retry=retry_if_exception_type((RequestException, ConnectionError, OSError)),
210218
)
211-
def resilient_request(self, path, method="GET", allow_cache: bool = False, hosts: list[str] = None, **kwargs) -> Response:
219+
def resilient_request(
220+
self,
221+
path,
222+
method="GET",
223+
allow_cache: bool = False,
224+
hosts: list[str] = None,
225+
**kwargs,
226+
) -> Response:
212227
"""
213228
Makes a resilient (retrying) request.
214229
@@ -237,4 +252,4 @@ def resilient_request(self, path, method="GET", allow_cache: bool = False, hosts
237252
resp.url = cached.url
238253
return resp
239254
self._update_cache(url, response)
240-
return response
255+
return response

prefab_cloud_python/config_client.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,12 @@ def load_initial_data(self):
157157

158158
def load_checkpoint_from_api_cdn(self):
159159
try:
160+
hwm = self.config_loader.highwater_mark
160161
response = self.api_client.resilient_request(
161-
"/api/v1/configs/0", auth=("authuser", self.options.api_key), timeout=4, allow_cache=True
162+
"/api/v1/configs/" + str(hwm),
163+
auth=("authuser", self.options.api_key),
164+
timeout=4,
165+
allow_cache=True,
162166
)
163167
if response.ok:
164168
configs = Prefab.Configs.FromString(response.content)

tests/test_api_client.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
from requests import Response
44
from prefab_cloud_python._requests import ApiClient
55

6+
67
# Dummy options for testing.
78
class DummyOptions:
89
prefab_api_urls = ["https://a.example.com", "https://b.example.com"]
910
version = "1.0"
1011

12+
1113
class TestApiClient(unittest.TestCase):
1214
def setUp(self):
1315
self.options = DummyOptions()
@@ -16,7 +18,13 @@ def setUp(self):
1618
# patch _get_attempt_number to always return 1.
1719
self.client._get_attempt_number = lambda: 1
1820

19-
def create_response(self, status_code=200, content=b"dummy", headers=None, url="https://a.example.com/api/v1/configs/0"):
21+
def create_response(
22+
self,
23+
status_code=200,
24+
content=b"dummy",
25+
headers=None,
26+
url="https://a.example.com/api/v1/configs/0",
27+
):
2028
resp = Response()
2129
resp.status_code = status_code
2230
resp._content = content
@@ -31,7 +39,7 @@ def test_no_cache(self, mock_send_request):
3139
status_code=200,
3240
content=b"response_no_cache",
3341
headers={"Cache-Control": "max-age=60", "ETag": "abc"},
34-
url="https://a.example.com/api/v1/configs/0"
42+
url="https://a.example.com/api/v1/configs/0",
3543
)
3644
mock_send_request.return_value = response
3745

@@ -47,7 +55,7 @@ def test_cache_miss_and_hit(self, mock_send_request):
4755
status_code=200,
4856
content=b"cached_response",
4957
headers={"Cache-Control": "max-age=60", "ETag": "abc"},
50-
url="https://a.example.com/api/v1/configs/0"
58+
url="https://a.example.com/api/v1/configs/0",
5159
)
5260
mock_send_request.return_value = response
5361

@@ -61,7 +69,7 @@ def test_cache_miss_and_hit(self, mock_send_request):
6169
status_code=200,
6270
content=b"new_response",
6371
headers={"Cache-Control": "max-age=60", "ETag": "def"},
64-
url="https://a.example.com/api/v1/configs/0"
72+
url="https://a.example.com/api/v1/configs/0",
6573
)
6674
mock_send_request.return_value = new_response
6775

@@ -78,7 +86,7 @@ def test_304_returns_cached_response(self, mock_send_request):
7886
status_code=200,
7987
content=b"cached_response",
8088
headers={"Cache-Control": "max-age=60", "ETag": "abc"},
81-
url="https://a.example.com/api/v1/configs/0"
89+
url="https://a.example.com/api/v1/configs/0",
8290
)
8391
mock_send_request.return_value = response
8492
resp1 = self.client.resilient_request("/api/v1/configs/0", allow_cache=True)
@@ -91,13 +99,14 @@ def test_304_returns_cached_response(self, mock_send_request):
9199
status_code=304,
92100
content=b"",
93101
headers={},
94-
url="https://a.example.com/api/v1/configs/0"
102+
url="https://a.example.com/api/v1/configs/0",
95103
)
96104
mock_send_request.return_value = response_304
97105
resp2 = self.client.resilient_request("/api/v1/configs/0", allow_cache=True)
98106
self.assertEqual(resp2.status_code, 200)
99107
self.assertEqual(resp2.content, b"cached_response")
100108
self.assertEqual(resp2.headers.get("X-Cache"), "HIT")
101109

110+
102111
if __name__ == "__main__":
103-
unittest.main()
112+
unittest.main()

0 commit comments

Comments
 (0)