feat(core): Add database support to SeriesApp (Task 7)
- Added db_session parameter to SeriesApp.__init__() - Added db_session property and set_db_session() method - Added init_from_db_async() for async database initialization - Pass db_session to SerieList and SerieScanner during construction - Added get_series_app_with_db() dependency for FastAPI endpoints - All 815 unit tests and 55 API tests pass
This commit is contained in:
@@ -8,10 +8,16 @@ progress reporting, and error handling.
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import warnings
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from events import Events
|
||||
|
||||
try:
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
except ImportError: # pragma: no cover - optional dependency
|
||||
AsyncSession = object # type: ignore
|
||||
|
||||
from src.core.entities.SerieList import SerieList
|
||||
from src.core.entities.series import Serie
|
||||
from src.core.providers.provider_factory import Loaders
|
||||
@@ -130,15 +136,20 @@ class SeriesApp:
|
||||
def __init__(
|
||||
self,
|
||||
directory_to_search: str,
|
||||
db_session: Optional[AsyncSession] = None,
|
||||
):
|
||||
"""
|
||||
Initialize SeriesApp.
|
||||
|
||||
Args:
|
||||
directory_to_search: Base directory for anime series
|
||||
db_session: Optional database session for database-backed
|
||||
storage. When provided, SerieList and SerieScanner will
|
||||
use the database instead of file-based storage.
|
||||
"""
|
||||
|
||||
self.directory_to_search = directory_to_search
|
||||
self._db_session = db_session
|
||||
|
||||
# Initialize events
|
||||
self._events = Events()
|
||||
@@ -147,15 +158,20 @@ class SeriesApp:
|
||||
|
||||
self.loaders = Loaders()
|
||||
self.loader = self.loaders.GetLoader(key="aniworld.to")
|
||||
self.serie_scanner = SerieScanner(directory_to_search, self.loader)
|
||||
self.list = SerieList(self.directory_to_search)
|
||||
self.serie_scanner = SerieScanner(
|
||||
directory_to_search, self.loader, db_session=db_session
|
||||
)
|
||||
self.list = SerieList(
|
||||
self.directory_to_search, db_session=db_session
|
||||
)
|
||||
# Synchronous init used during constructor to avoid awaiting
|
||||
# in __init__
|
||||
self._init_list_sync()
|
||||
|
||||
logger.info(
|
||||
"SeriesApp initialized for directory: %s",
|
||||
directory_to_search
|
||||
"SeriesApp initialized for directory: %s (db_session: %s)",
|
||||
directory_to_search,
|
||||
"provided" if db_session else "none"
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -188,6 +204,53 @@ class SeriesApp:
|
||||
"""Set scan_status event handler."""
|
||||
self._events.scan_status = value
|
||||
|
||||
@property
|
||||
def db_session(self) -> Optional[AsyncSession]:
|
||||
"""
|
||||
Get the database session.
|
||||
|
||||
Returns:
|
||||
AsyncSession or None: The database session if configured
|
||||
"""
|
||||
return self._db_session
|
||||
|
||||
def set_db_session(self, session: Optional[AsyncSession]) -> None:
|
||||
"""
|
||||
Update the database session.
|
||||
|
||||
Also updates the db_session on SerieList and SerieScanner.
|
||||
|
||||
Args:
|
||||
session: The new database session or None
|
||||
"""
|
||||
self._db_session = session
|
||||
self.list._db_session = session
|
||||
self.serie_scanner._db_session = session
|
||||
logger.debug(
|
||||
"Database session updated: %s",
|
||||
"provided" if session else "none"
|
||||
)
|
||||
|
||||
async def init_from_db_async(self) -> None:
|
||||
"""
|
||||
Initialize series list from database (async).
|
||||
|
||||
This should be called when using database storage instead of
|
||||
the synchronous file-based initialization.
|
||||
"""
|
||||
if self._db_session:
|
||||
await self.list.load_series_from_db(self._db_session)
|
||||
self.series_list = self.list.GetMissingEpisode()
|
||||
logger.debug(
|
||||
"Loaded %d series with missing episodes from database",
|
||||
len(self.series_list)
|
||||
)
|
||||
else:
|
||||
warnings.warn(
|
||||
"init_from_db_async called without db_session configured",
|
||||
UserWarning
|
||||
)
|
||||
|
||||
def _init_list_sync(self) -> None:
|
||||
"""Synchronous initialization helper for constructor."""
|
||||
self.series_list = self.list.GetMissingEpisode()
|
||||
|
||||
@@ -65,6 +65,10 @@ def get_series_app() -> SeriesApp:
|
||||
Raises:
|
||||
HTTPException: If SeriesApp is not initialized or anime directory
|
||||
is not configured
|
||||
|
||||
Note:
|
||||
This creates a SeriesApp without database support. For database-
|
||||
backed storage, use get_series_app_with_db() instead.
|
||||
"""
|
||||
global _series_app
|
||||
|
||||
@@ -103,7 +107,6 @@ def reset_series_app() -> None:
|
||||
_series_app = None
|
||||
|
||||
|
||||
|
||||
async def get_database_session() -> AsyncGenerator:
|
||||
"""
|
||||
Dependency to get database session.
|
||||
@@ -166,6 +169,43 @@ async def get_optional_database_session() -> AsyncGenerator:
|
||||
yield None
|
||||
|
||||
|
||||
async def get_series_app_with_db(
|
||||
db: AsyncSession = Depends(get_optional_database_session),
|
||||
) -> SeriesApp:
|
||||
"""
|
||||
Dependency to get SeriesApp instance with database support.
|
||||
|
||||
This creates or returns a SeriesApp instance and injects the
|
||||
database session for database-backed storage.
|
||||
|
||||
Args:
|
||||
db: Optional database session from dependency injection
|
||||
|
||||
Returns:
|
||||
SeriesApp: The main application instance with database support
|
||||
|
||||
Raises:
|
||||
HTTPException: If SeriesApp is not initialized or anime directory
|
||||
is not configured
|
||||
|
||||
Example:
|
||||
@app.post("/api/anime/scan")
|
||||
async def scan_anime(
|
||||
series_app: SeriesApp = Depends(get_series_app_with_db)
|
||||
):
|
||||
# series_app has db_session configured
|
||||
await series_app.serie_scanner.scan_async()
|
||||
"""
|
||||
# Get the base SeriesApp
|
||||
app = get_series_app()
|
||||
|
||||
# Inject database session if available
|
||||
if db:
|
||||
app.set_db_session(db)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
def get_current_user(
|
||||
credentials: Optional[HTTPAuthorizationCredentials] = Depends(
|
||||
http_bearer_security
|
||||
|
||||
Reference in New Issue
Block a user