From 256f8712b6b08488c2f7c07d533a195dcea472d9 Mon Sep 17 00:00:00 2001 From: rimkusaurimas Date: Thu, 9 Jul 2026 17:09:24 +0200 Subject: [PATCH 1/2] fix(preprocess): start the first preprocess run instead of crashing when the table has no status record yet (404) --- src/sourcerykit/handoff/_preprocess.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/sourcerykit/handoff/_preprocess.py b/src/sourcerykit/handoff/_preprocess.py index ff2e240..0ca40b8 100644 --- a/src/sourcerykit/handoff/_preprocess.py +++ b/src/sourcerykit/handoff/_preprocess.py @@ -3,6 +3,7 @@ from sourcerykit.bootstrap.bootstrap import get_bootstrap from sourcerykit.errors import SourceryKitBootstrapError from sourcerykit.logger import get_logger +from sourcerykit.provably._errors import ProvablyAPIError from sourcerykit.provably.service import service _log = get_logger(__name__) @@ -27,8 +28,14 @@ async def run_preprocess() -> None: _log.error("preprocess_failed_incomplete_bootstrap") raise SourceryKitBootstrapError("Provably bootstrap incomplete: middleware_id and table_id are required") - # 1. Check if preprocessing is already in progress - status = await service.get_preprocess_status_only(middleware_id, table_id) + # 1. Check if preprocessing is already in progress. A never-preprocessed table has no + # status record yet (404) — treat that as "not started" and start the first run. + try: + status = await service.get_preprocess_status_only(middleware_id, table_id) + except ProvablyAPIError as e: + if e.status_code != 404: + raise + status = "unknown" _log.info("preprocess_status_check", status=status) # 2. If in progress, wait for it to complete first From de7baf57fc76b1065660f406e300452bcb889295 Mon Sep 17 00:00:00 2001 From: SimoneBottoni Date: Thu, 9 Jul 2026 18:11:48 +0200 Subject: [PATCH 2/2] fix(errors): add ProvablyNotFoundError for handling 404 responses --- src/sourcerykit/handoff/_preprocess.py | 8 +++----- src/sourcerykit/provably/_errors.py | 24 +++++++++++++++++++----- tests/unit/test_errors.py | 19 +++++++++++++++++++ 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/sourcerykit/handoff/_preprocess.py b/src/sourcerykit/handoff/_preprocess.py index 0ca40b8..bc573fe 100644 --- a/src/sourcerykit/handoff/_preprocess.py +++ b/src/sourcerykit/handoff/_preprocess.py @@ -3,7 +3,7 @@ from sourcerykit.bootstrap.bootstrap import get_bootstrap from sourcerykit.errors import SourceryKitBootstrapError from sourcerykit.logger import get_logger -from sourcerykit.provably._errors import ProvablyAPIError +from sourcerykit.provably._errors import ProvablyNotFoundError from sourcerykit.provably.service import service _log = get_logger(__name__) @@ -29,12 +29,10 @@ async def run_preprocess() -> None: raise SourceryKitBootstrapError("Provably bootstrap incomplete: middleware_id and table_id are required") # 1. Check if preprocessing is already in progress. A never-preprocessed table has no - # status record yet (404) — treat that as "not started" and start the first run. + # status record yet — treat that as "not started" and start the first run. try: status = await service.get_preprocess_status_only(middleware_id, table_id) - except ProvablyAPIError as e: - if e.status_code != 404: - raise + except ProvablyNotFoundError: status = "unknown" _log.info("preprocess_status_check", status=status) diff --git a/src/sourcerykit/provably/_errors.py b/src/sourcerykit/provably/_errors.py index ce7568a..3247d75 100644 --- a/src/sourcerykit/provably/_errors.py +++ b/src/sourcerykit/provably/_errors.py @@ -54,6 +54,12 @@ class ProvablyUnauthorizedError(ProvablyAuthError): pass +class ProvablyNotFoundError(ProvablyAPIError): + """Raised when the requested resource does not exist (HTTP 404).""" + + pass + + @asynccontextmanager async def provably_auth_error_handler(service: str) -> AsyncIterator[None]: """ @@ -122,17 +128,25 @@ async def provably_error_handler(service: str) -> AsyncIterator[None]: try: yield except httpx.HTTPStatusError as e: + status = e.response.status_code + body = e.response.text # Log API failures _log.error( f"provably_api_rejected_{service}", - status_code=e.response.status_code, + status_code=status, path=str(e.request.url), - response=e.response.text[:500], + response=body[:500], ) + if status == 404: + raise ProvablyNotFoundError( + message=f"Resource not found for {service_name}: {body}", + status_code=status, + response_body=body, + ) from e raise ProvablyAPIError( - message=f"Provably API rejected {service_name}: {e.response.text}", - status_code=e.response.status_code, - response_body=e.response.text, + message=f"Provably API rejected {service_name}: {body}", + status_code=status, + response_body=body, ) from e except (ValueError, TypeError, KeyError) as e: diff --git a/tests/unit/test_errors.py b/tests/unit/test_errors.py index 68b980f..3d6e32a 100644 --- a/tests/unit/test_errors.py +++ b/tests/unit/test_errors.py @@ -10,6 +10,7 @@ ProvablyConnectionError, ProvablyDataError, ProvablyError, + ProvablyNotFoundError, ProvablyResourceAlreadyExistsError, ProvablyUnauthorizedError, provably_auth_error_handler, @@ -43,6 +44,13 @@ def test_api_error_status_code_defaults_to_none(self) -> None: err = ProvablyAPIError("bad request") assert err.status_code is None + def test_not_found_is_api_error(self) -> None: + assert issubclass(ProvablyNotFoundError, ProvablyAPIError) + + def test_not_found_stores_status_code(self) -> None: + err = ProvablyNotFoundError("not found", status_code=404, response_body="Not Found") + assert err.status_code == 404 + # --------------------------------------------------------------------------- # provably_error_handler @@ -66,6 +74,17 @@ async def test_http_status_error_raises_api_error(self) -> None: assert exc_info.value.status_code == 422 assert "run query" in str(exc_info.value).lower() + async def test_http_404_raises_not_found_error(self) -> None: + mock_request = httpx.Request("GET", "https://api.provably.ai/test") + mock_response = httpx.Response(404, request=mock_request, text="Not Found") + http_err = httpx.HTTPStatusError("404", request=mock_request, response=mock_response) + + with pytest.raises(ProvablyNotFoundError) as exc_info: + async with provably_error_handler("get_preprocess_status"): + raise http_err + + assert exc_info.value.status_code == 404 + async def test_value_error_raises_data_error(self) -> None: with pytest.raises(ProvablyDataError): async with provably_error_handler("get_collection"):