- Add async context manager to NFOService wrapping TMDBClient + ImageDownloader - Add TMDBClient.__del__ warning when session leaks - Log exc_info on session recreation for traceback visibility - Document async-with usage in docs/DEVELOPMENT.md and docs/TESTING.md - Add unit tests covering leak detection, context-manager cleanup, and connector-closed warning Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
146 lines
4.7 KiB
Markdown
146 lines
4.7 KiB
Markdown
# Development Guide
|
|
|
|
## Document Purpose
|
|
|
|
This document provides guidance for developers working on the Aniworld project.
|
|
|
|
### What This Document Contains
|
|
|
|
- **Prerequisites**: Required software and tools
|
|
- **Environment Setup**: Step-by-step local development setup
|
|
- **Project Structure**: Source code organization explanation
|
|
- **Development Workflow**: Branch strategy, commit conventions
|
|
- **Coding Standards**: Style guide, linting, formatting
|
|
- **Running the Application**: Development server, CLI usage
|
|
- **Debugging Tips**: Common debugging approaches
|
|
- **IDE Configuration**: VS Code settings, recommended extensions
|
|
- **Contributing Guidelines**: How to submit changes
|
|
- **Code Review Process**: Review checklist and expectations
|
|
|
|
### What This Document Does NOT Contain
|
|
|
|
- Production deployment (see [DEPLOYMENT.md](DEPLOYMENT.md))
|
|
- API reference (see [API.md](API.md))
|
|
- Architecture decisions (see [ARCHITECTURE.md](ARCHITECTURE.md))
|
|
- Test writing guides (see [TESTING.md](TESTING.md))
|
|
- Security guidelines (see [SECURITY.md](SECURITY.md))
|
|
|
|
### Target Audience
|
|
|
|
- New Developers joining the project
|
|
- Contributors (internal and external)
|
|
- Anyone setting up a development environment
|
|
|
|
---
|
|
|
|
## Sections to Document
|
|
|
|
1. Prerequisites
|
|
- Python version
|
|
- Conda environment
|
|
- Node.js (if applicable)
|
|
- Git
|
|
2. Getting Started
|
|
- Clone repository
|
|
- Setup conda environment
|
|
- Install dependencies
|
|
- Configuration setup
|
|
3. Project Structure Overview
|
|
4. Development Server
|
|
- Starting FastAPI server
|
|
- Hot reload configuration
|
|
- Debug mode
|
|
5. CLI Development
|
|
6. Code Style
|
|
- PEP 8 compliance
|
|
- Type hints requirements
|
|
- Docstring format
|
|
- Import organization
|
|
7. Git Workflow
|
|
- Branch naming
|
|
- Commit message format
|
|
- Pull request process
|
|
8. Common Development Tasks
|
|
|
|
### Adding Queue Deduplication
|
|
|
|
The download queue prevents duplicate entries at two levels:
|
|
|
|
**In-Memory Deduplication** (`src/server/services/download_service.py`):
|
|
- `_pending_by_episode` dict tracks pending episodes: key = `(serie_id, season, episode)`
|
|
- `_add_to_pending_queue()` updates the dict when adding items
|
|
- `add_to_queue()` checks this dict before adding episodes (includes batch-local dedup)
|
|
- `_remove_from_pending_queue()` cleans up the dict when items are removed
|
|
|
|
**Database Constraint** (`src/server/models.py`):
|
|
- `DownloadQueueItem` has a unique index on `episode_id` via `__table_args__`
|
|
- Prevents duplicate queue entries at the database level
|
|
- Unique constraint: `Index("ix_download_queue_episode_pending", "episode_id", unique=True)`
|
|
|
|
**Scheduler Cooldown** (`src/server/services/scheduler_service.py`):
|
|
- `_last_auto_download_time` tracks when auto-download last ran
|
|
- 5-minute cooldown prevents rapid re-triggers
|
|
- Checked at start of `_auto_download_missing()`
|
|
|
|
### Mocking the Download Queue
|
|
|
|
When testing components that use the download queue:
|
|
|
|
```python
|
|
# Mock repository for unit tests
|
|
class MockQueueRepository:
|
|
def __init__(self):
|
|
self._items: Dict[str, DownloadItem] = {}
|
|
|
|
async def save_item(self, item: DownloadItem) -> DownloadItem:
|
|
self._items[item.id] = item
|
|
return item
|
|
|
|
async def get_all_items(self) -> List[DownloadItem]:
|
|
return list(self._items.values())
|
|
|
|
# Use in fixture
|
|
@pytest.fixture
|
|
def mock_queue_repository():
|
|
return MockQueueRepository()
|
|
|
|
@pytest.fixture
|
|
def download_service(mock_anime_service, mock_queue_repository):
|
|
return DownloadService(
|
|
anime_service=mock_anime_service,
|
|
queue_repository=mock_queue_repository,
|
|
max_retries=3,
|
|
)
|
|
```
|
|
|
|
9. Troubleshooting Development Issues
|
|
|
|
### Async Context Managers for aiohttp
|
|
|
|
All `aiohttp.ClientSession` usages must be wrapped in `async with`:
|
|
|
|
```python
|
|
# Correct — session properly closed on exit
|
|
async with TMDBClient(api_key="key") as client:
|
|
result = await client.search_tv_show("Show")
|
|
|
|
# Wrong — session may leak if exception occurs
|
|
client = TMDBClient(api_key="key")
|
|
result = await client.search_tv_show("Show")
|
|
await client.close() # May not be called if exception raised earlier
|
|
```
|
|
|
|
**Why:**
|
|
- `aiohttp.ClientSession` holds TCP connections that must be explicitly closed
|
|
- If exception occurs before `close()`, session leaks
|
|
- Context manager guarantees `__aexit__` runs even on exceptions
|
|
|
|
**Services that use aiohttp:**
|
|
- `TMDBClient` — has `__aenter__`/`__aexit__`, use `async with`
|
|
- `ImageDownloader` — has `__aenter__`/`__aexit__`, use `async with`
|
|
- `NFOService` — wraps both above, use `async with`
|
|
|
|
**Verification:**
|
|
- Missing context manager usage triggers `__del__` warning on garbage collection
|
|
- Integration tests verify no "Unclosed client session" errors in logs
|