Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fastapi_radar/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
from .radar import Radar
from .background import track_background_task

__version__ = "0.3.1"
__version__ = "0.3.2"
__all__ = ["Radar", "track_background_task"]
326 changes: 0 additions & 326 deletions fastapi_radar/dashboard/dist/assets/index-8Om0PGu6.js

This file was deleted.

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions fastapi_radar/dashboard/dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FastAPI Radar - Debugging Dashboard</title>
<script type="module" crossorigin src="/__radar/assets/index-8Om0PGu6.js"></script>
<link rel="stylesheet" crossorigin href="/__radar/assets/index-XlGcZj49.css">
<script type="module" crossorigin src="/__radar/assets/index-BQIU9U77.js"></script>
<link rel="stylesheet" crossorigin href="/__radar/assets/index-D51YrvFG.css">
</head>
<body>
<div id="root"></div>
Expand Down
75 changes: 68 additions & 7 deletions fastapi_radar/radar.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
import sys
import multiprocessing
from pathlib import Path
from typing import List, Optional
from typing import List, Optional, Union
import asyncio

from fastapi import FastAPI
from sqlalchemy import create_engine
from sqlalchemy.engine import Engine
from sqlalchemy.ext.asyncio import AsyncEngine
from sqlalchemy.orm import Session, sessionmaker
from sqlalchemy.pool import StaticPool

Expand Down Expand Up @@ -47,7 +49,7 @@ def __init__(
self,
app: FastAPI,
db_engine: Optional[Engine] = None,
storage_engine: Optional[Engine] = None,
storage_engine: Optional[Union[Engine, AsyncEngine]] = None,
dashboard_path: str = "/__radar",
max_requests: int = 1000,
retention_hours: int = 24,
Expand Down Expand Up @@ -146,9 +148,22 @@ def __init__(
poolclass=StaticPool,
)

self.SessionLocal = sessionmaker(
autocommit=False, autoflush=False, bind=self.storage_engine
)
# Check if storage_engine is async or sync
# If async, we'll use it for DDL operations but keep sessions sync
# by accessing the sync engine from the async engine
if isinstance(self.storage_engine, AsyncEngine):
# For async engines, get the underlying sync engine for session operations
# The middleware and other components use sessions synchronously
self._is_async_storage = True
sync_engine = self.storage_engine.sync_engine
self.SessionLocal = sessionmaker(
autocommit=False, autoflush=False, bind=sync_engine
)
else:
self._is_async_storage = False
self.SessionLocal = sessionmaker(
autocommit=False, autoflush=False, bind=self.storage_engine
)

self._setup_middleware()

Expand Down Expand Up @@ -372,16 +387,62 @@ def create_tables(self) -> None:

With dev mode (fastapi dev), this safely handles
multiple process attempts to create tables.

Supports both sync and async storage engines.
"""
try:
Base.metadata.create_all(bind=self.storage_engine)
if isinstance(self.storage_engine, AsyncEngine):
# For async engines, we need to run the DDL in a sync context
async def _create_tables():
async with self.storage_engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)

# Check if there's already an event loop running
try:
asyncio.get_running_loop()
except RuntimeError:
# No event loop running, safe to use asyncio.run()
asyncio.run(_create_tables())
else:
# Event loop is running, we need to run in a thread
import concurrent.futures

with concurrent.futures.ThreadPoolExecutor() as executor:
future = executor.submit(asyncio.run, _create_tables())
future.result()
else:
Base.metadata.create_all(bind=self.storage_engine)
except Exception as e:
error_msg = str(e).lower()
if "already exists" not in error_msg and "lock" not in error_msg:
raise

def drop_tables(self) -> None:
Base.metadata.drop_all(bind=self.storage_engine)
"""Drop all Radar tables.

Supports both sync and async storage engines.
"""
if isinstance(self.storage_engine, AsyncEngine):
# For async engines, we need to run the DDL in a sync context
async def _drop_tables():
async with self.storage_engine.begin() as conn:
await conn.run_sync(Base.metadata.drop_all)

# Check if there's already an event loop running
try:
asyncio.get_running_loop()
except RuntimeError:
# No event loop running, safe to use asyncio.run()
asyncio.run(_drop_tables())
else:
# Event loop is running, we need to run in a thread
import concurrent.futures

with concurrent.futures.ThreadPoolExecutor() as executor:
future = executor.submit(asyncio.run, _drop_tables())
future.result()
else:
Base.metadata.drop_all(bind=self.storage_engine)

def cleanup(self, older_than_hours: Optional[int] = None) -> None:
from datetime import datetime, timedelta, timezone
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "fastapi-radar"
version = "0.3.1"
version = "0.3.2"
description = "A debugging dashboard for FastAPI applications with real-time monitoring"
readme = "README.md"
requires-python = ">=3.9"
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

setup(
name="fastapi-radar",
version="0.3.1",
version="0.3.2",
author="Arif Dogan",
author_email="me@arif.sh",
description=(
Expand Down
Loading