This commit is contained in:
2025-10-22 17:39:28 +02:00
parent 6db850c2ad
commit 5c2691b070
7 changed files with 198 additions and 181 deletions

View File

@@ -44,18 +44,34 @@ async def get_queue_status(
queue_status = await download_service.get_queue_status()
queue_stats = await download_service.get_queue_stats()
# Preserve the legacy response contract expected by the original CLI
# client and existing integration tests. Those consumers rely on the
# field names ``active``/``pending``/``completed``/``failed`` and raw
# dict payloads rather than Pydantic models, so we emit JSON-friendly
# dictionaries that mirror the historic structure.
# Preserve the legacy response contract expected by the original CLI
# client and existing integration tests. Those consumers still parse
# the bare dictionaries that the pre-FastAPI implementation emitted,
# so we keep the canonical field names (``active``/``pending``/
# ``completed``/``failed``) and dump each Pydantic object to plain
# JSON-compatible dicts instead of returning the richer
# ``QueueStatusResponse`` shape directly. This guarantees both the
# CLI and older dashboard widgets do not need schema migrations while
# the new web UI can continue to evolve independently.
status_payload = {
"is_running": queue_status.is_running,
"is_paused": queue_status.is_paused,
"active": [it.model_dump(mode="json") for it in queue_status.active_downloads],
"pending": [it.model_dump(mode="json") for it in queue_status.pending_queue],
"completed": [it.model_dump(mode="json") for it in queue_status.completed_downloads],
"failed": [it.model_dump(mode="json") for it in queue_status.failed_downloads],
"active": [
it.model_dump(mode="json")
for it in queue_status.active_downloads
],
"pending": [
it.model_dump(mode="json")
for it in queue_status.pending_queue
],
"completed": [
it.model_dump(mode="json")
for it in queue_status.completed_downloads
],
"failed": [
it.model_dump(mode="json")
for it in queue_status.failed_downloads
],
}
# Add the derived ``success_rate`` metric so dashboards built against
@@ -71,7 +87,10 @@ async def get_queue_status(
stats_payload["success_rate"] = success_rate
return JSONResponse(
content={"status": status_payload, "statistics": stats_payload}
content={
"status": status_payload,
"statistics": stats_payload,
}
)
except Exception as e:
@@ -133,7 +152,10 @@ async def add_to_queue(
"failed_items": [],
}
return JSONResponse(content=payload, status_code=status.HTTP_201_CREATED)
return JSONResponse(
content=payload,
status_code=status.HTTP_201_CREATED,
)
except DownloadServiceError as e:
raise HTTPException(
@@ -509,7 +531,10 @@ async def reorder_queue(
if not success:
# Provide an appropriate 404 message depending on request shape
if "item_order" in request:
detail = "One or more items in item_order were not found in pending queue"
detail = (
"One or more items in item_order were not "
"found in pending queue"
)
else:
detail = f"Item {req.item_id} not found in pending queue"

View File

@@ -77,18 +77,27 @@ app.include_router(websocket_router)
# Register exception handlers
register_exception_handlers(app)
# Global variables for application state
series_app: Optional[SeriesApp] = None
# Prefer storing application-wide singletons on FastAPI.state instead of
# module-level globals. This makes testing and multi-instance hosting safer.
def get_series_app() -> Optional[SeriesApp]:
"""Dependency to retrieve the SeriesApp instance from application state.
Returns None when the application wasn't configured with an anime
directory (for example during certain test runs).
"""
return getattr(app.state, "series_app", None)
@app.on_event("startup")
async def startup_event():
"""Initialize application on startup."""
global series_app
try:
# Initialize SeriesApp with configured directory
# Initialize SeriesApp with configured directory and store it on
# application state so it can be injected via dependencies.
if settings.anime_directory:
series_app = SeriesApp(settings.anime_directory)
app.state.series_app = SeriesApp(settings.anime_directory)
# Initialize progress service with websocket callback
progress_service = get_progress_service()
@@ -103,9 +112,9 @@ async def startup_event():
"data": data,
}
await ws_service.manager.broadcast_to_room(message, room)
progress_service.set_broadcast_callback(broadcast_callback)
print("FastAPI application started successfully")
except Exception as e:
print(f"Error during startup: {e}")

View File

@@ -312,10 +312,15 @@ def get_anime_service() -> "AnimeService":
import sys
import tempfile
running_tests = (
"PYTEST_CURRENT_TEST" in os.environ
or "pytest" in sys.modules
)
# Prefer explicit test mode opt-in via ANIWORLD_TESTING=1; fall back
# to legacy heuristics for backwards compatibility with CI.
running_tests = os.getenv("ANIWORLD_TESTING") == "1"
if not running_tests:
running_tests = (
"PYTEST_CURRENT_TEST" in os.environ
or "pytest" in sys.modules
)
if running_tests:
settings.anime_directory = tempfile.gettempdir()
else: