fastapi-radar 0.1.8__py3-none-any.whl → 0.2.0__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/__init__.py +1 -1
- fastapi_radar/api.py +4 -4
- fastapi_radar/capture.py +38 -10
- fastapi_radar/dashboard/dist/assets/{index-By5DXl8Z.js → index-31zorKsE.js} +62 -67
- fastapi_radar/dashboard/dist/index.html +1 -1
- fastapi_radar/middleware.py +15 -7
- fastapi_radar/models.py +20 -8
- fastapi_radar/radar.py +73 -10
- fastapi_radar/tracing.py +6 -6
- {fastapi_radar-0.1.8.dist-info → fastapi_radar-0.2.0.dist-info}/METADATA +19 -1
- fastapi_radar-0.2.0.dist-info/RECORD +19 -0
- tests/test_async_radar.py +56 -0
- fastapi_radar-0.1.8.dist-info/RECORD +0 -18
- {fastapi_radar-0.1.8.dist-info → fastapi_radar-0.2.0.dist-info}/WHEEL +0 -0
- {fastapi_radar-0.1.8.dist-info → fastapi_radar-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {fastapi_radar-0.1.8.dist-info → fastapi_radar-0.2.0.dist-info}/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>FastAPI Radar - Debugging Dashboard</title>
|
|
7
|
-
<script type="module" crossorigin src="/__radar/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/__radar/assets/index-31zorKsE.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/__radar/assets/index-XlGcZj49.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
fastapi_radar/middleware.py
CHANGED
|
@@ -7,6 +7,7 @@ import uuid
|
|
|
7
7
|
from contextvars import ContextVar
|
|
8
8
|
from typing import Callable, Optional
|
|
9
9
|
|
|
10
|
+
from sqlalchemy.exc import SQLAlchemyError
|
|
10
11
|
from starlette.middleware.base import BaseHTTPMiddleware
|
|
11
12
|
from starlette.requests import Request
|
|
12
13
|
from starlette.responses import Response, StreamingResponse
|
|
@@ -118,16 +119,23 @@ class RadarMiddleware(BaseHTTPMiddleware):
|
|
|
118
119
|
|
|
119
120
|
async def capture_response():
|
|
120
121
|
response_body = ""
|
|
122
|
+
capturing = True
|
|
121
123
|
async for chunk in original_response.body_iterator:
|
|
122
124
|
yield chunk
|
|
123
|
-
if
|
|
125
|
+
if capturing:
|
|
124
126
|
response_body += chunk.decode("utf-8", errors="ignore")
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
response_body
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
127
|
+
try:
|
|
128
|
+
with self.get_session() as session:
|
|
129
|
+
captured_request.response_body = truncate_body(
|
|
130
|
+
response_body, self.max_body_size
|
|
131
|
+
)
|
|
132
|
+
session.add(captured_request)
|
|
133
|
+
session.commit()
|
|
134
|
+
except SQLAlchemyError:
|
|
135
|
+
# CapturedRequest record has been deleted.
|
|
136
|
+
capturing = False
|
|
137
|
+
else:
|
|
138
|
+
capturing = len(response_body) < self.max_body_size
|
|
131
139
|
|
|
132
140
|
response = StreamingResponse(
|
|
133
141
|
content=capture_response(),
|
fastapi_radar/models.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Storage models for FastAPI Radar."""
|
|
2
2
|
|
|
3
|
-
from datetime import datetime
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
4
|
|
|
5
5
|
from sqlalchemy import (
|
|
6
6
|
Column,
|
|
@@ -40,7 +40,9 @@ class CapturedRequest(Base):
|
|
|
40
40
|
response_headers = Column(JSON)
|
|
41
41
|
duration_ms = Column(Float)
|
|
42
42
|
client_ip = Column(String(50))
|
|
43
|
-
created_at = Column(
|
|
43
|
+
created_at = Column(
|
|
44
|
+
DateTime, default=lambda: datetime.now(timezone.utc), index=True
|
|
45
|
+
)
|
|
44
46
|
|
|
45
47
|
queries = relationship(
|
|
46
48
|
"CapturedQuery",
|
|
@@ -68,7 +70,9 @@ class CapturedQuery(Base):
|
|
|
68
70
|
duration_ms = Column(Float)
|
|
69
71
|
rows_affected = Column(Integer)
|
|
70
72
|
connection_name = Column(String(100))
|
|
71
|
-
created_at = Column(
|
|
73
|
+
created_at = Column(
|
|
74
|
+
DateTime, default=lambda: datetime.now(timezone.utc), index=True
|
|
75
|
+
)
|
|
72
76
|
|
|
73
77
|
request = relationship(
|
|
74
78
|
"CapturedRequest",
|
|
@@ -87,7 +91,9 @@ class CapturedException(Base):
|
|
|
87
91
|
exception_type = Column(String(100), nullable=False)
|
|
88
92
|
exception_value = Column(Text)
|
|
89
93
|
traceback = Column(Text, nullable=False)
|
|
90
|
-
created_at = Column(
|
|
94
|
+
created_at = Column(
|
|
95
|
+
DateTime, default=lambda: datetime.now(timezone.utc), index=True
|
|
96
|
+
)
|
|
91
97
|
|
|
92
98
|
request = relationship(
|
|
93
99
|
"CapturedRequest",
|
|
@@ -104,13 +110,17 @@ class Trace(Base):
|
|
|
104
110
|
trace_id = Column(String(32), primary_key=True, index=True)
|
|
105
111
|
service_name = Column(String(100), index=True)
|
|
106
112
|
operation_name = Column(String(200))
|
|
107
|
-
start_time = Column(
|
|
113
|
+
start_time = Column(
|
|
114
|
+
DateTime, default=lambda: datetime.now(timezone.utc), index=True
|
|
115
|
+
)
|
|
108
116
|
end_time = Column(DateTime)
|
|
109
117
|
duration_ms = Column(Float)
|
|
110
118
|
span_count = Column(Integer, default=0)
|
|
111
119
|
status = Column(String(20), default="ok")
|
|
112
120
|
tags = Column(JSON)
|
|
113
|
-
created_at = Column(
|
|
121
|
+
created_at = Column(
|
|
122
|
+
DateTime, default=lambda: datetime.now(timezone.utc), index=True
|
|
123
|
+
)
|
|
114
124
|
|
|
115
125
|
spans = relationship(
|
|
116
126
|
"Span",
|
|
@@ -135,7 +145,9 @@ class Span(Base):
|
|
|
135
145
|
status = Column(String(20), default="ok")
|
|
136
146
|
tags = Column(JSON)
|
|
137
147
|
logs = Column(JSON)
|
|
138
|
-
created_at = Column(
|
|
148
|
+
created_at = Column(
|
|
149
|
+
DateTime, default=lambda: datetime.now(timezone.utc), index=True
|
|
150
|
+
)
|
|
139
151
|
|
|
140
152
|
trace = relationship(
|
|
141
153
|
"Trace",
|
|
@@ -154,4 +166,4 @@ class SpanRelation(Base):
|
|
|
154
166
|
parent_span_id = Column(String(16), index=True)
|
|
155
167
|
child_span_id = Column(String(16), index=True)
|
|
156
168
|
depth = Column(Integer, default=0)
|
|
157
|
-
created_at = Column(DateTime, default=datetime.
|
|
169
|
+
created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc))
|
fastapi_radar/radar.py
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from contextlib import contextmanager
|
|
4
4
|
import os
|
|
5
|
+
import sys
|
|
6
|
+
import multiprocessing
|
|
5
7
|
from pathlib import Path
|
|
6
8
|
from typing import List, Optional
|
|
7
9
|
|
|
@@ -16,6 +18,31 @@ from .middleware import RadarMiddleware
|
|
|
16
18
|
from .models import Base
|
|
17
19
|
|
|
18
20
|
|
|
21
|
+
def is_reload_worker() -> bool:
|
|
22
|
+
"""Check if we're running in a reload worker process (used by fastapi dev)."""
|
|
23
|
+
# Check for uvicorn reload worker
|
|
24
|
+
if os.environ.get("UVICORN_RELOAD"):
|
|
25
|
+
return True
|
|
26
|
+
|
|
27
|
+
# Check for Werkzeug reloader (used by some dev servers)
|
|
28
|
+
if os.environ.get("WERKZEUG_RUN_MAIN"):
|
|
29
|
+
return True
|
|
30
|
+
|
|
31
|
+
# Check if we're not the main process (common in reload scenarios)
|
|
32
|
+
# On Windows, the main process name is often "MainProcess"
|
|
33
|
+
if hasattr(multiprocessing.current_process(), "name"):
|
|
34
|
+
process_name = multiprocessing.current_process().name
|
|
35
|
+
if process_name != "MainProcess" and "SpawnProcess" in process_name:
|
|
36
|
+
return True
|
|
37
|
+
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def is_windows() -> bool:
|
|
42
|
+
"""Check if we're running on Windows."""
|
|
43
|
+
return sys.platform.startswith("win")
|
|
44
|
+
|
|
45
|
+
|
|
19
46
|
class Radar:
|
|
20
47
|
query_capture: Optional[QueryCapture]
|
|
21
48
|
|
|
@@ -95,13 +122,34 @@ class Radar:
|
|
|
95
122
|
else:
|
|
96
123
|
radar_db_path = Path.cwd() / "radar.duckdb"
|
|
97
124
|
radar_db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
125
|
+
|
|
126
|
+
# Handle reload worker scenario (fastapi dev) on all platforms
|
|
127
|
+
if is_reload_worker():
|
|
128
|
+
import warnings
|
|
129
|
+
|
|
130
|
+
warnings.warn(
|
|
131
|
+
"FastAPI Radar: Detected development mode with auto-reload. "
|
|
132
|
+
"Using in-memory database to avoid file locking issues. "
|
|
133
|
+
"Data will not persist between reloads.",
|
|
134
|
+
UserWarning,
|
|
135
|
+
)
|
|
136
|
+
# Use in-memory database for dev mode with reload
|
|
137
|
+
self.storage_engine = create_engine(
|
|
138
|
+
"duckdb:///:memory:",
|
|
139
|
+
connect_args={
|
|
140
|
+
"read_only": False,
|
|
141
|
+
"config": {"memory_limit": "500mb"},
|
|
142
|
+
},
|
|
143
|
+
)
|
|
144
|
+
else:
|
|
145
|
+
# Normal file-based database for production
|
|
146
|
+
self.storage_engine = create_engine(
|
|
147
|
+
f"duckdb:///{radar_db_path}",
|
|
148
|
+
connect_args={
|
|
149
|
+
"read_only": False,
|
|
150
|
+
"config": {"memory_limit": "500mb"},
|
|
151
|
+
},
|
|
152
|
+
)
|
|
105
153
|
|
|
106
154
|
self.SessionLocal = sessionmaker(
|
|
107
155
|
autocommit=False, autoflush=False, bind=self.storage_engine
|
|
@@ -333,19 +381,34 @@ class Radar:
|
|
|
333
381
|
)
|
|
334
382
|
|
|
335
383
|
def create_tables(self) -> None:
|
|
336
|
-
|
|
384
|
+
"""Create database tables.
|
|
385
|
+
|
|
386
|
+
With dev mode (fastapi dev), this will safely handle
|
|
387
|
+
multiple process attempts to create tables.
|
|
388
|
+
"""
|
|
389
|
+
try:
|
|
390
|
+
Base.metadata.create_all(bind=self.storage_engine)
|
|
391
|
+
except Exception as e:
|
|
392
|
+
# With reload workers, table creation might fail
|
|
393
|
+
# if another process already created them or has a lock
|
|
394
|
+
error_msg = str(e).lower()
|
|
395
|
+
if "already exists" in error_msg or "lock" in error_msg:
|
|
396
|
+
pass # Tables already exist or locked, that's fine in dev mode
|
|
397
|
+
else:
|
|
398
|
+
# Re-raise other exceptions
|
|
399
|
+
raise
|
|
337
400
|
|
|
338
401
|
def drop_tables(self) -> None:
|
|
339
402
|
Base.metadata.drop_all(bind=self.storage_engine)
|
|
340
403
|
|
|
341
404
|
def cleanup(self, older_than_hours: Optional[int] = None) -> None:
|
|
342
|
-
from datetime import datetime, timedelta
|
|
405
|
+
from datetime import datetime, timedelta, timezone
|
|
343
406
|
|
|
344
407
|
from .models import CapturedRequest
|
|
345
408
|
|
|
346
409
|
with self.get_session() as session:
|
|
347
410
|
hours = older_than_hours or self.retention_hours
|
|
348
|
-
cutoff = datetime.
|
|
411
|
+
cutoff = datetime.now(timezone.utc) - timedelta(hours=hours)
|
|
349
412
|
|
|
350
413
|
deleted = (
|
|
351
414
|
session.query(CapturedRequest)
|
fastapi_radar/tracing.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Tracing core functionality module."""
|
|
2
2
|
|
|
3
3
|
import uuid
|
|
4
|
-
from datetime import datetime
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
5
|
from typing import Optional, Dict, Any, List
|
|
6
6
|
from contextvars import ContextVar
|
|
7
7
|
from sqlalchemy.orm import Session
|
|
@@ -23,7 +23,7 @@ class TraceContext:
|
|
|
23
23
|
self.root_span_id: Optional[str] = None
|
|
24
24
|
self.current_span_id: Optional[str] = None
|
|
25
25
|
self.spans: Dict[str, Dict[str, Any]] = {}
|
|
26
|
-
self.start_time = datetime.
|
|
26
|
+
self.start_time = datetime.now(timezone.utc)
|
|
27
27
|
|
|
28
28
|
def create_span(
|
|
29
29
|
self,
|
|
@@ -42,7 +42,7 @@ class TraceContext:
|
|
|
42
42
|
"operation_name": operation_name,
|
|
43
43
|
"service_name": self.service_name,
|
|
44
44
|
"span_kind": span_kind,
|
|
45
|
-
"start_time": datetime.
|
|
45
|
+
"start_time": datetime.now(timezone.utc),
|
|
46
46
|
"tags": tags or {},
|
|
47
47
|
"logs": [],
|
|
48
48
|
"status": "ok",
|
|
@@ -64,7 +64,7 @@ class TraceContext:
|
|
|
64
64
|
return
|
|
65
65
|
|
|
66
66
|
span_data = self.spans[span_id]
|
|
67
|
-
span_data["end_time"] = datetime.
|
|
67
|
+
span_data["end_time"] = datetime.now(timezone.utc)
|
|
68
68
|
span_data["duration_ms"] = (
|
|
69
69
|
span_data["end_time"] - span_data["start_time"]
|
|
70
70
|
).total_seconds() * 1000
|
|
@@ -79,7 +79,7 @@ class TraceContext:
|
|
|
79
79
|
return
|
|
80
80
|
|
|
81
81
|
log_entry = {
|
|
82
|
-
"timestamp": datetime.
|
|
82
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
83
83
|
"level": level,
|
|
84
84
|
"message": message,
|
|
85
85
|
**fields,
|
|
@@ -108,7 +108,7 @@ class TraceContext:
|
|
|
108
108
|
error_count += 1
|
|
109
109
|
|
|
110
110
|
start_time = min(all_times) if all_times else self.start_time
|
|
111
|
-
end_time = max(all_times) if all_times else datetime.
|
|
111
|
+
end_time = max(all_times) if all_times else datetime.now(timezone.utc)
|
|
112
112
|
|
|
113
113
|
return {
|
|
114
114
|
"trace_id": self.trace_id,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-radar
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: A debugging dashboard for FastAPI applications with real-time monitoring
|
|
5
5
|
Home-page: https://github.com/doganarif/fastapi-radar
|
|
6
6
|
Author: Arif Dogan
|
|
@@ -29,6 +29,7 @@ Requires-Dist: pydantic
|
|
|
29
29
|
Requires-Dist: starlette
|
|
30
30
|
Requires-Dist: duckdb==1.1.3
|
|
31
31
|
Requires-Dist: duckdb-engine==0.17.0
|
|
32
|
+
Requires-Dist: aiosqlite>=0.21.0
|
|
32
33
|
Provides-Extra: dev
|
|
33
34
|
Requires-Dist: pytest; extra == "dev"
|
|
34
35
|
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
@@ -163,6 +164,23 @@ radar = Radar(app, db_path="./data")
|
|
|
163
164
|
|
|
164
165
|
If the specified path cannot be created, FastAPI Radar will fallback to using the current directory with a warning.
|
|
165
166
|
|
|
167
|
+
### Development Mode with Auto-Reload
|
|
168
|
+
|
|
169
|
+
When running your FastAPI application with `fastapi dev` (which uses auto-reload), FastAPI Radar automatically switches to an in-memory database to avoid file locking issues. This means:
|
|
170
|
+
|
|
171
|
+
- **No file locking errors** - The dashboard will work seamlessly in development
|
|
172
|
+
- **Data doesn't persist between reloads** - Each reload starts with a fresh database
|
|
173
|
+
- **Production behavior unchanged** - When using `fastapi run` or deploying, the normal file-based database is used
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
# With fastapi dev (auto-reload enabled):
|
|
177
|
+
# Automatically uses in-memory database - no configuration needed!
|
|
178
|
+
radar = Radar(app)
|
|
179
|
+
radar.create_tables() # Safe to call - handles multiple processes gracefully
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
This behavior only applies when using the development server with auto-reload (`fastapi dev`). In production or when using `fastapi run`, the standard file-based DuckDB storage is used.
|
|
183
|
+
|
|
166
184
|
## What Gets Captured?
|
|
167
185
|
|
|
168
186
|
- ✅ HTTP requests and responses
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
fastapi_radar/__init__.py,sha256=RC_pRDx1vkO5_-d_XA4VMJEdVBelwu3CxQrXCAEl8Uc,137
|
|
2
|
+
fastapi_radar/api.py,sha256=uuUQp-E8MBZf9bEklrRLfgXv2c0AYiE9yCI8FBIR8Fk,16136
|
|
3
|
+
fastapi_radar/capture.py,sha256=weWpI2HBb-qp04SZMWXt-Lx3NYo44WBvH65-Yyvv_UI,6623
|
|
4
|
+
fastapi_radar/middleware.py,sha256=qSELsASuZwpg4rsljQCU64fhkAYcOjadfiZICkZO7BA,8439
|
|
5
|
+
fastapi_radar/models.py,sha256=aOxShW8DeYKhehGL9tvBTSWkqqJSgDMKJKNQ1w_OKdE,5092
|
|
6
|
+
fastapi_radar/radar.py,sha256=sBmniNedkd_IWWBOqdgYBLmys2YE9MbGmz8kacDcGcg,15104
|
|
7
|
+
fastapi_radar/tracing.py,sha256=GNayJJaxZR68ZiT3Io9GUyd9SnbFrfXGnRRpQigLDL0,8798
|
|
8
|
+
fastapi_radar/utils.py,sha256=82cNXjbtm7oTaNRwrSy2fPWDVGi8XSk56C_8o7N7oRQ,1516
|
|
9
|
+
fastapi_radar/dashboard/dist/index.html,sha256=qLA_fwKrBIEDOYEuBU0ZlW8EbDy5rec-IaP6RIp67BA,436
|
|
10
|
+
fastapi_radar/dashboard/dist/assets/index-31zorKsE.js,sha256=Hul-8iFpscVAAFsmcQU5Ub2Smo6OCPnGZlCDGnEIItU,837403
|
|
11
|
+
fastapi_radar/dashboard/dist/assets/index-XlGcZj49.css,sha256=z2yLr4jYQoOM7KQ2aw5zrPpMlkr9dRvwBSGaxjzZNnc,35552
|
|
12
|
+
fastapi_radar-0.2.0.dist-info/licenses/LICENSE,sha256=0ga4BB6q-nqx6xlDRhtrgKrYs0HgX02PQyIzNFRK09Q,1067
|
|
13
|
+
tests/__init__.py,sha256=kAWaI50iJRZ4JlAdyt7FICgm8MsloZz0ZlsmhgLXBas,31
|
|
14
|
+
tests/test_async_radar.py,sha256=zsj2r6-q9WjjamLtzrIDWI8fxcBJ0oVOcynfvPlOhRE,1610
|
|
15
|
+
tests/test_radar.py,sha256=3F-_zdemPcgQnjP4kzCa7GhMxNJNYU0SgSWprctyXiA,2374
|
|
16
|
+
fastapi_radar-0.2.0.dist-info/METADATA,sha256=_Qmv_JSleXcpDOIk-6PoisOsK-nNmQiU5IW7ih5VAh0,7637
|
|
17
|
+
fastapi_radar-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
18
|
+
fastapi_radar-0.2.0.dist-info/top_level.txt,sha256=M-bALM-KDkiLcATq2aAx-BnG59Nv-GdFBzuzkUhiCa0,20
|
|
19
|
+
fastapi_radar-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from fastapi import FastAPI
|
|
2
|
+
from fastapi_radar import Radar
|
|
3
|
+
from sqlalchemy import Column, Integer, MetaData, String, Table, select
|
|
4
|
+
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
|
5
|
+
|
|
6
|
+
app = FastAPI()
|
|
7
|
+
engine = create_async_engine("sqlite+aiosqlite:///./app.db")
|
|
8
|
+
async_session: async_sessionmaker[AsyncSession] = async_sessionmaker(
|
|
9
|
+
engine, expire_on_commit=False
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
# 定义一个简单的测试表
|
|
13
|
+
metadata = MetaData()
|
|
14
|
+
users_table = Table(
|
|
15
|
+
"users",
|
|
16
|
+
metadata,
|
|
17
|
+
Column("id", Integer, primary_key=True, autoincrement=True),
|
|
18
|
+
Column("name", String(50), nullable=False),
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
radar = Radar(app, db_engine=engine)
|
|
23
|
+
radar.create_tables()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@app.on_event("startup")
|
|
27
|
+
async def on_startup() -> None:
|
|
28
|
+
"""应用启动时创建测试表并写入示例数据。"""
|
|
29
|
+
|
|
30
|
+
async with engine.begin() as conn:
|
|
31
|
+
await conn.run_sync(metadata.create_all)
|
|
32
|
+
|
|
33
|
+
async with async_session() as session:
|
|
34
|
+
result = await session.execute(select(users_table.c.id).limit(1))
|
|
35
|
+
if result.first() is None:
|
|
36
|
+
await session.execute(
|
|
37
|
+
users_table.insert(),
|
|
38
|
+
[{"name": "Alice"}, {"name": "Bob"}, {"name": "Carol"}],
|
|
39
|
+
)
|
|
40
|
+
await session.commit()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# Your routes work unchanged
|
|
44
|
+
@app.get("/users")
|
|
45
|
+
async def get_users():
|
|
46
|
+
async with async_session() as session:
|
|
47
|
+
result = await session.execute(select(users_table))
|
|
48
|
+
rows = result.mappings().all()
|
|
49
|
+
|
|
50
|
+
return {"users": [dict(row) for row in rows]}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
import uvicorn
|
|
55
|
+
|
|
56
|
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
fastapi_radar/__init__.py,sha256=fedYsDj_d9FfIVMa1eDrNw5-COcCokqHcM8puxLKLRM,137
|
|
2
|
-
fastapi_radar/api.py,sha256=JYXYocgWdmjHbrfthpBuqXmIWH8ZNct4qA3WCbinL2o,16099
|
|
3
|
-
fastapi_radar/capture.py,sha256=butzGPmjR8f3gdrNqh4iOlXD2WBD8Zh6CcSTDlnYR5o,5186
|
|
4
|
-
fastapi_radar/middleware.py,sha256=6FRlZa3XJwOA8UF5NkZ3pOXyK7F9P_ksvixf1CbFllk,8037
|
|
5
|
-
fastapi_radar/models.py,sha256=jsYakaDd3HRDdAI7SfXLL5fI_fAfVBBhcYq8ktDPfmk,4865
|
|
6
|
-
fastapi_radar/radar.py,sha256=GeD1bUqHX_20oZ0spQylk3-Vusopc9KIUmPoNRNRgQQ,12637
|
|
7
|
-
fastapi_radar/tracing.py,sha256=m0AEX4sa-VUu5STezhpa51BZQ7R8Ax9u2zry2JSF3hQ,8743
|
|
8
|
-
fastapi_radar/utils.py,sha256=82cNXjbtm7oTaNRwrSy2fPWDVGi8XSk56C_8o7N7oRQ,1516
|
|
9
|
-
fastapi_radar/dashboard/dist/index.html,sha256=-Dx0Jw0WDyD29adrACqarkIq_XlUDo7BzqHmt3_mAkc,436
|
|
10
|
-
fastapi_radar/dashboard/dist/assets/index-By5DXl8Z.js,sha256=D4jEA0Ep4L7PXXo8rZ_saAQIWhl43aAfIdjI6b4eotQ,833887
|
|
11
|
-
fastapi_radar/dashboard/dist/assets/index-XlGcZj49.css,sha256=z2yLr4jYQoOM7KQ2aw5zrPpMlkr9dRvwBSGaxjzZNnc,35552
|
|
12
|
-
fastapi_radar-0.1.8.dist-info/licenses/LICENSE,sha256=0ga4BB6q-nqx6xlDRhtrgKrYs0HgX02PQyIzNFRK09Q,1067
|
|
13
|
-
tests/__init__.py,sha256=kAWaI50iJRZ4JlAdyt7FICgm8MsloZz0ZlsmhgLXBas,31
|
|
14
|
-
tests/test_radar.py,sha256=3F-_zdemPcgQnjP4kzCa7GhMxNJNYU0SgSWprctyXiA,2374
|
|
15
|
-
fastapi_radar-0.1.8.dist-info/METADATA,sha256=pwGBPzfpMOI9z1gnzM1mGc_Svqa9d_jKH3PTe03fKVU,6685
|
|
16
|
-
fastapi_radar-0.1.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
17
|
-
fastapi_radar-0.1.8.dist-info/top_level.txt,sha256=M-bALM-KDkiLcATq2aAx-BnG59Nv-GdFBzuzkUhiCa0,20
|
|
18
|
-
fastapi_radar-0.1.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|