Add series key metadata to callback contexts

This commit is contained in:
Lukas 2025-11-23 20:02:11 +01:00
parent 41a53bbf8f
commit 883f89b113
4 changed files with 46 additions and 31 deletions

View File

@ -811,6 +811,12 @@ A comprehensive callback system for real-time progress reporting, error handling
- `ErrorContext`: Error details, recoverability, retry information
- `CompletionContext`: Success status, results, and statistics
All context dataclasses expose a `key` field (provider identifier) plus an
optional `folder` field used purely for display metadata. This keeps the
callback contract aligned with the broader series identifier standardization
work: downstream consumers rely on `key` for lookups while still showing a
user-friendly folder name when needed.
#### Enums
- `OperationType`: SCAN, DOWNLOAD, SEARCH, INITIALIZATION
@ -893,6 +899,10 @@ Implemented a comprehensive progress callback system for real-time operation tra
- Coverage for all callback types
- Exception handling verification
- Multiple callback registration tests
5. **Identifier Support (Nov 2025)**:
- Added `key` + optional `folder` fields to every context object
- Docstrings now clarify that `key` is the canonical lookup identifier
- Tests updated to guarantee both fields serialize correctly
### Core Logic Enhancement (October 2025)

View File

@ -169,36 +169,6 @@ For each task completed:
---
#### Task 2.2: Update Callback Interfaces to Use Key
**File:** [`src/core/interfaces/callbacks.py`](src/core/interfaces/callbacks.py)
**Objective:** Ensure callback interfaces use `key` in all progress and event contexts.
**Steps:**
1. Open [`src/core/interfaces/callbacks.py`](src/core/interfaces/callbacks.py)
2. Review `ProgressContext` and other context classes
3. Ensure context classes include `key` field where series is referenced
4. Add `folder` field as optional metadata
5. Update docstrings to clarify `key` vs `folder` usage
6. Ensure backward compatibility where needed
**Success Criteria:**
- [ ] Context classes include `key` field
- [ ] `folder` included as optional metadata
- [ ] Docstrings clearly document field usage
- [ ] All callback tests pass
**Test Command:**
```bash
conda run -n AniWorld python -m pytest tests/unit/test_callbacks.py -v
```
---
### Phase 3: Service Layer
#### Task 3.1: Update DownloadService to Use Key
@ -1089,7 +1059,6 @@ conda run -n AniWorld python -m pytest tests/integration/test_identifier_consist
- [ ] **Task 1.4: Update Provider Classes** ⭐ NEW
- [ ] Phase 2: Core Application Layer
- [ ] Task 2.1: Update SeriesApp
- [ ] **Task 2.2: Update Callback Interfaces** ⭐ NEW
- [ ] Phase 3: Service Layer
- [ ] Task 3.1: Update DownloadService
- [ ] Task 3.2: Update AnimeService

View File

@ -47,6 +47,8 @@ class ProgressContext:
percentage: Completion percentage (0.0 to 100.0)
message: Human-readable progress message
details: Additional context-specific details
key: Provider-assigned series identifier (None when not applicable)
folder: Optional folder metadata for display purposes only
metadata: Extra metadata for specialized use cases
"""
@ -58,6 +60,8 @@ class ProgressContext:
percentage: float
message: str
details: Optional[str] = None
key: Optional[str] = None
folder: Optional[str] = None
metadata: Dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> Dict[str, Any]:
@ -71,6 +75,8 @@ class ProgressContext:
"percentage": round(self.percentage, 2),
"message": self.message,
"details": self.details,
"key": self.key,
"folder": self.folder,
"metadata": self.metadata,
}
@ -87,6 +93,8 @@ class ErrorContext:
message: Human-readable error message
recoverable: Whether the error is recoverable
retry_count: Number of retry attempts made
key: Provider-assigned series identifier (None when not applicable)
folder: Optional folder metadata for display purposes only
metadata: Additional error context
"""
@ -96,6 +104,8 @@ class ErrorContext:
message: str
recoverable: bool = False
retry_count: int = 0
key: Optional[str] = None
folder: Optional[str] = None
metadata: Dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> Dict[str, Any]:
@ -108,6 +118,8 @@ class ErrorContext:
"message": self.message,
"recoverable": self.recoverable,
"retry_count": self.retry_count,
"key": self.key,
"folder": self.folder,
"metadata": self.metadata,
}
@ -124,6 +136,8 @@ class CompletionContext:
message: Human-readable completion message
result_data: Result data from the operation
statistics: Operation statistics (duration, items processed, etc.)
key: Provider-assigned series identifier (None when not applicable)
folder: Optional folder metadata for display purposes only
metadata: Additional completion context
"""
@ -133,6 +147,8 @@ class CompletionContext:
message: str
result_data: Optional[Any] = None
statistics: Dict[str, Any] = field(default_factory=dict)
key: Optional[str] = None
folder: Optional[str] = None
metadata: Dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> Dict[str, Any]:
@ -143,6 +159,8 @@ class CompletionContext:
"success": self.success,
"message": self.message,
"statistics": self.statistics,
"key": self.key,
"folder": self.folder,
"metadata": self.metadata,
}

View File

@ -34,6 +34,8 @@ class TestProgressContext(unittest.TestCase):
percentage=50.0,
message="Downloading...",
details="Episode 5",
key="attack-on-titan",
folder="Attack on Titan (2013)",
metadata={"series": "Test"}
)
@ -45,6 +47,8 @@ class TestProgressContext(unittest.TestCase):
self.assertEqual(context.percentage, 50.0)
self.assertEqual(context.message, "Downloading...")
self.assertEqual(context.details, "Episode 5")
self.assertEqual(context.key, "attack-on-titan")
self.assertEqual(context.folder, "Attack on Titan (2013)")
self.assertEqual(context.metadata, {"series": "Test"})
def test_progress_context_to_dict(self):
@ -69,6 +73,8 @@ class TestProgressContext(unittest.TestCase):
self.assertEqual(result["percentage"], 100.0)
self.assertEqual(result["message"], "Scan complete")
self.assertIsNone(result["details"])
self.assertIsNone(result["key"])
self.assertIsNone(result["folder"])
self.assertEqual(result["metadata"], {})
def test_progress_context_default_metadata(self):
@ -100,6 +106,8 @@ class TestErrorContext(unittest.TestCase):
message="Download failed",
recoverable=True,
retry_count=2,
key="jujutsu-kaisen",
folder="Jujutsu Kaisen",
metadata={"attempt": 3}
)
@ -109,6 +117,8 @@ class TestErrorContext(unittest.TestCase):
self.assertEqual(context.message, "Download failed")
self.assertTrue(context.recoverable)
self.assertEqual(context.retry_count, 2)
self.assertEqual(context.key, "jujutsu-kaisen")
self.assertEqual(context.folder, "Jujutsu Kaisen")
self.assertEqual(context.metadata, {"attempt": 3})
def test_error_context_to_dict(self):
@ -131,6 +141,8 @@ class TestErrorContext(unittest.TestCase):
self.assertEqual(result["message"], "Scan error occurred")
self.assertFalse(result["recoverable"])
self.assertEqual(result["retry_count"], 0)
self.assertIsNone(result["key"])
self.assertIsNone(result["folder"])
self.assertEqual(result["metadata"], {})
@ -146,6 +158,8 @@ class TestCompletionContext(unittest.TestCase):
message="Download completed successfully",
result_data={"file": "episode.mp4"},
statistics={"size": 1024, "time": 60},
key="bleach",
folder="Bleach (2004)",
metadata={"quality": "HD"}
)
@ -155,6 +169,8 @@ class TestCompletionContext(unittest.TestCase):
self.assertEqual(context.message, "Download completed successfully")
self.assertEqual(context.result_data, {"file": "episode.mp4"})
self.assertEqual(context.statistics, {"size": 1024, "time": 60})
self.assertEqual(context.key, "bleach")
self.assertEqual(context.folder, "Bleach (2004)")
self.assertEqual(context.metadata, {"quality": "HD"})
def test_completion_context_to_dict(self):
@ -173,6 +189,8 @@ class TestCompletionContext(unittest.TestCase):
self.assertFalse(result["success"])
self.assertEqual(result["message"], "Scan failed")
self.assertEqual(result["statistics"], {})
self.assertIsNone(result["key"])
self.assertIsNone(result["folder"])
self.assertEqual(result["metadata"], {})