fix(cloud_api): restore legacy is_retrieval_ready swallow-on-error contract

For faithful 0.2.x cloud SDK drop-in compatibility, is_retrieval_ready
again swallows PageIndexAPIError and returns False (instead of raising),
so existing `while not is_retrieval_ready(...)` polling loops behave
exactly as before. Documented that this can loop forever on a permanent
error — that is the legacy contract; callers guard their own loops.

(The new SDK's own indexing path doesn't use this method — it polls
document status with a bounded 120-attempt cap — so the infinite-loop
risk is confined to legacy-SDK usage that already had it.)

Test updated to assert the swallow behavior.

Claude-Session: https://claude.ai/code/session_01Kx5DgKbhK1N8autqXH8SmS
This commit is contained in:
mountain 2026-07-07 15:48:49 +08:00
parent b2756c73d2
commit 154c483fb1
2 changed files with 14 additions and 11 deletions

View file

@ -93,13 +93,17 @@ class LegacyCloudAPI:
def is_retrieval_ready(self, doc_id: str) -> bool: def is_retrieval_ready(self, doc_id: str) -> bool:
"""Return whether retrieval is ready for ``doc_id``. """Return whether retrieval is ready for ``doc_id``.
API failures (revoked key, network down, unknown doc) propagate as Faithfully matches the 0.2.x cloud SDK: API errors are swallowed and
PageIndexAPIError instead of reading as "not ready" swallowing them reported as "not ready" (False) so existing
turned ``while not is_retrieval_ready(...)`` polling loops into ``while not is_retrieval_ready(...)`` polling loops behave identically.
infinite loops. Note this can loop forever on a permanent error (revoked key, deleted
doc) that is the legacy contract; guard the loop yourself if needed.
""" """
result = self.get_tree(doc_id) try:
return result.get("retrieval_ready", False) result = self.get_tree(doc_id)
return result.get("retrieval_ready", False)
except PageIndexAPIError:
return False
def submit_query(self, doc_id: str, query: str, thinking: bool = False) -> dict[str, Any]: def submit_query(self, doc_id: str, query: str, thinking: bool = False) -> dict[str, Any]:
payload = { payload = {

View file

@ -326,15 +326,14 @@ def test_empty_api_key_warns_and_falls_back_to_local(caplog, tmp_path, monkeypat
assert client._legacy_cloud_api is None assert client._legacy_cloud_api is None
def test_is_retrieval_ready_propagates_api_errors(monkeypatch): def test_is_retrieval_ready_swallows_errors_like_legacy_sdk(monkeypatch):
"""Regression: API failures (revoked key etc.) were swallowed as False, """Faithful 0.2.x contract: API errors are swallowed and reported as
turning `while not is_retrieval_ready(...)` into an infinite poll.""" "not ready" (False), so existing polling loops behave identically."""
def fake_request(method, url, **kwargs): def fake_request(method, url, **kwargs):
return FakeResponse(status_code=401, text="invalid api key") return FakeResponse(status_code=401, text="invalid api key")
monkeypatch.setattr("pageindex.cloud_api.requests.request", fake_request) monkeypatch.setattr("pageindex.cloud_api.requests.request", fake_request)
with pytest.raises(PageIndexAPIError): assert PageIndexClient("pi-test").is_retrieval_ready("doc-1") is False
PageIndexClient("pi-test").is_retrieval_ready("doc-1")
def test_legacy_urls_encode_special_char_ids(monkeypatch): def test_legacy_urls_encode_special_char_ids(monkeypatch):