evalstate HF Staff commited on
Commit
0316aff
·
verified ·
1 Parent(s): c2a3de5

Deploy OpenClaw PR API

Browse files
src/slop_farmer/app/pr_search_api.py CHANGED
@@ -10,7 +10,7 @@ from fastapi import FastAPI, HTTPException, Request
10
  from fastapi.responses import JSONResponse
11
 
12
  from slop_farmer.config import PrSearchRefreshOptions
13
- from slop_farmer.data.ghreplica_api import GhrProbeClient
14
  from slop_farmer.reports.pr_search_service import (
15
  get_pr_search_candidate_clusters,
16
  get_pr_search_cluster,
@@ -98,6 +98,12 @@ def create_app(settings: PrSearchApiSettings | None = None) -> FastAPI:
98
  status_code = 404 if _looks_not_found(exc) else 400
99
  return JSONResponse({"detail": str(exc)}, status_code=status_code)
100
 
 
 
 
 
 
 
101
  @app.get("/healthz")
102
  async def healthz() -> dict[str, bool]:
103
  return {"ok": True}
 
10
  from fastapi.responses import JSONResponse
11
 
12
  from slop_farmer.config import PrSearchRefreshOptions
13
+ from slop_farmer.data.ghreplica_api import GhReplicaProbeUnavailableError, GhrProbeClient
14
  from slop_farmer.reports.pr_search_service import (
15
  get_pr_search_candidate_clusters,
16
  get_pr_search_cluster,
 
98
  status_code = 404 if _looks_not_found(exc) else 400
99
  return JSONResponse({"detail": str(exc)}, status_code=status_code)
100
 
101
+ @app.exception_handler(GhReplicaProbeUnavailableError)
102
+ async def handle_probe_unavailable(
103
+ _request: Request, exc: GhReplicaProbeUnavailableError
104
+ ) -> JSONResponse:
105
+ return JSONResponse({"detail": str(exc)}, status_code=exc.status_code)
106
+
107
  @app.get("/healthz")
108
  async def healthz() -> dict[str, bool]:
109
  return {"ok": True}
src/slop_farmer/data/ghreplica_api.py CHANGED
@@ -19,6 +19,14 @@ class GhReplicaApiRequestError(RuntimeError):
19
  super().__init__(f"ghreplica API request failed: {status_code} {path} {detail}")
20
 
21
 
 
 
 
 
 
 
 
 
22
  class GhrProbeClient:
23
  provider = "ghreplica"
24
 
@@ -52,14 +60,54 @@ class GhrProbeClient:
52
  raise GhReplicaApiRequestError(exc.code, path, detail) from exc
53
  return json.loads(payload)
54
 
 
 
 
 
 
 
 
 
55
  def get_pull_request(self, owner: str, repo: str, number: int) -> dict[str, Any]:
56
- payload = self._request_json(f"/v1/github/repos/{owner}/{repo}/pulls/{number}")
 
 
 
 
 
 
 
 
57
  if not isinstance(payload, dict):
58
  raise RuntimeError(f"Expected dict payload for pull request, got {type(payload)!r}")
59
  return payload
60
 
61
  def iter_pull_files(self, owner: str, repo: str, number: int) -> Iterable[dict[str, Any]]:
62
- payload = self._request_json(f"/v1/changes/repos/{owner}/{repo}/pulls/{number}/files")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  rows = payload if isinstance(payload, list) else payload.get("files")
64
  if not isinstance(rows, list):
65
  raise RuntimeError(
@@ -85,7 +133,9 @@ class GhrProbeClient:
85
  }
86
 
87
  def get_pull_request_status(self, owner: str, repo: str, number: int) -> dict[str, Any] | None:
88
- payload = self._request_json(f"/v1/changes/repos/{owner}/{repo}/pulls/{number}/status")
 
 
89
  if payload is None:
90
  return None
91
  if not isinstance(payload, dict):
 
19
  super().__init__(f"ghreplica API request failed: {status_code} {path} {detail}")
20
 
21
 
22
+ class GhReplicaProbeUnavailableError(RuntimeError):
23
+ """Raised when ghreplica cannot yet serve a live probe payload."""
24
+
25
+ def __init__(self, detail: str, *, status_code: int = 503):
26
+ self.status_code = status_code
27
+ super().__init__(detail)
28
+
29
+
30
  class GhrProbeClient:
31
  provider = "ghreplica"
32
 
 
60
  raise GhReplicaApiRequestError(exc.code, path, detail) from exc
61
  return json.loads(payload)
62
 
63
+ def _request_json_or_none(self, path: str) -> Any | None:
64
+ try:
65
+ return self._request_json(path)
66
+ except GhReplicaApiRequestError as exc:
67
+ if exc.status_code == 404:
68
+ return None
69
+ raise
70
+
71
  def get_pull_request(self, owner: str, repo: str, number: int) -> dict[str, Any]:
72
+ try:
73
+ payload = self._request_json(f"/v1/github/repos/{owner}/{repo}/pulls/{number}")
74
+ except GhReplicaApiRequestError as exc:
75
+ if exc.status_code == 404:
76
+ raise GhReplicaProbeUnavailableError(
77
+ f"PR #{number} was not found in ghreplica.",
78
+ status_code=404,
79
+ ) from exc
80
+ raise
81
  if not isinstance(payload, dict):
82
  raise RuntimeError(f"Expected dict payload for pull request, got {type(payload)!r}")
83
  return payload
84
 
85
  def iter_pull_files(self, owner: str, repo: str, number: int) -> Iterable[dict[str, Any]]:
86
+ try:
87
+ payload = self._request_json(f"/v1/changes/repos/{owner}/{repo}/pulls/{number}/files")
88
+ except GhReplicaApiRequestError as exc:
89
+ if exc.status_code != 404:
90
+ raise
91
+ status = self.get_pull_request_status(owner, repo, number)
92
+ if isinstance(status, dict):
93
+ detail_bits = []
94
+ for key in (
95
+ "indexed",
96
+ "backfill_in_progress",
97
+ "changed_files",
98
+ "indexed_file_count",
99
+ ):
100
+ if key in status:
101
+ detail_bits.append(f"{key}={status[key]}")
102
+ suffix = f" ({', '.join(detail_bits)})" if detail_bits else ""
103
+ raise GhReplicaProbeUnavailableError(
104
+ f"PR #{number} is not available in ghreplica yet{suffix}.",
105
+ status_code=503,
106
+ ) from exc
107
+ raise GhReplicaProbeUnavailableError(
108
+ f"PR #{number} was not found in ghreplica changed-file replica.",
109
+ status_code=404,
110
+ ) from exc
111
  rows = payload if isinstance(payload, list) else payload.get("files")
112
  if not isinstance(rows, list):
113
  raise RuntimeError(
 
133
  }
134
 
135
  def get_pull_request_status(self, owner: str, repo: str, number: int) -> dict[str, Any] | None:
136
+ payload = self._request_json_or_none(
137
+ f"/v1/changes/repos/{owner}/{repo}/pulls/{number}/status"
138
+ )
139
  if payload is None:
140
  return None
141
  if not isinstance(payload, dict):