Phase 8: Documentation and deprecation warnings for identifier standardization

- Enhanced infrastructure.md with identifier convention table, format requirements, migration notes
- Updated docs/README.md with series identifier convention section
- Updated docs/api_reference.md with key-based API examples and notes
- Added deprecation warnings to SerieList.get_by_folder()
- Added deprecation warnings to anime.py folder fallback lookup
- Added deprecation warnings to validate_series_key_or_folder()
- All warnings include v3.0.0 removal timeline
- All 1006 tests pass
This commit is contained in:
Lukas 2025-11-28 18:06:04 +01:00
parent ddff43595f
commit 85a6b053eb
7 changed files with 220 additions and 118 deletions

View File

@ -97,6 +97,42 @@ Production deployment instructions covering:
- Status notifications
- Error alerts
## Series Identifier Convention
### Understanding Series Identifiers
The application uses two identifiers for anime series:
| Identifier | Purpose | Example | Used For |
| ---------- | ------------------------ | -------------------------- | ----------------- |
| `key` | **Primary identifier** | `"attack-on-titan"` | All API lookups |
| `folder` | Filesystem metadata only | `"Attack on Titan (2013)"` | Display purposes |
### Key Format
- Lowercase letters and numbers only
- Words separated by hyphens (`-`)
- No spaces, underscores, or uppercase letters
- Examples: `"one-piece"`, `"86-eighty-six"`, `"re-zero"`
### Usage Guidelines
```python
# ✅ Correct: Use 'key' for API operations
GET /api/anime/attack-on-titan
POST /api/queue/add {"serie_id": "attack-on-titan", ...}
# ❌ Deprecated: Folder-based lookups (backward compatibility only)
GET /api/anime/Attack%20on%20Titan%20(2013) # Will work but deprecated
```
### Backward Compatibility
For existing integrations, folder-based lookups are still supported but deprecated:
- API endpoints check `key` first, then fall back to `folder`
- New code should always use `key` as the identifier
- Deprecation warnings will be added in future versions
## Documentation Examples
### API Usage Example
@ -285,8 +321,8 @@ docs/
## Document Info
- **Last Updated**: October 22, 2025
- **Version**: 1.0.0
- **Last Updated**: November 28, 2025
- **Version**: 2.0.0
- **Status**: Production Ready
- **Maintainers**: Development Team

View File

@ -421,12 +421,16 @@ Authorization: Bearer <token>
### Anime Endpoints
> **Note on Identifiers**: All anime endpoints use `key` as the primary series identifier (e.g., `"attack-on-titan"`).
> The `folder` field is metadata only and should not be used for lookups.
> For backward compatibility, folder-based lookups are supported but deprecated.
#### List Anime with Missing Episodes
Lists all anime series with missing episodes.
```http
GET /api/v1/anime
GET /api/anime
Authorization: Bearer <token>
```
@ -442,14 +446,18 @@ Authorization: Bearer <token>
```json
[
{
"id": "aniworld_123",
"title": "Attack on Titan",
"missing_episodes": 5
"key": "attack-on-titan",
"name": "Attack on Titan",
"folder": "Attack on Titan (2013)",
"missing_episodes": 5,
"link": "https://aniworld.to/anime/stream/attack-on-titan"
},
{
"id": "aniworld_456",
"title": "Demon Slayer",
"missing_episodes": 2
"key": "demon-slayer",
"name": "Demon Slayer",
"folder": "Demon Slayer (2019)",
"missing_episodes": 2,
"link": "https://aniworld.to/anime/stream/demon-slayer"
}
]
```
@ -461,20 +469,23 @@ Authorization: Bearer <token>
Retrieves detailed information for a specific anime series.
```http
GET /api/v1/anime/{anime_id}
GET /api/anime/{anime_id}
Authorization: Bearer <token>
```
**Path Parameters**:
- `anime_id` (string): Series `key` (preferred) or `folder` (deprecated)
**Response (200 OK)**:
```json
{
"id": "aniworld_123",
"key": "attack-on-titan",
"title": "Attack on Titan",
"episodes": ["Season 1 Episode 1", "Season 1 Episode 2"],
"description": "Anime description...",
"total_episodes": 100,
"downloaded_episodes": 95
"folder": "Attack on Titan (2013)",
"episodes": ["S01-E01", "S01-E02", "S02-E01"],
"description": "Anime description..."
}
```
@ -489,7 +500,7 @@ Authorization: Bearer <token>
Rescans the local anime directory for new series and episodes.
```http
POST /api/v1/anime/rescan
POST /api/anime/rescan
Authorization: Bearer <token>
```
@ -539,6 +550,9 @@ Authorization: Bearer <token>
### Download Queue Endpoints
> **Note on Identifiers**: Download queue operations use `serie_id` which should be the series `key` (e.g., `"attack-on-titan"`).
> The `serie_folder` field is filesystem metadata and should not be used for identification.
#### Get Queue Status
Retrieves download queue status and statistics.
@ -577,13 +591,21 @@ Authorization: Bearer <token>
Content-Type: application/json
{
"anime_id": "aniworld_123",
"serie_id": "attack-on-titan",
"serie_folder": "Attack on Titan (2013)",
"serie_name": "Attack on Titan",
"episodes": ["S01E01", "S01E02"],
"priority": "normal"
"priority": 1
}
```
**Priority Values**: `low`, `normal`, `high`
**Request Fields**:
- `serie_id` (string, required): Series `key` - primary identifier
- `serie_folder` (string, optional): Filesystem folder name (metadata)
- `serie_name` (string, required): Display name
- `episodes` (array, required): List of episodes to download
- `priority` (integer, optional): Priority level (default: 0)
**Response (201 Created)**:
@ -592,7 +614,7 @@ Content-Type: application/json
"success": true,
"data": {
"queue_item_id": "queue_456",
"anime_id": "aniworld_123",
"serie_id": "attack-on-titan",
"status": "pending"
}
}
@ -682,18 +704,21 @@ Authorization: Bearer <token>
### WebSocket Endpoints
> **Note on Identifiers**: All WebSocket events include `key` as the primary series identifier.
> The `folder` field is included as metadata but should not be used for identification.
#### Real-Time Progress Updates
Establishes WebSocket connection for real-time download progress updates.
```
WS /ws/downloads
WS /ws/connect
```
**Connection**:
```javascript
const ws = new WebSocket("ws://localhost:8000/ws/downloads");
const ws = new WebSocket("ws://localhost:8000/ws/connect");
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
@ -701,6 +726,8 @@ ws.onmessage = (event) => {
};
```
**Rooms**: `downloads`, `download_progress`, `scan_progress`
**Message Types**:
**Download Started**:
@ -710,8 +737,9 @@ ws.onmessage = (event) => {
"type": "download_started",
"timestamp": "2025-10-22T12:00:00Z",
"data": {
"queue_item_id": "queue_456",
"anime_title": "Attack on Titan",
"download_id": "dl_456",
"key": "attack-on-titan",
"folder": "Attack on Titan (2013)",
"episode": "S01E01"
}
}
@ -724,11 +752,12 @@ ws.onmessage = (event) => {
"type": "download_progress",
"timestamp": "2025-10-22T12:00:05Z",
"data": {
"queue_item_id": "queue_456",
"progress_percent": 45,
"downloaded_bytes": 500000000,
"total_bytes": 1100000000,
"speed_mbps": 5.5
"download_id": "dl_456",
"key": "attack-on-titan",
"folder": "Attack on Titan (2013)",
"percent": 45.2,
"speed_mbps": 5.5,
"eta_seconds": 180
}
}
```
@ -737,11 +766,12 @@ ws.onmessage = (event) => {
```json
{
"type": "download_completed",
"type": "download_complete",
"timestamp": "2025-10-22T12:05:00Z",
"data": {
"queue_item_id": "queue_456",
"total_time_seconds": 300,
"download_id": "dl_456",
"key": "attack-on-titan",
"folder": "Attack on Titan (2013)",
"file_path": "/path/to/anime/file.mkv"
}
}
@ -751,15 +781,17 @@ ws.onmessage = (event) => {
```json
{
"type": "download_error",
"type": "download_failed",
"timestamp": "2025-10-22T12:05:00Z",
"data": {
"queue_item_id": "queue_456",
"error_message": "Connection timeout",
"error_code": "PROVIDER_ERROR"
"download_id": "dl_456",
"key": "attack-on-titan",
"folder": "Attack on Titan (2013)",
"error": "Connection timeout"
}
}
```
```
---

View File

@ -41,12 +41,30 @@ tests/ # Test suites
## Series Identifier Convention
Throughout the codebase, two identifiers are used for anime series:
Throughout the codebase, three identifiers are used for anime series:
- **`key`**: Primary identifier (provider-assigned, URL-safe, e.g., `"attack-on-titan"`)
- **`folder`**: Display/filesystem metadata only (e.g., `"Attack on Titan (2013)"`)
| Identifier | Type | Purpose | Example |
| ---------- | --------------- | --------------------------------------------------------- | ----------------------------- |
| `key` | Unique, Indexed | **PRIMARY** - All lookups, API operations, WebSocket events | `"attack-on-titan"` |
| `folder` | String | Display/filesystem metadata only (never for lookups) | `"Attack on Titan (2013)"` |
| `id` | Primary Key | Internal database key for relationships | `1`, `42` |
All lookups, events, and API operations use `key`. The `folder` is metadata for display purposes.
### Key Format Requirements
- **Lowercase only**: No uppercase letters allowed
- **URL-safe**: Only alphanumeric characters and hyphens
- **Hyphen-separated**: Words separated by single hyphens
- **No leading/trailing hyphens**: Must start and end with alphanumeric
- **No consecutive hyphens**: `attack--titan` is invalid
**Valid examples**: `"attack-on-titan"`, `"one-piece"`, `"86-eighty-six"`, `"re-zero"`
**Invalid examples**: `"Attack On Titan"`, `"attack_on_titan"`, `"attack on titan"`
### Migration Notes
- **Backward Compatibility**: API endpoints accepting `anime_id` will check `key` first, then fall back to `folder` lookup
- **Deprecation**: Folder-based lookups are deprecated and will be removed in a future version
- **New Code**: Always use `key` for identification; `folder` is metadata only
## API Endpoints
@ -282,3 +300,38 @@ conda run -n AniWorld python -m pytest tests/api/ -v
- Move WebSocket registry to Redis
- Use distributed locking for queue operations
- Consider Redis for session/cache storage
## Code Examples
### API Usage with Key Identifier
```python
# Fetching anime list - response includes 'key' as identifier
response = requests.get("/api/anime", headers={"Authorization": f"Bearer {token}"})
anime_list = response.json()
# Each item has: key="attack-on-titan", folder="Attack on Titan (2013)", ...
# Fetching specific anime by key (preferred)
response = requests.get("/api/anime/attack-on-titan", headers={"Authorization": f"Bearer {token}"})
# Adding to download queue using key
download_request = {
"serie_id": "attack-on-titan", # Use key, not folder
"serie_folder": "Attack on Titan (2013)", # Metadata for filesystem
"serie_name": "Attack on Titan",
"episodes": ["S01E01", "S01E02"],
"priority": 1
}
response = requests.post("/api/queue/add", json=download_request, headers=headers)
```
### WebSocket Event Handling
```javascript
// WebSocket events always include 'key' as identifier
socket.on("download_progress", (data) => {
const key = data.key; // Primary identifier: "attack-on-titan"
const folder = data.folder; // Metadata: "Attack on Titan (2013)"
updateProgressBar(key, data.percent);
});
```

View File

@ -248,75 +248,18 @@ conda run -n AniWorld python -m pytest tests/integration/test_identifier_consist
---
### Phase 8: Documentation and Cleanup
### Phase 8: Documentation and Cleanup ✅ **Completed November 28, 2025**
#### Task 8.1: Update Infrastructure Documentation
All tasks completed:
**File:** [`infrastructure.md`](infrastructure.md)
- **Task 8.1**: Updated `infrastructure.md` with enhanced identifier convention section including table format, key format requirements, migration notes, and code examples
- **Task 8.2**: Updated `docs/README.md` with series identifier convention section, updated `docs/api_reference.md` with key-based API examples
- **Task 8.3**: Added deprecation warnings with Python's `warnings` module to:
- `SerieList.get_by_folder()` in `src/core/entities/SerieList.py`
- Folder fallback lookup in `src/server/api/anime.py`
- `validate_series_key_or_folder()` in `src/server/utils/validators.py`
**Objective:** Document the identifier standardization in infrastructure documentation.
**Steps:**
1. Open [`infrastructure.md`](infrastructure.md)
2. Add section explaining identifier usage:
- `key`: Unique series identifier (provider-assigned)
- `folder`: Filesystem folder name (metadata only)
- `id`: Database primary key (internal use)
3. Update all API documentation to show `key` as identifier
4. Update data model documentation
5. Add migration notes if needed
**Success Criteria:**
- [ ] Documentation clearly explains identifier usage
- [ ] All API examples use `key`
- [ ] Data model section updated
- [ ] Migration notes added if applicable
---
#### Task 8.2: Update README and Developer Documentation
**Files:** [`README.md`](README.md), [`docs/`](docs/)
**Objective:** Update all developer-facing documentation.
**Steps:**
1. Update main README to explain identifier usage
2. Update any developer guides to use `key`
3. Add note about backward compatibility with `folder`
4. Update code examples to use `key`
**Success Criteria:**
- [ ] README updated
- [ ] Developer guides updated
- [ ] Code examples use `key`
- [ ] Backward compatibility documented
---
#### Task 8.3: Add Deprecation Warnings for Folder-Based Lookups
**Files:** Various service and API files
**Objective:** Add deprecation warnings where `folder` is still accepted for backward compatibility.
**Steps:**
1. Identify all methods that accept `folder` for lookups
2. Add deprecation warnings using Python's `warnings` module
3. Update docstrings to indicate deprecation
4. Plan removal timeline (e.g., next major version)
**Success Criteria:**
- [ ] Deprecation warnings added
- [ ] Docstrings indicate deprecation
- [ ] Removal timeline documented
- [ ] Tests updated to suppress warnings
All deprecation warnings include removal timeline (v3.0.0) and guidance to use `key` instead.
---
@ -482,18 +425,11 @@ conda run -n AniWorld python -m pytest tests/integration/test_identifier_consist
- [x] Phase 3: Service Layer ✅
- [x] Phase 4: API Layer ✅ **Completed November 28, 2025**
- [x] Phase 5: Frontend ✅ **Completed November 28, 2025**
- [x] Task 5.1: Update Frontend JavaScript
- [x] Task 5.2: Update WebSocket Events
- [x] Task 5.3: Update Additional Frontend JavaScript Files
- [x] Task 5.4: Update HTML Templates
- [x] Phase 6: Database Layer ✅ **Completed November 28, 2025**
- [x] Task 6.1: Verify Database Models
- [x] Task 6.2: Update Database Services
- [x] Phase 7: Testing and Validation ✅ **Completed November 28, 2025**
- [x] Task 7.1: Update Test Fixtures - Updated all test fixtures and mocks to use `key` consistently with realistic key values (URL-safe, lowercase, hyphenated)
- [x] Task 7.2: Add Integration Tests - Created `tests/integration/test_identifier_consistency.py` with 10 tests verifying `key` usage across all layers
- [ ] Phase 8: Documentation and Cleanup
- [ ] Task 8.1: Update Infrastructure Documentation
- [ ] Task 8.2: Update README
- [ ] Task 8.3: Add Deprecation Warnings
- [x] Phase 8: Documentation and Cleanup ✅ **Completed November 28, 2025**
- [x] Task 8.1: Update Infrastructure Documentation - Enhanced identifier convention section with table, format requirements, migration notes, and code examples
- [x] Task 8.2: Update README and Developer Docs - Updated docs/README.md with identifier section, updated api_reference.md with key-based examples
- [x] Task 8.3: Add Deprecation Warnings - Added warnings to SerieList.get_by_folder(), anime.py folder fallback, and validators.py
- [ ] Phase 9: Final Validation
- [ ] Phase 10: Deployment

View File

@ -2,6 +2,7 @@
import logging
import os
import warnings
from json import JSONDecodeError
from typing import Dict, Iterable, List, Optional
@ -144,6 +145,11 @@ class SerieList:
"""
Get a series by its folder name.
.. deprecated:: 2.0.0
Use :meth:`get_by_key` instead. Folder-based lookups will be
removed in version 3.0.0. The `folder` field is metadata only
and should not be used for identification.
This method is provided for backward compatibility only.
Prefer using get_by_key() for new code.
@ -153,6 +159,12 @@ class SerieList:
Returns:
The Serie instance if found, None otherwise
"""
warnings.warn(
"get_by_folder() is deprecated and will be removed in v3.0.0. "
"Use get_by_key() instead. The 'folder' field is metadata only.",
DeprecationWarning,
stacklevel=2
)
for serie in self.keyDict.values():
if serie.folder == folder:
return serie

View File

@ -1,3 +1,5 @@
import logging
import warnings
from typing import Any, List, Optional
from fastapi import APIRouter, Depends, HTTPException, status
@ -11,6 +13,8 @@ from src.server.utils.dependencies import (
require_auth,
)
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/anime", tags=["anime"])
@ -715,6 +719,21 @@ async def get_anime(
for serie in series:
if getattr(serie, "folder", None) == anime_id:
found = serie
# Log deprecation warning for folder-based lookup
key = getattr(serie, "key", "unknown")
logger.warning(
"Folder-based lookup for '%s' is deprecated. "
"Use series key '%s' instead. Folder-based lookups "
"will be removed in v3.0.0.",
anime_id,
key
)
warnings.warn(
f"Folder-based lookup for '{anime_id}' is deprecated. "
f"Use series key '{key}' instead.",
DeprecationWarning,
stacklevel=2
)
break
if not found:

View File

@ -6,6 +6,7 @@ utilities for ensuring data integrity across the application.
"""
import re
import warnings
from pathlib import Path
from typing import Any, Dict, List, Optional
@ -496,6 +497,10 @@ def validate_series_key_or_folder(
"""
Validate an identifier that could be either a series key or folder.
.. deprecated:: 2.0.0
Folder-based identification is deprecated. Use series `key` only.
This function will require key format only in v3.0.0.
This function provides backward compatibility during the transition
from folder-based to key-based identification.
@ -532,6 +537,15 @@ def validate_series_key_or_folder(
"Keys must be lowercase with hyphens (e.g., 'attack-on-titan')."
)
# Emit deprecation warning for folder-based identification
warnings.warn(
f"Folder-based identification for '{identifier}' is deprecated. "
"Use series key (lowercase with hyphens) instead. "
"Folder-based identification will be removed in v3.0.0.",
DeprecationWarning,
stacklevel=2
)
# Validate as folder (more permissive)
if len(identifier) > 1000:
raise ValueError("Identifier too long (max 1000 characters)")