feat: Implement SQLAlchemy database layer with comprehensive models
Implemented a complete database layer for persistent storage of anime series, episodes, download queue, and user sessions using SQLAlchemy ORM. Features: - 4 SQLAlchemy models: AnimeSeries, Episode, DownloadQueueItem, UserSession - Automatic timestamp tracking via TimestampMixin - Foreign key relationships with cascade deletes - Async and sync database session support - FastAPI dependency injection integration - SQLite optimizations (WAL mode, foreign keys) - Enum types for status and priority fields Models: - AnimeSeries: Series metadata with one-to-many relationships - Episode: Individual episodes linked to series - DownloadQueueItem: Queue persistence with progress tracking - UserSession: JWT session storage with expiry and revocation Database Management: - Async engine creation with aiosqlite - Session factory with proper lifecycle - Connection pooling configuration - Automatic table creation on initialization Testing: - 19 comprehensive unit tests (all passing) - In-memory SQLite for test isolation - Relationship and constraint validation - Query operation testing Documentation: - Comprehensive database section in infrastructure.md - Database package README with examples - Implementation summary document - Usage guides and troubleshooting Dependencies: - Added: sqlalchemy>=2.0.35 (Python 3.13 compatible) - Added: alembic==1.13.0 (for future migrations) - Added: aiosqlite>=0.19.0 (async SQLite driver) Files: - src/server/database/__init__.py (package exports) - src/server/database/base.py (base classes and mixins) - src/server/database/models.py (ORM models, ~435 lines) - src/server/database/connection.py (connection management) - src/server/database/migrations.py (migration placeholder) - src/server/database/README.md (package documentation) - tests/unit/test_database_models.py (19 test cases) - DATABASE_IMPLEMENTATION_SUMMARY.md (implementation summary) Closes #9 Database Layer implementation task
This commit is contained in:
74
src/server/database/base.py
Normal file
74
src/server/database/base.py
Normal file
@@ -0,0 +1,74 @@
|
||||
"""Base SQLAlchemy declarative base for all database models.
|
||||
|
||||
This module provides the base class that all ORM models inherit from,
|
||||
along with common functionality and mixins.
|
||||
"""
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import DateTime, func
|
||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
||||
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
"""Base class for all SQLAlchemy ORM models.
|
||||
|
||||
Provides common functionality and type annotations for all models.
|
||||
All models should inherit from this class.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class TimestampMixin:
|
||||
"""Mixin to add created_at and updated_at timestamp columns.
|
||||
|
||||
Automatically tracks when records are created and updated.
|
||||
Use this mixin for models that need audit timestamps.
|
||||
|
||||
Attributes:
|
||||
created_at: Timestamp when record was created
|
||||
updated_at: Timestamp when record was last updated
|
||||
"""
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
server_default=func.now(),
|
||||
nullable=False,
|
||||
doc="Timestamp when record was created"
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
server_default=func.now(),
|
||||
onupdate=func.now(),
|
||||
nullable=False,
|
||||
doc="Timestamp when record was last updated"
|
||||
)
|
||||
|
||||
|
||||
class SoftDeleteMixin:
|
||||
"""Mixin to add soft delete functionality.
|
||||
|
||||
Instead of deleting records, marks them as deleted with a timestamp.
|
||||
Useful for maintaining audit trails and allowing recovery.
|
||||
|
||||
Attributes:
|
||||
deleted_at: Timestamp when record was soft deleted, None if active
|
||||
"""
|
||||
deleted_at: Mapped[datetime | None] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=True,
|
||||
default=None,
|
||||
doc="Timestamp when record was soft deleted"
|
||||
)
|
||||
|
||||
@property
|
||||
def is_deleted(self) -> bool:
|
||||
"""Check if record is soft deleted."""
|
||||
return self.deleted_at is not None
|
||||
|
||||
def soft_delete(self) -> None:
|
||||
"""Mark record as deleted without removing from database."""
|
||||
self.deleted_at = datetime.utcnow()
|
||||
|
||||
def restore(self) -> None:
|
||||
"""Restore a soft deleted record."""
|
||||
self.deleted_at = None
|
||||
Reference in New Issue
Block a user