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 - Status notifications
- Error alerts - 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 ## Documentation Examples
### API Usage Example ### API Usage Example
@ -285,8 +321,8 @@ docs/
## Document Info ## Document Info
- **Last Updated**: October 22, 2025 - **Last Updated**: November 28, 2025
- **Version**: 1.0.0 - **Version**: 2.0.0
- **Status**: Production Ready - **Status**: Production Ready
- **Maintainers**: Development Team - **Maintainers**: Development Team

View File

@ -421,12 +421,16 @@ Authorization: Bearer <token>
### Anime Endpoints ### 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 #### List Anime with Missing Episodes
Lists all anime series with missing episodes. Lists all anime series with missing episodes.
```http ```http
GET /api/v1/anime GET /api/anime
Authorization: Bearer <token> Authorization: Bearer <token>
``` ```
@ -442,14 +446,18 @@ Authorization: Bearer <token>
```json ```json
[ [
{ {
"id": "aniworld_123", "key": "attack-on-titan",
"title": "Attack on Titan", "name": "Attack on Titan",
"missing_episodes": 5 "folder": "Attack on Titan (2013)",
"missing_episodes": 5,
"link": "https://aniworld.to/anime/stream/attack-on-titan"
}, },
{ {
"id": "aniworld_456", "key": "demon-slayer",
"title": "Demon Slayer", "name": "Demon Slayer",
"missing_episodes": 2 "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. Retrieves detailed information for a specific anime series.
```http ```http
GET /api/v1/anime/{anime_id} GET /api/anime/{anime_id}
Authorization: Bearer <token> Authorization: Bearer <token>
``` ```
**Path Parameters**:
- `anime_id` (string): Series `key` (preferred) or `folder` (deprecated)
**Response (200 OK)**: **Response (200 OK)**:
```json ```json
{ {
"id": "aniworld_123", "key": "attack-on-titan",
"title": "Attack on Titan", "title": "Attack on Titan",
"episodes": ["Season 1 Episode 1", "Season 1 Episode 2"], "folder": "Attack on Titan (2013)",
"description": "Anime description...", "episodes": ["S01-E01", "S01-E02", "S02-E01"],
"total_episodes": 100, "description": "Anime description..."
"downloaded_episodes": 95
} }
``` ```
@ -489,7 +500,7 @@ Authorization: Bearer <token>
Rescans the local anime directory for new series and episodes. Rescans the local anime directory for new series and episodes.
```http ```http
POST /api/v1/anime/rescan POST /api/anime/rescan
Authorization: Bearer <token> Authorization: Bearer <token>
``` ```
@ -539,6 +550,9 @@ Authorization: Bearer <token>
### Download Queue Endpoints ### 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 #### Get Queue Status
Retrieves download queue status and statistics. Retrieves download queue status and statistics.
@ -577,13 +591,21 @@ Authorization: Bearer <token>
Content-Type: application/json 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"], "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)**: **Response (201 Created)**:
@ -592,7 +614,7 @@ Content-Type: application/json
"success": true, "success": true,
"data": { "data": {
"queue_item_id": "queue_456", "queue_item_id": "queue_456",
"anime_id": "aniworld_123", "serie_id": "attack-on-titan",
"status": "pending" "status": "pending"
} }
} }
@ -682,18 +704,21 @@ Authorization: Bearer <token>
### WebSocket Endpoints ### 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 #### Real-Time Progress Updates
Establishes WebSocket connection for real-time download progress updates. Establishes WebSocket connection for real-time download progress updates.
``` ```
WS /ws/downloads WS /ws/connect
``` ```
**Connection**: **Connection**:
```javascript ```javascript
const ws = new WebSocket("ws://localhost:8000/ws/downloads"); const ws = new WebSocket("ws://localhost:8000/ws/connect");
ws.onmessage = (event) => { ws.onmessage = (event) => {
const message = JSON.parse(event.data); const message = JSON.parse(event.data);
@ -701,6 +726,8 @@ ws.onmessage = (event) => {
}; };
``` ```
**Rooms**: `downloads`, `download_progress`, `scan_progress`
**Message Types**: **Message Types**:
**Download Started**: **Download Started**:
@ -710,8 +737,9 @@ ws.onmessage = (event) => {
"type": "download_started", "type": "download_started",
"timestamp": "2025-10-22T12:00:00Z", "timestamp": "2025-10-22T12:00:00Z",
"data": { "data": {
"queue_item_id": "queue_456", "download_id": "dl_456",
"anime_title": "Attack on Titan", "key": "attack-on-titan",
"folder": "Attack on Titan (2013)",
"episode": "S01E01" "episode": "S01E01"
} }
} }
@ -724,11 +752,12 @@ ws.onmessage = (event) => {
"type": "download_progress", "type": "download_progress",
"timestamp": "2025-10-22T12:00:05Z", "timestamp": "2025-10-22T12:00:05Z",
"data": { "data": {
"queue_item_id": "queue_456", "download_id": "dl_456",
"progress_percent": 45, "key": "attack-on-titan",
"downloaded_bytes": 500000000, "folder": "Attack on Titan (2013)",
"total_bytes": 1100000000, "percent": 45.2,
"speed_mbps": 5.5 "speed_mbps": 5.5,
"eta_seconds": 180
} }
} }
``` ```
@ -737,11 +766,12 @@ ws.onmessage = (event) => {
```json ```json
{ {
"type": "download_completed", "type": "download_complete",
"timestamp": "2025-10-22T12:05:00Z", "timestamp": "2025-10-22T12:05:00Z",
"data": { "data": {
"queue_item_id": "queue_456", "download_id": "dl_456",
"total_time_seconds": 300, "key": "attack-on-titan",
"folder": "Attack on Titan (2013)",
"file_path": "/path/to/anime/file.mkv" "file_path": "/path/to/anime/file.mkv"
} }
} }
@ -751,15 +781,17 @@ ws.onmessage = (event) => {
```json ```json
{ {
"type": "download_error", "type": "download_failed",
"timestamp": "2025-10-22T12:05:00Z", "timestamp": "2025-10-22T12:05:00Z",
"data": { "data": {
"queue_item_id": "queue_456", "download_id": "dl_456",
"error_message": "Connection timeout", "key": "attack-on-titan",
"error_code": "PROVIDER_ERROR" "folder": "Attack on Titan (2013)",
"error": "Connection timeout"
} }
} }
``` ```
```
--- ---

View File

@ -41,12 +41,30 @@ tests/ # Test suites
## Series Identifier Convention ## 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"`) | Identifier | Type | Purpose | Example |
- **`folder`**: Display/filesystem metadata only (e.g., `"Attack on Titan (2013)"`) | ---------- | --------------- | --------------------------------------------------------- | ----------------------------- |
| `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 ## API Endpoints
@ -282,3 +300,38 @@ conda run -n AniWorld python -m pytest tests/api/ -v
- Move WebSocket registry to Redis - Move WebSocket registry to Redis
- Use distributed locking for queue operations - Use distributed locking for queue operations
- Consider Redis for session/cache storage - 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. All deprecation warnings include removal timeline (v3.0.0) and guidance to use `key` instead.
**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
--- ---
@ -482,18 +425,11 @@ conda run -n AniWorld python -m pytest tests/integration/test_identifier_consist
- [x] Phase 3: Service Layer ✅ - [x] Phase 3: Service Layer ✅
- [x] Phase 4: API Layer ✅ **Completed November 28, 2025** - [x] Phase 4: API Layer ✅ **Completed November 28, 2025**
- [x] Phase 5: Frontend ✅ **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] 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] 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] Phase 8: Documentation and Cleanup ✅ **Completed November 28, 2025**
- [x] Task 7.2: Add Integration Tests - Created `tests/integration/test_identifier_consistency.py` with 10 tests verifying `key` usage across all layers - [x] Task 8.1: Update Infrastructure Documentation - Enhanced identifier convention section with table, format requirements, migration notes, and code examples
- [ ] Phase 8: Documentation and Cleanup - [x] Task 8.2: Update README and Developer Docs - Updated docs/README.md with identifier section, updated api_reference.md with key-based examples
- [ ] Task 8.1: Update Infrastructure Documentation - [x] Task 8.3: Add Deprecation Warnings - Added warnings to SerieList.get_by_folder(), anime.py folder fallback, and validators.py
- [ ] Task 8.2: Update README
- [ ] Task 8.3: Add Deprecation Warnings
- [ ] Phase 9: Final Validation - [ ] Phase 9: Final Validation
- [ ] Phase 10: Deployment

View File

@ -2,6 +2,7 @@
import logging import logging
import os import os
import warnings
from json import JSONDecodeError from json import JSONDecodeError
from typing import Dict, Iterable, List, Optional from typing import Dict, Iterable, List, Optional
@ -144,6 +145,11 @@ class SerieList:
""" """
Get a series by its folder name. 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. This method is provided for backward compatibility only.
Prefer using get_by_key() for new code. Prefer using get_by_key() for new code.
@ -153,6 +159,12 @@ class SerieList:
Returns: Returns:
The Serie instance if found, None otherwise 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(): for serie in self.keyDict.values():
if serie.folder == folder: if serie.folder == folder:
return serie return serie

View File

@ -1,3 +1,5 @@
import logging
import warnings
from typing import Any, List, Optional from typing import Any, List, Optional
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status
@ -11,6 +13,8 @@ from src.server.utils.dependencies import (
require_auth, require_auth,
) )
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/anime", tags=["anime"]) router = APIRouter(prefix="/api/anime", tags=["anime"])
@ -715,6 +719,21 @@ async def get_anime(
for serie in series: for serie in series:
if getattr(serie, "folder", None) == anime_id: if getattr(serie, "folder", None) == anime_id:
found = serie 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 break
if not found: if not found:

View File

@ -6,6 +6,7 @@ utilities for ensuring data integrity across the application.
""" """
import re import re
import warnings
from pathlib import Path from pathlib import Path
from typing import Any, Dict, List, Optional 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. 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 This function provides backward compatibility during the transition
from folder-based to key-based identification. 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')." "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) # Validate as folder (more permissive)
if len(identifier) > 1000: if len(identifier) > 1000:
raise ValueError("Identifier too long (max 1000 characters)") raise ValueError("Identifier too long (max 1000 characters)")