fastapi-radar 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of fastapi-radar might be problematic. Click here for more details.

fastapi_radar/api.py CHANGED
@@ -10,6 +10,13 @@ from pydantic import BaseModel
10
10
  from .models import CapturedRequest, CapturedQuery, CapturedException
11
11
 
12
12
 
13
+ def round_float(value: Optional[float], decimals: int = 2) -> Optional[float]:
14
+ """Round a float value to specified decimal places."""
15
+ if value is None:
16
+ return None
17
+ return round(value, decimals)
18
+
19
+
13
20
  class RequestSummary(BaseModel):
14
21
  id: int
15
22
  request_id: str
@@ -72,7 +79,7 @@ class DashboardStats(BaseModel):
72
79
 
73
80
 
74
81
  def create_api_router(get_session_context) -> APIRouter:
75
- router = APIRouter(prefix="/api/radar", tags=["radar"])
82
+ router = APIRouter(prefix="/__radar/api", tags=["radar"])
76
83
 
77
84
  def get_db():
78
85
  """Dependency function for FastAPI to get database session."""
@@ -91,11 +98,22 @@ def create_api_router(get_session_context) -> APIRouter:
91
98
  query = session.query(CapturedRequest)
92
99
 
93
100
  if status_code:
94
- query = query.filter(CapturedRequest.status_code == status_code)
101
+ # Handle status code ranges (e.g., 200 for 2xx, 400 for 4xx)
102
+ if status_code in [200, 300, 400, 500]:
103
+ # Filter by status code range
104
+ lower_bound = status_code
105
+ upper_bound = status_code + 100
106
+ query = query.filter(
107
+ CapturedRequest.status_code >= lower_bound,
108
+ CapturedRequest.status_code < upper_bound
109
+ )
110
+ else:
111
+ # Exact status code match
112
+ query = query.filter(CapturedRequest.status_code == status_code)
95
113
  if method:
96
114
  query = query.filter(CapturedRequest.method == method)
97
115
  if search:
98
- query = query.filter(CapturedRequest.path.contains(search))
116
+ query = query.filter(CapturedRequest.path.ilike(f"%{search}%"))
99
117
 
100
118
  requests = (
101
119
  query.order_by(desc(CapturedRequest.created_at))
@@ -111,7 +129,7 @@ def create_api_router(get_session_context) -> APIRouter:
111
129
  method=req.method,
112
130
  path=req.path,
113
131
  status_code=req.status_code,
114
- duration_ms=req.duration_ms,
132
+ duration_ms=round_float(req.duration_ms),
115
133
  query_count=len(req.queries),
116
134
  has_exception=len(req.exceptions) > 0,
117
135
  created_at=req.created_at,
@@ -142,7 +160,7 @@ def create_api_router(get_session_context) -> APIRouter:
142
160
  status_code=request.status_code,
143
161
  response_body=request.response_body,
144
162
  response_headers=request.response_headers,
145
- duration_ms=request.duration_ms,
163
+ duration_ms=round_float(request.duration_ms),
146
164
  client_ip=request.client_ip,
147
165
  created_at=request.created_at,
148
166
  queries=[
@@ -150,7 +168,7 @@ def create_api_router(get_session_context) -> APIRouter:
150
168
  "id": q.id,
151
169
  "sql": q.sql,
152
170
  "parameters": q.parameters,
153
- "duration_ms": q.duration_ms,
171
+ "duration_ms": round_float(q.duration_ms),
154
172
  "rows_affected": q.rows_affected,
155
173
  "connection_name": q.connection_name,
156
174
  "created_at": q.created_at.isoformat(),
@@ -175,12 +193,15 @@ def create_api_router(get_session_context) -> APIRouter:
175
193
  offset: int = Query(0, ge=0),
176
194
  slow_only: bool = Query(False),
177
195
  slow_threshold: int = Query(100),
196
+ search: Optional[str] = None,
178
197
  session: Session = Depends(get_db),
179
198
  ):
180
199
  query = session.query(CapturedQuery)
181
200
 
182
201
  if slow_only:
183
202
  query = query.filter(CapturedQuery.duration_ms >= slow_threshold)
203
+ if search:
204
+ query = query.filter(CapturedQuery.sql.ilike(f"%{search}%"))
184
205
 
185
206
  queries = (
186
207
  query.order_by(desc(CapturedQuery.created_at))
@@ -195,7 +216,7 @@ def create_api_router(get_session_context) -> APIRouter:
195
216
  request_id=q.request_id,
196
217
  sql=q.sql,
197
218
  parameters=q.parameters,
198
- duration_ms=q.duration_ms,
219
+ duration_ms=round_float(q.duration_ms),
199
220
  rows_affected=q.rows_affected,
200
221
  connection_name=q.connection_name,
201
222
  created_at=q.created_at,
@@ -236,7 +257,7 @@ def create_api_router(get_session_context) -> APIRouter:
236
257
 
237
258
  @router.get("/stats", response_model=DashboardStats)
238
259
  async def get_stats(
239
- hours: int = Query(1, ge=1, le=24),
260
+ hours: int = Query(1, ge=1, le=720), # Allow up to 30 days
240
261
  slow_threshold: int = Query(100),
241
262
  session: Session = Depends(get_db),
242
263
  ):
@@ -284,12 +305,12 @@ def create_api_router(get_session_context) -> APIRouter:
284
305
 
285
306
  return DashboardStats(
286
307
  total_requests=total_requests,
287
- avg_response_time=avg_response_time,
308
+ avg_response_time=round_float(avg_response_time),
288
309
  total_queries=total_queries,
289
- avg_query_time=avg_query_time,
310
+ avg_query_time=round_float(avg_query_time),
290
311
  total_exceptions=len(exceptions),
291
312
  slow_queries=slow_queries,
292
- requests_per_minute=requests_per_minute,
313
+ requests_per_minute=round_float(requests_per_minute),
293
314
  )
294
315
 
295
316
  @router.delete("/clear")
fastapi_radar/capture.py CHANGED
@@ -64,7 +64,7 @@ class QueryCapture:
64
64
  if start_time is None:
65
65
  return
66
66
 
67
- duration_ms = (time.time() - start_time) * 1000
67
+ duration_ms = round((time.time() - start_time) * 1000, 2)
68
68
 
69
69
  # Skip radar's own queries
70
70
  if "radar_" in statement: