Phase 5: ESI Expires caching, error-limit back-off, setup docs (#16)
EsiClient now caches GET responses honoring the Expires header and backs off when the rolling ESI error window is nearly exhausted (X-Esi-Error-Limit-*). README gains Setup / Connect-to-Claude / enabling-writes sections. Closes #16. SDE bulk-load split out to its own follow-up (#51). 135 tests, coverage 90%. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
3 files changed
+146
-2
No files matched your search
@@ -8,6 +8,54 @@ import pytest
|
||||
from familiar import esi
|
||||
|
||||
|
||||
_FUTURE = "Wed, 21 Oct 2099 07:28:00 GMT"
|
||||
|
||||
|
||||
def test_get_caches_when_expires_present(mock_esi):
|
||||
calls = {"n": 0}
|
||||
|
||||
def handler(req):
|
||||
calls["n"] += 1
|
||||
return httpx.Response(200, json={"v": 1}, headers={"Expires": _FUTURE})
|
||||
|
||||
mock_esi(handler)
|
||||
first = asyncio.run(esi.client().get("/x/"))
|
||||
second = asyncio.run(esi.client().get("/x/"))
|
||||
assert first == second == {"v": 1}
|
||||
assert calls["n"] == 1 # second served from cache
|
||||
|
||||
|
||||
def test_no_cache_without_expires(mock_esi):
|
||||
calls = {"n": 0}
|
||||
|
||||
def handler(req):
|
||||
calls["n"] += 1
|
||||
return httpx.Response(200, json={"v": 1})
|
||||
|
||||
mock_esi(handler)
|
||||
asyncio.run(esi.client().get("/y/"))
|
||||
asyncio.run(esi.client().get("/y/"))
|
||||
assert calls["n"] == 2
|
||||
|
||||
|
||||
def test_error_limit_triggers_backoff(mock_esi, monkeypatch):
|
||||
slept = []
|
||||
|
||||
async def fake_sleep(s):
|
||||
slept.append(s)
|
||||
|
||||
monkeypatch.setattr(esi, "_sleep", fake_sleep)
|
||||
mock_esi(
|
||||
lambda req: httpx.Response(
|
||||
200, json={}, headers={"X-Esi-Error-Limit-Remain": "1", "X-Esi-Error-Limit-Reset": "3"}
|
||||
)
|
||||
)
|
||||
c = esi.client()
|
||||
asyncio.run(c.get("/a/")) # records remain=1
|
||||
asyncio.run(c.get("/b/")) # next request backs off before firing
|
||||
assert slept and slept[0] > 0
|
||||
|
||||
|
||||
def test_get_raises_esi_error_on_http_error(mock_esi):
|
||||
mock_esi(lambda req: httpx.Response(404, json={"error": "not found"}))
|
||||
with pytest.raises(esi.EsiError) as exc:
|
||||
|
||||
Reference in new issue
Block a user