NoDataFile #1

Merged
lukas.pupkalipinski merged 70 commits from NoDataFile into main 2026-01-09 18:42:18 +01:00
6 changed files with 52 additions and 29 deletions
Showing only changes of commit e0a7c6baa9 - Show all commits

View File

@ -170,10 +170,10 @@ All series-related WebSocket events include `key` as the primary identifier in t
The application uses **SQLite database** as the primary storage for anime series metadata. This replaces the legacy file-based storage system. The application uses **SQLite database** as the primary storage for anime series metadata. This replaces the legacy file-based storage system.
| Storage Method | Status | Location | Purpose | | Storage Method | Status | Location | Purpose |
| -------------- | --------------------- | ------------------------- | ------------------------------ | | -------------- | --------------------- | -------------------- | ----------------------------- |
| SQLite DB | **Primary (Current)** | `data/aniworld.db` | All series metadata and state | | SQLite DB | **Primary (Current)** | `data/aniworld.db` | All series metadata and state |
| Data Files | **Deprecated** | `{anime_dir}/*/data` | Legacy per-series JSON files | | Data Files | **Deprecated** | `{anime_dir}/*/data` | Legacy per-series JSON files |
### Database Storage (Recommended) ### Database Storage (Recommended)
@ -194,9 +194,9 @@ await AnimeSeriesService.update(db_session, series_id, update_data)
The legacy file-based storage is **deprecated** and will be removed in v3.0.0: The legacy file-based storage is **deprecated** and will be removed in v3.0.0:
- `Serie.save_to_file()` - Deprecated, use `AnimeSeriesService.create()` - `Serie.save_to_file()` - Deprecated, use `AnimeSeriesService.create()`
- `Serie.load_from_file()` - Deprecated, use `AnimeSeriesService.get_by_key()` - `Serie.load_from_file()` - Deprecated, use `AnimeSeriesService.get_by_key()`
- `SerieList.add()` - Deprecated, use `SerieList.add_to_db()` - `SerieList.add()` - Deprecated, use `SerieList.add_to_db()`
Deprecation warnings are raised when using these methods. Deprecation warnings are raised when using these methods.

View File

@ -288,9 +288,10 @@ async def lifespan(app: FastAPI):
- Test duplicate key handling - Test duplicate key handling
**Implementation Notes:** **Implementation Notes:**
- Added `get_optional_database_session()` dependency in `dependencies.py` for graceful fallback
- Endpoint saves to database when available, falls back to file-based storage when not - Added `get_optional_database_session()` dependency in `dependencies.py` for graceful fallback
- All 55 API tests and 809 unit tests pass - Endpoint saves to database when available, falls back to file-based storage when not
- All 55 API tests and 809 unit tests pass
--- ---
@ -319,12 +320,13 @@ async def lifespan(app: FastAPI):
- Test dependency injection provides correct sessions - Test dependency injection provides correct sessions
**Implementation Notes:** **Implementation Notes:**
- Added `db_session` parameter to `SeriesApp.__init__()`
- Added `db_session` property and `set_db_session()` method - Added `db_session` parameter to `SeriesApp.__init__()`
- Added `init_from_db_async()` for async database initialization - Added `db_session` property and `set_db_session()` method
- Created `get_series_app_with_db()` dependency that injects database session - Added `init_from_db_async()` for async database initialization
- Added 6 new tests for database support in `test_series_app.py` - Created `get_series_app_with_db()` dependency that injects database session
- All 815 unit tests and 55 API tests pass - Added 6 new tests for database support in `test_series_app.py`
- All 815 unit tests and 55 API tests pass
--- ---
@ -351,9 +353,10 @@ async def lifespan(app: FastAPI):
- Create sample data files for migration tests - Create sample data files for migration tests
**Implementation Notes:** **Implementation Notes:**
- Added 5 new integration tests to cover all required test cases
- All 11 migration integration tests pass - Added 5 new integration tests to cover all required test cases
- All 870 tests pass (815 unit + 55 API) - All 11 migration integration tests pass
- All 870 tests pass (815 unit + 55 API)
--- ---
@ -383,10 +386,11 @@ async def lifespan(app: FastAPI):
- Verify existing file-based tests still pass ✅ - Verify existing file-based tests still pass ✅
**Implementation Notes:** **Implementation Notes:**
- Added deprecation warnings to Serie.save_to_file() and Serie.load_from_file()
- Added deprecation warning tests to test_serie_class.py - Added deprecation warnings to Serie.save_to_file() and Serie.load_from_file()
- Updated infrastructure.md with Data Storage section - Added deprecation warning tests to test_serie_class.py
- All 1012 tests pass (872 unit + 55 API + 85 integration) - Updated infrastructure.md with Data Storage section
- All 1012 tests pass (872 unit + 55 API + 85 integration)
--- ---

View File

@ -35,6 +35,7 @@ from src.core.providers.base_provider import Loader
if TYPE_CHECKING: if TYPE_CHECKING:
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from src.server.database.models import AnimeSeries from src.server.database.models import AnimeSeries
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -549,7 +550,7 @@ class SerieScanner:
Created or updated AnimeSeries instance, or None if unchanged Created or updated AnimeSeries instance, or None if unchanged
""" """
from src.server.database.service import AnimeSeriesService from src.server.database.service import AnimeSeriesService
# Check if series already exists # Check if series already exists
existing = await AnimeSeriesService.get_by_key(db, serie.key) existing = await AnimeSeriesService.get_by_key(db, serie.key)

View File

@ -23,6 +23,7 @@ from src.core.entities.series import Serie
if TYPE_CHECKING: if TYPE_CHECKING:
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from src.server.database.models import AnimeSeries from src.server.database.models import AnimeSeries
@ -147,7 +148,7 @@ class SerieList:
print(f"Added series: {result.name}") print(f"Added series: {result.name}")
""" """
from src.server.database.service import AnimeSeriesService from src.server.database.service import AnimeSeriesService
# Check if series already exists in DB # Check if series already exists in DB
existing = await AnimeSeriesService.get_by_key(db, serie.key) existing = await AnimeSeriesService.get_by_key(db, serie.key)
if existing: if existing:
@ -262,7 +263,7 @@ class SerieList:
print(f"Loaded {count} series from database") print(f"Loaded {count} series from database")
""" """
from src.server.database.service import AnimeSeriesService from src.server.database.service import AnimeSeriesService
# Clear existing in-memory data # Clear existing in-memory data
self.keyDict.clear() self.keyDict.clear()

View File

@ -51,6 +51,15 @@ async def lifespan(app: FastAPI):
try: try:
logger.info("Starting FastAPI application...") logger.info("Starting FastAPI application...")
# Initialize database first (required for migration and other services)
try:
from src.server.database.connection import init_db
await init_db()
logger.info("Database initialized successfully")
except Exception as e:
logger.error("Failed to initialize database: %s", e, exc_info=True)
raise # Database is required, fail startup if it fails
# Load configuration from config.json and sync with settings # Load configuration from config.json and sync with settings
try: try:
from src.server.services.config_service import get_config_service from src.server.services.config_service import get_config_service
@ -128,6 +137,14 @@ async def lifespan(app: FastAPI):
except Exception as e: except Exception as e:
logger.error("Error stopping download service: %s", e, exc_info=True) logger.error("Error stopping download service: %s", e, exc_info=True)
# Close database connections
try:
from src.server.database.connection import close_db
await close_db()
logger.info("Database connections closed")
except Exception as e:
logger.error("Error closing database: %s", e, exc_info=True)
logger.info("FastAPI application shutdown complete") logger.info("FastAPI application shutdown complete")

View File

@ -291,8 +291,8 @@ class TestScanSavesToDatabase:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_scan_async_saves_to_database(self): async def test_scan_async_saves_to_database(self):
"""Test scan_async method saves series to database.""" """Test scan_async method saves series to database."""
from src.core.SerieScanner import SerieScanner
from src.core.entities.series import Serie from src.core.entities.series import Serie
from src.core.SerieScanner import SerieScanner
with tempfile.TemporaryDirectory() as tmp_dir: with tempfile.TemporaryDirectory() as tmp_dir:
# Create series folder structure # Create series folder structure
@ -341,7 +341,7 @@ class TestSerieListReadsFromDatabase:
async def test_load_series_from_db(self): async def test_load_series_from_db(self):
"""Test SerieList.load_series_from_db() method.""" """Test SerieList.load_series_from_db() method."""
from src.core.entities.SerieList import SerieList from src.core.entities.SerieList import SerieList
# Create mock database session # Create mock database session
mock_db = AsyncMock() mock_db = AsyncMock()
@ -408,8 +408,8 @@ class TestSearchAndAddWorkflow:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_search_and_add_workflow(self): async def test_search_and_add_workflow(self):
"""Test searching for anime and adding it saves to database.""" """Test searching for anime and adding it saves to database."""
from src.core.SeriesApp import SeriesApp
from src.core.entities.series import Serie from src.core.entities.series import Serie
from src.core.SeriesApp import SeriesApp
with tempfile.TemporaryDirectory() as tmp_dir: with tempfile.TemporaryDirectory() as tmp_dir:
# Mock database # Mock database