Skip to content

Commit 8987d89

Browse files
committed
Extract shared gateway response primitives
1 parent dd794ff commit 8987d89

2 files changed

Lines changed: 76 additions & 43 deletions

File tree

projects/policyengine-api-simulation/src/modal/gateway/endpoints.py

Lines changed: 12 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import modal
99
from fastapi import APIRouter, HTTPException
10-
from fastapi.responses import JSONResponse
1110

1211
from src.modal.budget_window_state import (
1312
build_batch_status_response,
@@ -27,6 +26,11 @@
2726
PolicyEngineBundle,
2827
SimulationRequest,
2928
)
29+
from src.modal.gateway.responses import (
30+
batch_status_response,
31+
failed_job_response,
32+
running_job_response,
33+
)
3034

3135
logger = logging.getLogger(__name__)
3236

@@ -82,16 +86,6 @@ def _serialize_job_metadata(
8286
}
8387

8488

85-
def _batch_status_payload(response: BudgetWindowBatchStatusResponse) -> dict:
86-
payload = response.model_dump(mode="json")
87-
if response.policyengine_bundle is not None:
88-
payload["policyengine_bundle"] = response.policyengine_bundle.model_dump(
89-
mode="json",
90-
exclude_none=True,
91-
)
92-
return payload
93-
94-
9589
def _build_budget_window_parent_payload(
9690
request: BudgetWindowBatchRequest,
9791
*,
@@ -114,15 +108,6 @@ def _build_budget_window_parent_payload(
114108
return payload
115109

116110

117-
def _json_batch_status_response(response: BudgetWindowBatchStatusResponse):
118-
payload = _batch_status_payload(response)
119-
if response.status in {"submitted", "running"}:
120-
return JSONResponse(status_code=202, content=payload)
121-
if response.status == "failed":
122-
return JSONResponse(status_code=500, content=payload)
123-
return response
124-
125-
126111
def get_app_name(country: str, version: Optional[str]) -> tuple[str, str]:
127112
"""
128113
Resolve country + version to Modal app name.
@@ -286,25 +271,9 @@ async def get_job_status(job_id: str):
286271
status="complete", result=result, **(job_metadata or {})
287272
)
288273
except TimeoutError:
289-
return JSONResponse(
290-
status_code=202,
291-
content={
292-
"status": "running",
293-
"result": None,
294-
"error": None,
295-
**(job_metadata or {}),
296-
},
297-
)
274+
return running_job_response(job_metadata)
298275
except Exception as e:
299-
return JSONResponse(
300-
status_code=500,
301-
content={
302-
"status": "failed",
303-
"result": None,
304-
"error": str(e),
305-
**(job_metadata or {}),
306-
},
307-
)
276+
return failed_job_response(error=str(e), job_metadata=job_metadata)
308277

309278

310279
@router.get(
@@ -318,7 +287,7 @@ async def get_budget_window_job_status(batch_job_id: str):
318287
"""
319288
state = get_batch_job_state(batch_job_id)
320289
if state is not None:
321-
return _json_batch_status_response(build_batch_status_response(state))
290+
return batch_status_response(build_batch_status_response(state))
322291

323292
seed_state = get_batch_job_seed(batch_job_id)
324293
if seed_state is None:
@@ -329,19 +298,19 @@ async def get_budget_window_job_status(batch_job_id: str):
329298
try:
330299
call = modal.FunctionCall.from_id(batch_job_id)
331300
except Exception:
332-
return _json_batch_status_response(build_batch_status_response(seed_state))
301+
return batch_status_response(build_batch_status_response(seed_state))
333302

334303
try:
335304
result = call.get(timeout=0)
336305
except TimeoutError:
337-
return _json_batch_status_response(build_batch_status_response(seed_state))
306+
return batch_status_response(build_batch_status_response(seed_state))
338307
except Exception as e:
339308
seed_state.status = "failed"
340309
seed_state.error = str(e)
341-
return _json_batch_status_response(build_batch_status_response(seed_state))
310+
return batch_status_response(build_batch_status_response(seed_state))
342311

343312
response = BudgetWindowBatchStatusResponse.model_validate(result)
344-
return _json_batch_status_response(response)
313+
return batch_status_response(response)
345314

346315

347316
@router.get("/versions")
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""Shared HTTP responses for gateway endpoints."""
2+
3+
from __future__ import annotations
4+
5+
from fastapi.responses import JSONResponse
6+
7+
from src.modal.gateway.models import BudgetWindowBatchStatusResponse
8+
9+
10+
class AcceptedResponse(JSONResponse):
11+
"""Shared 202 JSON response."""
12+
13+
def __init__(self, content: dict):
14+
super().__init__(status_code=202, content=content)
15+
16+
17+
class ServerErrorResponse(JSONResponse):
18+
"""Shared 500 JSON response."""
19+
20+
def __init__(self, content: dict):
21+
super().__init__(status_code=500, content=content)
22+
23+
24+
def batch_status_payload(response: BudgetWindowBatchStatusResponse) -> dict:
25+
payload = response.model_dump(mode="json")
26+
if response.policyengine_bundle is not None:
27+
payload["policyengine_bundle"] = response.policyengine_bundle.model_dump(
28+
mode="json",
29+
exclude_none=True,
30+
)
31+
return payload
32+
33+
34+
def batch_status_response(response: BudgetWindowBatchStatusResponse):
35+
payload = batch_status_payload(response)
36+
if response.status in {"submitted", "running"}:
37+
return AcceptedResponse(payload)
38+
if response.status == "failed":
39+
return ServerErrorResponse(payload)
40+
return response
41+
42+
43+
def running_job_response(job_metadata: dict | None = None) -> AcceptedResponse:
44+
return AcceptedResponse(
45+
{
46+
"status": "running",
47+
"result": None,
48+
"error": None,
49+
**(job_metadata or {}),
50+
}
51+
)
52+
53+
54+
def failed_job_response(
55+
*, error: str, job_metadata: dict | None = None
56+
) -> ServerErrorResponse:
57+
return ServerErrorResponse(
58+
{
59+
"status": "failed",
60+
"result": None,
61+
"error": error,
62+
**(job_metadata or {}),
63+
}
64+
)

0 commit comments

Comments
 (0)