Skip to content

Commit 9c50b37

Browse files
authored
Merge pull request #109 from amito/chore/ruff-lint-fixes
Add type annotations, mypy config, and error handling improvements
2 parents 2eb667f + 6883fe2 commit 9c50b37

22 files changed

Lines changed: 440 additions & 318 deletions

pyproject.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ dev = [
3434
"pytest-asyncio==0.24.0",
3535
"httpx==0.27.2",
3636
"ruff==0.8.4",
37+
"mypy>=1.13",
38+
"types-PyYAML>=6.0.12",
39+
"types-requests>=2.32",
3740
]
3841

3942
[build-system]
@@ -112,6 +115,14 @@ line-ending = "auto"
112115
known-first-party = ["neuralnav"]
113116
section-order = ["future", "standard-library", "third-party", "first-party", "local-folder"]
114117

118+
[tool.mypy]
119+
python_version = "3.11"
120+
warn_return_any = true
121+
warn_unused_configs = true
122+
disallow_untyped_defs = false
123+
check_untyped_defs = true
124+
ignore_missing_imports = true
125+
115126
[tool.pytest.ini_options]
116127
asyncio_mode = "strict"
117128
asyncio_default_fixture_loop_scope = "session"

src/neuralnav/api/routes/configuration.py

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ async def deploy_model(request: DeploymentRequest):
106106
except Exception as e:
107107
logger.error(f"YAML validation failed: {e}")
108108
raise HTTPException(
109-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Generated YAML validation failed: {str(e)}"
109+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
110+
detail=f"Generated YAML validation failed: {str(e)}",
110111
) from e
111112

112113
return DeploymentResponse(
@@ -120,7 +121,8 @@ async def deploy_model(request: DeploymentRequest):
120121
except Exception as e:
121122
logger.error(f"Failed to generate deployment: {e}", exc_info=True)
122123
raise HTTPException(
123-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to generate deployment: {str(e)}"
124+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
125+
detail=f"Failed to generate deployment: {str(e)}",
124126
) from e
125127

126128

@@ -206,7 +208,9 @@ async def get_deployment_status(deployment_id: str):
206208

207209
except Exception as e:
208210
logger.error(f"Failed to get deployment status: {e}")
209-
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Deployment not found: {deployment_id}") from e
211+
raise HTTPException(
212+
status_code=status.HTTP_404_NOT_FOUND, detail=f"Deployment not found: {deployment_id}"
213+
) from e
210214

211215

212216
@router.post("/deploy-to-cluster")
@@ -247,7 +251,8 @@ async def deploy_to_cluster(request: DeploymentRequest):
247251
except Exception as e:
248252
logger.error(f"YAML validation failed: {e}")
249253
raise HTTPException(
250-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Generated YAML validation failed: {str(e)}"
254+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
255+
detail=f"Generated YAML validation failed: {str(e)}",
251256
) from e
252257

253258
# Step 3: Deploy to cluster
@@ -258,7 +263,8 @@ async def deploy_to_cluster(request: DeploymentRequest):
258263
if not deployment_result["success"]:
259264
logger.error(f"Deployment failed: {deployment_result['errors']}")
260265
raise HTTPException(
261-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Deployment failed: {deployment_result['errors']}"
266+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
267+
detail=f"Deployment failed: {deployment_result['errors']}",
262268
)
263269

264270
logger.info(f"Successfully deployed {deployment_id} to cluster")
@@ -276,7 +282,10 @@ async def deploy_to_cluster(request: DeploymentRequest):
276282
raise
277283
except Exception as e:
278284
logger.error(f"Failed to deploy to cluster: {e}", exc_info=True)
279-
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to deploy to cluster: {str(e)}") from e
285+
raise HTTPException(
286+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
287+
detail=f"Failed to deploy to cluster: {str(e)}",
288+
) from e
280289

281290

282291
@router.get("/cluster-status")
@@ -333,7 +342,8 @@ async def get_k8s_deployment_status(deployment_id: str):
333342
except Exception as e:
334343
logger.error(f"Failed to get K8s deployment status: {e}", exc_info=True)
335344
raise HTTPException(
336-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to get deployment status: {str(e)}"
345+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
346+
detail=f"Failed to get deployment status: {str(e)}",
337347
) from e
338348

339349

@@ -362,7 +372,8 @@ async def get_deployment_yaml(deployment_id: str):
362372

363373
if not yaml_files:
364374
raise HTTPException(
365-
status_code=status.HTTP_404_NOT_FOUND, detail=f"No YAML files found for deployment {deployment_id}"
375+
status_code=status.HTTP_404_NOT_FOUND,
376+
detail=f"No YAML files found for deployment {deployment_id}",
366377
)
367378

368379
return {"deployment_id": deployment_id, "files": yaml_files, "count": len(yaml_files)}
@@ -372,7 +383,8 @@ async def get_deployment_yaml(deployment_id: str):
372383
except Exception as e:
373384
logger.error(f"Failed to retrieve YAML files: {e}", exc_info=True)
374385
raise HTTPException(
375-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to retrieve YAML files: {str(e)}"
386+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
387+
detail=f"Failed to retrieve YAML files: {str(e)}",
376388
) from e
377389

378390

@@ -407,7 +419,10 @@ async def delete_deployment(deployment_id: str):
407419
raise
408420
except Exception as e:
409421
logger.error(f"Failed to delete deployment: {e}", exc_info=True)
410-
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to delete deployment: {str(e)}") from e
422+
raise HTTPException(
423+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
424+
detail=f"Failed to delete deployment: {str(e)}",
425+
) from e
411426

412427

413428
@router.get("/deployments")
@@ -428,10 +443,10 @@ async def list_all_deployments():
428443

429444
deployments = []
430445
for deployment_id in deployment_ids:
431-
status = manager.get_inferenceservice_status(deployment_id)
446+
svc_status = manager.get_inferenceservice_status(deployment_id)
432447
pods = manager.get_deployment_pods(deployment_id)
433448

434-
deployments.append({"deployment_id": deployment_id, "status": status, "pods": pods})
449+
deployments.append({"deployment_id": deployment_id, "status": svc_status, "pods": pods})
435450

436451
return {
437452
"success": True,
@@ -442,4 +457,7 @@ async def list_all_deployments():
442457

443458
except Exception as e:
444459
logger.error(f"Failed to list deployments: {e}", exc_info=True)
445-
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to list deployments: {str(e)}") from e
460+
raise HTTPException(
461+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
462+
detail=f"Failed to list deployments: {str(e)}",
463+
) from e

src/neuralnav/api/routes/database.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ async def db_status():
4242
conn.close()
4343
except Exception as e:
4444
logger.error(f"Failed to get DB status: {e}")
45-
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=f"Database not accessible: {e}") from e
45+
raise HTTPException(
46+
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=f"Database not accessible: {e}"
47+
) from e
4648

4749

4850
@router.post("/db/upload-benchmarks")
@@ -57,13 +59,17 @@ async def upload_benchmarks(file: UploadFile = File(...)):
5759
curl -X POST -F 'file=@benchmarks.json' http://host/api/v1/db/upload-benchmarks
5860
"""
5961
if not file.filename or not file.filename.endswith(".json"):
60-
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="File must be a .json file")
62+
raise HTTPException(
63+
status_code=status.HTTP_400_BAD_REQUEST, detail="File must be a .json file"
64+
)
6165

6266
try:
6367
content = await file.read()
6468
data = json.loads(content)
6569
except json.JSONDecodeError as e:
66-
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid JSON: {e}") from e
70+
raise HTTPException(
71+
status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid JSON: {e}"
72+
) from e
6773

6874
benchmarks = data.get("benchmarks", [])
6975
if not benchmarks:
@@ -90,7 +96,10 @@ async def upload_benchmarks(file: UploadFile = File(...)):
9096
conn.close()
9197
except Exception as e:
9298
logger.error(f"Failed to load benchmarks: {e}")
93-
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to load benchmarks: {e}") from e
99+
raise HTTPException(
100+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
101+
detail=f"Failed to load benchmarks: {e}",
102+
) from e
94103

95104

96105
@router.post("/db/reset")
@@ -118,4 +127,7 @@ async def reset_database():
118127
conn.close()
119128
except Exception as e:
120129
logger.error(f"Failed to reset database: {e}")
121-
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to reset database: {e}") from e
130+
raise HTTPException(
131+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
132+
detail=f"Failed to reset database: {e}",
133+
) from e

src/neuralnav/api/routes/recommendation.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Recommendation endpoints."""
22

33
import logging
4+
from typing import Literal
45

56
from fastapi import APIRouter, HTTPException, status
67
from pydantic import BaseModel
@@ -49,7 +50,7 @@ class RankedRecommendationFromSpecRequest(BaseModel):
4950
ttft_target_ms: int
5051
itl_target_ms: int
5152
e2e_target_ms: int
52-
percentile: str = "p95" # "mean", "p90", "p95", "p99"
53+
percentile: Literal["mean", "p90", "p95", "p99"] = "p95"
5354

5455
# Ranking options
5556
min_accuracy: int | None = None
@@ -92,7 +93,7 @@ async def simple_recommend(request: SimpleRecommendationRequest):
9293
recommendation=recommendation, namespace="default"
9394
)
9495
deployment_id = yaml_result["deployment_id"]
95-
yaml_files = yaml_result["files"]
96+
yaml_files: dict = yaml_result["files"]
9697
logger.info(
9798
f"Auto-generated YAML files for {deployment_id}: {list(yaml_files.keys())}"
9899
)
@@ -143,7 +144,8 @@ async def simple_recommend(request: SimpleRecommendationRequest):
143144
except Exception as e:
144145
logger.error(f"Failed to generate recommendation: {e}", exc_info=True)
145146
raise HTTPException(
146-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to generate recommendation: {str(e)}"
147+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
148+
detail=f"Failed to generate recommendation: {str(e)}",
147149
) from e
148150

149151

@@ -248,7 +250,8 @@ async def ranked_recommend_from_spec(request: RankedRecommendationFromSpecReques
248250
except Exception as e:
249251
logger.error(f"Failed to generate ranked recommendations from spec: {e}", exc_info=True)
250252
raise HTTPException(
251-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to generate ranked recommendations: {str(e)}"
253+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
254+
detail=f"Failed to generate ranked recommendations: {str(e)}",
252255
) from e
253256

254257

@@ -270,7 +273,9 @@ async def test_endpoint(message: str = "I need a chatbot for 1000 users"):
270273
return {
271274
"success": True,
272275
"model": recommendation.model_name,
273-
"gpu_config": f"{recommendation.gpu_config.gpu_count}x {recommendation.gpu_config.gpu_type}",
276+
"gpu_config": f"{recommendation.gpu_config.gpu_count}x {recommendation.gpu_config.gpu_type}"
277+
if recommendation.gpu_config
278+
else "N/A",
274279
"cost_per_month": f"${recommendation.cost_per_month_usd:.2f}",
275280
"meets_slo": recommendation.meets_slo,
276281
"reasoning": recommendation.reasoning,

src/neuralnav/api/routes/reference_data.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ async def get_benchmarks():
6666

6767
if not csv_path.exists():
6868
logger.error(f"Benchmark CSV not found at: {csv_path}")
69-
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Benchmark data file not found")
69+
raise HTTPException(
70+
status_code=status.HTTP_404_NOT_FOUND, detail="Benchmark data file not found"
71+
)
7072

7173
# Read CSV using built-in csv module
7274
records = []
@@ -84,7 +86,10 @@ async def get_benchmarks():
8486
raise
8587
except Exception as e:
8688
logger.error(f"Failed to load benchmarks: {e}", exc_info=True)
87-
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to load benchmarks: {str(e)}") from e
89+
raise HTTPException(
90+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
91+
detail=f"Failed to load benchmarks: {str(e)}",
92+
) from e
8893

8994

9095
@router.get("/priority-weights")
@@ -99,7 +104,10 @@ async def get_priority_weights():
99104

100105
if not json_path.exists():
101106
logger.error(f"Priority weights config not found at: {json_path}")
102-
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Priority weights configuration not found")
107+
raise HTTPException(
108+
status_code=status.HTTP_404_NOT_FOUND,
109+
detail="Priority weights configuration not found",
110+
)
103111

104112
with open(json_path) as f:
105113
data = json.load(f)
@@ -142,7 +150,8 @@ async def get_weighted_scores(use_case: str):
142150
if not csv_path.exists():
143151
logger.error(f"Weighted scores CSV not found at: {csv_path}")
144152
raise HTTPException(
145-
status_code=status.HTTP_404_NOT_FOUND, detail=f"Weighted scores file not found for use case: {use_case}"
153+
status_code=status.HTTP_404_NOT_FOUND,
154+
detail=f"Weighted scores file not found for use case: {use_case}",
146155
)
147156

148157
# Read CSV using built-in csv module
@@ -160,5 +169,6 @@ async def get_weighted_scores(use_case: str):
160169
except Exception as e:
161170
logger.error(f"Failed to load weighted scores: {e}", exc_info=True)
162171
raise HTTPException(
163-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to load weighted scores: {str(e)}"
172+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
173+
detail=f"Failed to load weighted scores: {str(e)}",
164174
) from e

0 commit comments

Comments
 (0)