|
1 | 1 | #!/usr/bin/env python3 |
2 | 2 |
|
3 | 3 | import asyncio |
| 4 | +import json |
4 | 5 | import os |
5 | 6 | import signal |
6 | 7 | from typing import Optional |
|
13 | 14 | app = FastAPI() |
14 | 15 |
|
15 | 16 |
|
| 17 | +def _to_camel(name: str) -> str: |
| 18 | + """Convert a snake_case string to camelCase.""" |
| 19 | + parts = name.split("_") |
| 20 | + return parts[0] + "".join(p.capitalize() for p in parts[1:]) |
| 21 | + |
| 22 | + |
| 23 | +def _normalize_keys(obj): |
| 24 | + """Recursively convert all dict keys from snake_case to camelCase.""" |
| 25 | + if isinstance(obj, dict): |
| 26 | + return {_to_camel(k): _normalize_keys(v) for k, v in obj.items()} |
| 27 | + if isinstance(obj, list): |
| 28 | + return [_normalize_keys(item) for item in obj] |
| 29 | + return obj |
| 30 | + |
| 31 | + |
| 32 | +def _normalize_response(raw: bytes) -> bytes: |
| 33 | + """Parse raw JSON bytes, normalize keys to camelCase, return bytes.""" |
| 34 | + try: |
| 35 | + parsed = json.loads(raw) |
| 36 | + normalized = _normalize_keys(parsed) |
| 37 | + return json.dumps(normalized).encode() |
| 38 | + except (json.JSONDecodeError, ValueError): |
| 39 | + return raw |
| 40 | + |
| 41 | + |
16 | 42 | def _shutdown(sig, _frame): |
17 | 43 | for ws, proc in list(busy_workspaces.items()): |
18 | 44 | try: |
@@ -216,7 +242,7 @@ async def _disconnect_watcher(): |
216 | 242 | if not req.fire_and_forget and await request.is_disconnected(): |
217 | 243 | return Response(status_code=499) |
218 | 244 |
|
219 | | - return Response(content=output, media_type="application/json") |
| 245 | + return Response(content=_normalize_response(output), media_type="application/json") |
220 | 246 |
|
221 | 247 |
|
222 | 248 | @app.get("/files/{path:path}") |
|
0 commit comments