This commit is contained in:
2025-10-22 13:38:46 +02:00
parent 1f39f07c5d
commit 04799633b4
9 changed files with 411 additions and 571 deletions

View File

@@ -5,7 +5,7 @@ This module provides dependency injection functions for the FastAPI
application, including SeriesApp instances, AnimeService, DownloadService,
database sessions, and authentication dependencies.
"""
from typing import AsyncGenerator, Optional
from typing import TYPE_CHECKING, AsyncGenerator, Optional
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
@@ -19,6 +19,10 @@ from src.config.settings import settings
from src.core.SeriesApp import SeriesApp
from src.server.services.auth_service import AuthError, auth_service
if TYPE_CHECKING:
from src.server.services.anime_service import AnimeService
from src.server.services.download_service import DownloadService
# Security scheme for JWT authentication
# Use auto_error=False to handle errors manually and return 401 instead of 403
security = HTTPBearer(auto_error=False)
@@ -28,8 +32,8 @@ security = HTTPBearer(auto_error=False)
_series_app: Optional[SeriesApp] = None
# Global service instances
_anime_service: Optional[object] = None
_download_service: Optional[object] = None
_anime_service: Optional["AnimeService"] = None
_download_service: Optional["DownloadService"] = None
def get_series_app() -> SeriesApp:
@@ -193,7 +197,13 @@ def get_current_user_optional(
class CommonQueryParams:
"""Common query parameters for API endpoints."""
def __init__(self, skip: int = 0, limit: int = 100):
def __init__(self, skip: int = 0, limit: int = 100) -> None:
"""Create a reusable pagination parameter container.
Args:
skip: Number of records to offset when querying collections.
limit: Maximum number of records to return in a single call.
"""
self.skip = skip
self.limit = limit
@@ -235,7 +245,7 @@ async def log_request_dependency():
pass
def get_anime_service() -> object:
def get_anime_service() -> "AnimeService":
"""
Dependency to get AnimeService instance.
@@ -257,29 +267,39 @@ def get_anime_service() -> object:
import sys
import tempfile
running_tests = "PYTEST_CURRENT_TEST" in os.environ or "pytest" in sys.modules
running_tests = (
"PYTEST_CURRENT_TEST" in os.environ
or "pytest" in sys.modules
)
if running_tests:
settings.anime_directory = tempfile.gettempdir()
else:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Anime directory not configured. Please complete setup.",
detail=(
"Anime directory not configured. "
"Please complete setup."
),
)
if _anime_service is None:
try:
from src.server.services.anime_service import AnimeService
_anime_service = AnimeService(settings.anime_directory)
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to initialize AnimeService: {str(e)}",
detail=(
"Failed to initialize AnimeService: "
f"{str(e)}"
),
) from e
return _anime_service
def get_download_service() -> object:
def get_download_service() -> "DownloadService":
"""
Dependency to get DownloadService instance.
@@ -293,46 +313,49 @@ def get_download_service() -> object:
if _download_service is None:
try:
from src.server.services import (
websocket_service as websocket_service_module,
)
from src.server.services.download_service import DownloadService
from src.server.services.websocket_service import get_websocket_service
# Get anime service first (required dependency)
anime_service = get_anime_service()
# Initialize download service with anime service
_download_service = DownloadService(anime_service)
# Setup WebSocket broadcast callback
ws_service = get_websocket_service()
async def broadcast_callback(update_type: str, data: dict):
ws_service = websocket_service_module.get_websocket_service()
async def broadcast_callback(update_type: str, data: dict) -> None:
"""Broadcast download updates via WebSocket."""
if update_type == "download_progress":
await ws_service.broadcast_download_progress(
data.get("download_id", ""), data
data.get("download_id", ""),
data,
)
elif update_type == "download_complete":
await ws_service.broadcast_download_complete(
data.get("download_id", ""), data
data.get("download_id", ""),
data,
)
elif update_type == "download_failed":
await ws_service.broadcast_download_failed(
data.get("download_id", ""), data
data.get("download_id", ""),
data,
)
elif update_type == "queue_status":
await ws_service.broadcast_queue_status(data)
else:
# Generic queue update
await ws_service.broadcast_queue_status(data)
_download_service.set_broadcast_callback(broadcast_callback)
except HTTPException:
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to initialize DownloadService: {str(e)}",
detail=(
"Failed to initialize DownloadService: "
f"{str(e)}"
),
) from e
return _download_service