Task 4.2: Update Download API Endpoints to Use Key
- Updated DownloadRequest and DownloadItem models with comprehensive docstrings explaining serie_id (key as primary identifier) vs serie_folder (filesystem metadata) - Updated add_to_queue() endpoint docstring to document request parameters - Updated all test files to include required serie_folder field: - tests/api/test_download_endpoints.py - tests/api/test_queue_features.py - tests/frontend/test_existing_ui_integration.py - tests/integration/test_download_flow.py - Updated infrastructure.md with Download Queue request/response models - All 869 tests pass This is part of the Series Identifier Standardization effort (Phase 4.2) to ensure key is used as the primary identifier throughout the codebase.
This commit is contained in:
parent
da4973829e
commit
589141e9aa
@ -86,6 +86,15 @@ All lookups, events, and API operations use `key`. The `folder` is metadata for
|
|||||||
- `POST /retry` - Retry failed downloads
|
- `POST /retry` - Retry failed downloads
|
||||||
- `DELETE /completed` - Clear completed items
|
- `DELETE /completed` - Clear completed items
|
||||||
|
|
||||||
|
**Request Models:**
|
||||||
|
|
||||||
|
- `DownloadRequest`: `serie_id` (key, primary identifier), `serie_folder` (filesystem path), `serie_name` (display), `episodes`, `priority`
|
||||||
|
|
||||||
|
**Response Models:**
|
||||||
|
|
||||||
|
- `DownloadItem`: `id`, `serie_id` (key), `serie_folder` (metadata), `serie_name`, `episode`, `status`, `progress`
|
||||||
|
- `QueueStatus`: `is_running`, `is_paused`, `active_downloads`, `pending_queue`, `completed_downloads`, `failed_downloads`
|
||||||
|
|
||||||
### WebSocket (`/ws/connect`)
|
### WebSocket (`/ws/connect`)
|
||||||
|
|
||||||
Real-time updates for downloads, scans, and queue operations.
|
Real-time updates for downloads, scans, and queue operations.
|
||||||
|
|||||||
@ -198,37 +198,20 @@ Updated `src/server/api/anime.py` to standardize all endpoints to use `key` as t
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
#### Task 4.2: Update Download API Endpoints to Use Key
|
#### Task 4.2: Update Download API Endpoints to Use Key ✅
|
||||||
|
|
||||||
**File:** [`src/server/api/download.py`](src/server/api/download.py)
|
**Completed:** November 27, 2025
|
||||||
|
|
||||||
**Objective:** Ensure download API uses `key` for series identification.
|
Updated `src/server/api/download.py` and `src/server/models/download.py` to standardize identifier documentation:
|
||||||
|
|
||||||
**Steps:**
|
- Updated `DownloadRequest` model with comprehensive docstrings explaining `serie_id` (key as primary identifier) vs `serie_folder` (filesystem metadata)
|
||||||
|
- Updated `DownloadItem` model with same clarification
|
||||||
|
- Updated `add_to_queue()` endpoint docstring to document request parameters
|
||||||
|
- Updated all test files to include required `serie_folder` field
|
||||||
|
- All download API tests pass (23/23)
|
||||||
|
- All integration tests pass (869/869)
|
||||||
|
|
||||||
1. Open [`src/server/api/download.py`](src/server/api/download.py)
|
------
|
||||||
2. Update `DownloadRequest` model:
|
|
||||||
- Rename or document `serie_id` as `series_key`
|
|
||||||
- Keep `serie_folder` for filesystem operations
|
|
||||||
- Keep `serie_name` for display
|
|
||||||
3. Update `add_to_queue()` endpoint:
|
|
||||||
- Use `series_key` for series identification
|
|
||||||
- Pass `serie_folder` to download service
|
|
||||||
4. Update all docstrings
|
|
||||||
|
|
||||||
**Success Criteria:**
|
|
||||||
|
|
||||||
- [ ] Request model uses `key` as identifier
|
|
||||||
- [ ] Endpoint passes correct identifiers to service
|
|
||||||
- [ ] All download API tests pass
|
|
||||||
|
|
||||||
**Test Command:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
conda run -n AniWorld python -m pytest tests/api/ -k "download" -v
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### Task 4.3: Update Queue API Endpoints to Use Key
|
#### Task 4.3: Update Queue API Endpoints to Use Key
|
||||||
|
|
||||||
@ -918,7 +901,7 @@ conda run -n AniWorld python -m pytest tests/integration/test_identifier_consist
|
|||||||
- [x] Phase 3: Service Layer ✅
|
- [x] Phase 3: Service Layer ✅
|
||||||
- [ ] Phase 4: API Layer
|
- [ ] Phase 4: API Layer
|
||||||
- [x] Task 4.1: Update Anime API Endpoints ✅ **Completed November 27, 2025**
|
- [x] Task 4.1: Update Anime API Endpoints ✅ **Completed November 27, 2025**
|
||||||
- [ ] Task 4.2: Update Download API Endpoints
|
- [x] Task 4.2: Update Download API Endpoints ✅ **Completed November 27, 2025**
|
||||||
- [ ] Task 4.3: Update Queue API Endpoints
|
- [ ] Task 4.3: Update Queue API Endpoints
|
||||||
- [ ] Task 4.4: Update WebSocket API Endpoints
|
- [ ] Task 4.4: Update WebSocket API Endpoints
|
||||||
- [ ] Task 4.5: Update Pydantic Models
|
- [ ] Task 4.5: Update Pydantic Models
|
||||||
|
|||||||
@ -74,7 +74,12 @@ async def add_to_queue(
|
|||||||
Requires authentication.
|
Requires authentication.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request: Download request with serie info, episodes, and priority
|
request: Download request containing:
|
||||||
|
- serie_id: Series key (primary identifier, 'attack-on-titan')
|
||||||
|
- serie_folder: Filesystem folder name for storing downloads
|
||||||
|
- serie_name: Display name for the series
|
||||||
|
- episodes: List of episodes to download
|
||||||
|
- priority: Queue priority level
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
DownloadResponse: Status and list of created download item IDs
|
DownloadResponse: Status and list of created download item IDs
|
||||||
|
|||||||
@ -63,24 +63,33 @@ class DownloadProgress(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class DownloadItem(BaseModel):
|
class DownloadItem(BaseModel):
|
||||||
"""Represents a single download item in the queue."""
|
"""Represents a single download item in the queue.
|
||||||
|
|
||||||
|
Note on identifiers:
|
||||||
|
- serie_id: The provider-assigned key (e.g., 'attack-on-titan') used for
|
||||||
|
all lookups and identification. This is the primary identifier.
|
||||||
|
- serie_folder: The filesystem folder name (e.g., 'Attack on Titan (2013)')
|
||||||
|
used only for filesystem operations. This is metadata, not an identifier.
|
||||||
|
"""
|
||||||
|
|
||||||
id: str = Field(..., description="Unique download item identifier")
|
id: str = Field(..., description="Unique download item identifier")
|
||||||
serie_id: str = Field(
|
serie_id: str = Field(
|
||||||
...,
|
...,
|
||||||
description=(
|
description=(
|
||||||
"Series identifier - provider key "
|
"Series key (primary identifier) - provider-assigned URL-safe "
|
||||||
"(e.g., 'attack-on-titan')"
|
"key (e.g., 'attack-on-titan'). Used for lookups/identification."
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
serie_folder: str = Field(
|
serie_folder: str = Field(
|
||||||
...,
|
...,
|
||||||
description=(
|
description=(
|
||||||
"Series folder name on disk "
|
"Series folder name on disk (metadata only) "
|
||||||
"(e.g., 'Attack on Titan (2013)')"
|
"(e.g., 'Attack on Titan (2013)'). For filesystem ops only."
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
serie_name: str = Field(..., min_length=1, description="Series name")
|
serie_name: str = Field(
|
||||||
|
..., min_length=1, description="Series display name"
|
||||||
|
)
|
||||||
episode: EpisodeIdentifier = Field(
|
episode: EpisodeIdentifier = Field(
|
||||||
..., description="Episode identification"
|
..., description="Episode identification"
|
||||||
)
|
)
|
||||||
@ -168,24 +177,31 @@ class QueueStats(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class DownloadRequest(BaseModel):
|
class DownloadRequest(BaseModel):
|
||||||
"""Request to add episode(s) to the download queue."""
|
"""Request to add episode(s) to the download queue.
|
||||||
|
|
||||||
|
Note on identifiers:
|
||||||
|
- serie_id: The provider-assigned key (e.g., 'attack-on-titan') used as
|
||||||
|
the primary identifier for all operations. This is the unique key.
|
||||||
|
- serie_folder: The filesystem folder name (e.g., 'Attack on Titan (2013)')
|
||||||
|
used only for storing downloaded files. This is metadata.
|
||||||
|
"""
|
||||||
|
|
||||||
serie_id: str = Field(
|
serie_id: str = Field(
|
||||||
...,
|
...,
|
||||||
description=(
|
description=(
|
||||||
"Series identifier - provider key "
|
"Series key (primary identifier) - provider-assigned URL-safe "
|
||||||
"(e.g., 'attack-on-titan')"
|
"key (e.g., 'attack-on-titan'). Used for lookups/identification."
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
serie_folder: str = Field(
|
serie_folder: str = Field(
|
||||||
...,
|
...,
|
||||||
description=(
|
description=(
|
||||||
"Series folder name on disk "
|
"Series folder name on disk (metadata only) "
|
||||||
"(e.g., 'Attack on Titan (2013)')"
|
"(e.g., 'Attack on Titan (2013)'). For filesystem ops only."
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
serie_name: str = Field(
|
serie_name: str = Field(
|
||||||
..., min_length=1, description="Series name for display"
|
..., min_length=1, description="Series display name"
|
||||||
)
|
)
|
||||||
episodes: List[EpisodeIdentifier] = Field(
|
episodes: List[EpisodeIdentifier] = Field(
|
||||||
..., description="List of episodes to download"
|
..., description="List of episodes to download"
|
||||||
|
|||||||
@ -146,7 +146,8 @@ async def test_get_queue_status_unauthorized(mock_download_service):
|
|||||||
async def test_add_to_queue(authenticated_client, mock_download_service):
|
async def test_add_to_queue(authenticated_client, mock_download_service):
|
||||||
"""Test POST /api/queue/add endpoint."""
|
"""Test POST /api/queue/add endpoint."""
|
||||||
request_data = {
|
request_data = {
|
||||||
"serie_id": "series-1",
|
"serie_id": "test-anime",
|
||||||
|
"serie_folder": "Test Anime (2024)",
|
||||||
"serie_name": "Test Anime",
|
"serie_name": "Test Anime",
|
||||||
"episodes": [
|
"episodes": [
|
||||||
{"season": 1, "episode": 1},
|
{"season": 1, "episode": 1},
|
||||||
@ -175,7 +176,8 @@ async def test_add_to_queue_with_high_priority(
|
|||||||
):
|
):
|
||||||
"""Test adding items with HIGH priority."""
|
"""Test adding items with HIGH priority."""
|
||||||
request_data = {
|
request_data = {
|
||||||
"serie_id": "series-1",
|
"serie_id": "test-anime",
|
||||||
|
"serie_folder": "Test Anime (2024)",
|
||||||
"serie_name": "Test Anime",
|
"serie_name": "Test Anime",
|
||||||
"episodes": [{"season": 1, "episode": 1}],
|
"episodes": [{"season": 1, "episode": 1}],
|
||||||
"priority": "high",
|
"priority": "high",
|
||||||
@ -198,7 +200,8 @@ async def test_add_to_queue_empty_episodes(
|
|||||||
):
|
):
|
||||||
"""Test adding empty episodes list returns 400."""
|
"""Test adding empty episodes list returns 400."""
|
||||||
request_data = {
|
request_data = {
|
||||||
"serie_id": "series-1",
|
"serie_id": "test-anime",
|
||||||
|
"serie_folder": "Test Anime (2024)",
|
||||||
"serie_name": "Test Anime",
|
"serie_name": "Test Anime",
|
||||||
"episodes": [],
|
"episodes": [],
|
||||||
"priority": "normal",
|
"priority": "normal",
|
||||||
@ -221,7 +224,8 @@ async def test_add_to_queue_service_error(
|
|||||||
)
|
)
|
||||||
|
|
||||||
request_data = {
|
request_data = {
|
||||||
"serie_id": "series-1",
|
"serie_id": "test-anime",
|
||||||
|
"serie_folder": "Test Anime (2024)",
|
||||||
"serie_name": "Test Anime",
|
"serie_name": "Test Anime",
|
||||||
"episodes": [{"season": 1, "episode": 1}],
|
"episodes": [{"season": 1, "episode": 1}],
|
||||||
"priority": "normal",
|
"priority": "normal",
|
||||||
|
|||||||
@ -48,6 +48,7 @@ def sample_download_request():
|
|||||||
"""Sample download request for testing."""
|
"""Sample download request for testing."""
|
||||||
return {
|
return {
|
||||||
"serie_id": "test-series",
|
"serie_id": "test-series",
|
||||||
|
"serie_folder": "Test Series (2024)",
|
||||||
"serie_name": "Test Series",
|
"serie_name": "Test Series",
|
||||||
"episodes": [
|
"episodes": [
|
||||||
{"season": 1, "episode": 1},
|
{"season": 1, "episode": 1},
|
||||||
@ -159,6 +160,7 @@ class TestQueueReordering:
|
|||||||
"/api/queue/add",
|
"/api/queue/add",
|
||||||
json={
|
json={
|
||||||
"serie_id": f"test-{i}",
|
"serie_id": f"test-{i}",
|
||||||
|
"serie_folder": f"Test Series {i} (2024)",
|
||||||
"serie_name": f"Test Series {i}",
|
"serie_name": f"Test Series {i}",
|
||||||
"episodes": [{"season": 1, "episode": i+1}],
|
"episodes": [{"season": 1, "episode": i+1}],
|
||||||
"priority": "normal"
|
"priority": "normal"
|
||||||
|
|||||||
@ -252,7 +252,8 @@ class TestFrontendDownloadAPI:
|
|||||||
response = await authenticated_client.post(
|
response = await authenticated_client.post(
|
||||||
"/api/queue/add",
|
"/api/queue/add",
|
||||||
json={
|
json={
|
||||||
"serie_id": "test_anime",
|
"serie_id": "test-anime",
|
||||||
|
"serie_folder": "Test Anime (2024)",
|
||||||
"serie_name": "Test Anime",
|
"serie_name": "Test Anime",
|
||||||
"episodes": [{"season": 1, "episode": 1}],
|
"episodes": [{"season": 1, "episode": 1}],
|
||||||
"priority": "normal"
|
"priority": "normal"
|
||||||
|
|||||||
@ -133,6 +133,7 @@ class TestDownloadFlowEndToEnd:
|
|||||||
"/api/queue/add",
|
"/api/queue/add",
|
||||||
json={
|
json={
|
||||||
"serie_id": "test-series-1",
|
"serie_id": "test-series-1",
|
||||||
|
"serie_folder": "Test Anime Series (2024)",
|
||||||
"serie_name": "Test Anime Series",
|
"serie_name": "Test Anime Series",
|
||||||
"episodes": [
|
"episodes": [
|
||||||
{"season": 1, "episode": 1, "title": "Episode 1"},
|
{"season": 1, "episode": 1, "title": "Episode 1"},
|
||||||
@ -158,6 +159,7 @@ class TestDownloadFlowEndToEnd:
|
|||||||
"/api/queue/add",
|
"/api/queue/add",
|
||||||
json={
|
json={
|
||||||
"serie_id": "test-series-2",
|
"serie_id": "test-series-2",
|
||||||
|
"serie_folder": "Another Series (2024)",
|
||||||
"serie_name": "Another Series",
|
"serie_name": "Another Series",
|
||||||
"episodes": [{"season": 1, "episode": 1}],
|
"episodes": [{"season": 1, "episode": 1}],
|
||||||
"priority": "high"
|
"priority": "high"
|
||||||
@ -194,6 +196,7 @@ class TestDownloadFlowEndToEnd:
|
|||||||
"/api/queue/add",
|
"/api/queue/add",
|
||||||
json={
|
json={
|
||||||
"serie_id": f"series-{priority}",
|
"serie_id": f"series-{priority}",
|
||||||
|
"serie_folder": f"Series {priority.title()} (2024)",
|
||||||
"serie_name": f"Series {priority.title()}",
|
"serie_name": f"Series {priority.title()}",
|
||||||
"episodes": [{"season": 1, "episode": 1}],
|
"episodes": [{"season": 1, "episode": 1}],
|
||||||
"priority": priority
|
"priority": priority
|
||||||
@ -208,6 +211,7 @@ class TestDownloadFlowEndToEnd:
|
|||||||
"/api/queue/add",
|
"/api/queue/add",
|
||||||
json={
|
json={
|
||||||
"serie_id": "test-series",
|
"serie_id": "test-series",
|
||||||
|
"serie_folder": "Test Series (2024)",
|
||||||
"serie_name": "Test Series",
|
"serie_name": "Test Series",
|
||||||
"episodes": [],
|
"episodes": [],
|
||||||
"priority": "normal"
|
"priority": "normal"
|
||||||
@ -224,6 +228,7 @@ class TestDownloadFlowEndToEnd:
|
|||||||
"/api/queue/add",
|
"/api/queue/add",
|
||||||
json={
|
json={
|
||||||
"serie_id": "test-series",
|
"serie_id": "test-series",
|
||||||
|
"serie_folder": "Test Series (2024)",
|
||||||
"serie_name": "Test Series",
|
"serie_name": "Test Series",
|
||||||
"episodes": [{"season": 1, "episode": 1}],
|
"episodes": [{"season": 1, "episode": 1}],
|
||||||
"priority": "invalid"
|
"priority": "invalid"
|
||||||
@ -267,6 +272,7 @@ class TestQueueItemOperations:
|
|||||||
"/api/queue/add",
|
"/api/queue/add",
|
||||||
json={
|
json={
|
||||||
"serie_id": "test-series",
|
"serie_id": "test-series",
|
||||||
|
"serie_folder": "Test Series (2024)",
|
||||||
"serie_name": "Test Series",
|
"serie_name": "Test Series",
|
||||||
"episodes": [{"season": 1, "episode": 1}],
|
"episodes": [{"season": 1, "episode": 1}],
|
||||||
"priority": "normal"
|
"priority": "normal"
|
||||||
@ -301,6 +307,7 @@ class TestDownloadProgressTracking:
|
|||||||
"/api/queue/add",
|
"/api/queue/add",
|
||||||
json={
|
json={
|
||||||
"serie_id": "test-series",
|
"serie_id": "test-series",
|
||||||
|
"serie_folder": "Test Series (2024)",
|
||||||
"serie_name": "Test Series",
|
"serie_name": "Test Series",
|
||||||
"episodes": [{"season": 1, "episode": 1}],
|
"episodes": [{"season": 1, "episode": 1}],
|
||||||
"priority": "normal"
|
"priority": "normal"
|
||||||
@ -358,6 +365,7 @@ class TestErrorHandlingAndRetries:
|
|||||||
"/api/queue/add",
|
"/api/queue/add",
|
||||||
json={
|
json={
|
||||||
"serie_id": "invalid-series",
|
"serie_id": "invalid-series",
|
||||||
|
"serie_folder": "Invalid Series (2024)",
|
||||||
"serie_name": "Invalid Series",
|
"serie_name": "Invalid Series",
|
||||||
"episodes": [{"season": 99, "episode": 99}],
|
"episodes": [{"season": 99, "episode": 99}],
|
||||||
"priority": "normal"
|
"priority": "normal"
|
||||||
@ -374,6 +382,7 @@ class TestErrorHandlingAndRetries:
|
|||||||
"/api/queue/add",
|
"/api/queue/add",
|
||||||
json={
|
json={
|
||||||
"serie_id": "test-series",
|
"serie_id": "test-series",
|
||||||
|
"serie_folder": "Test Series (2024)",
|
||||||
"serie_name": "Test Series",
|
"serie_name": "Test Series",
|
||||||
"episodes": [{"season": 1, "episode": 1}],
|
"episodes": [{"season": 1, "episode": 1}],
|
||||||
"priority": "normal"
|
"priority": "normal"
|
||||||
@ -412,6 +421,7 @@ class TestAuthenticationRequirements:
|
|||||||
"/api/queue/add",
|
"/api/queue/add",
|
||||||
json={
|
json={
|
||||||
"serie_id": "test-series",
|
"serie_id": "test-series",
|
||||||
|
"serie_folder": "Test Series (2024)",
|
||||||
"serie_name": "Test Series",
|
"serie_name": "Test Series",
|
||||||
"episodes": [{"season": 1, "episode": 1}],
|
"episodes": [{"season": 1, "episode": 1}],
|
||||||
"priority": "normal"
|
"priority": "normal"
|
||||||
@ -444,6 +454,7 @@ class TestConcurrentOperations:
|
|||||||
"/api/queue/add",
|
"/api/queue/add",
|
||||||
json={
|
json={
|
||||||
"serie_id": f"series-{i}",
|
"serie_id": f"series-{i}",
|
||||||
|
"serie_folder": f"Series {i} (2024)",
|
||||||
"serie_name": f"Series {i}",
|
"serie_name": f"Series {i}",
|
||||||
"episodes": [{"season": 1, "episode": 1}],
|
"episodes": [{"season": 1, "episode": 1}],
|
||||||
"priority": "normal"
|
"priority": "normal"
|
||||||
@ -488,6 +499,7 @@ class TestQueuePersistence:
|
|||||||
"/api/queue/add",
|
"/api/queue/add",
|
||||||
json={
|
json={
|
||||||
"serie_id": "persistent-series",
|
"serie_id": "persistent-series",
|
||||||
|
"serie_folder": "Persistent Series (2024)",
|
||||||
"serie_name": "Persistent Series",
|
"serie_name": "Persistent Series",
|
||||||
"episodes": [{"season": 1, "episode": 1}],
|
"episodes": [{"season": 1, "episode": 1}],
|
||||||
"priority": "normal"
|
"priority": "normal"
|
||||||
@ -524,6 +536,7 @@ class TestWebSocketIntegrationWithDownloads:
|
|||||||
"/api/queue/add",
|
"/api/queue/add",
|
||||||
json={
|
json={
|
||||||
"serie_id": "ws-series",
|
"serie_id": "ws-series",
|
||||||
|
"serie_folder": "WebSocket Series (2024)",
|
||||||
"serie_name": "WebSocket Series",
|
"serie_name": "WebSocket Series",
|
||||||
"episodes": [{"season": 1, "episode": 1}],
|
"episodes": [{"season": 1, "episode": 1}],
|
||||||
"priority": "normal"
|
"priority": "normal"
|
||||||
@ -546,6 +559,7 @@ class TestCompleteDownloadWorkflow:
|
|||||||
"/api/queue/add",
|
"/api/queue/add",
|
||||||
json={
|
json={
|
||||||
"serie_id": "workflow-series",
|
"serie_id": "workflow-series",
|
||||||
|
"serie_folder": "Workflow Test Series (2024)",
|
||||||
"serie_name": "Workflow Test Series",
|
"serie_name": "Workflow Test Series",
|
||||||
"episodes": [{"season": 1, "episode": 1}],
|
"episodes": [{"season": 1, "episode": 1}],
|
||||||
"priority": "high"
|
"priority": "high"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user