From 883f89b1132903d64d729a43615ea8da2d5049ec Mon Sep 17 00:00:00 2001 From: Lukas Date: Sun, 23 Nov 2025 20:02:11 +0100 Subject: [PATCH] Add series key metadata to callback contexts --- infrastructure.md | 10 ++++++++++ instructions.md | 31 ------------------------------- src/core/interfaces/callbacks.py | 18 ++++++++++++++++++ tests/unit/test_callbacks.py | 18 ++++++++++++++++++ 4 files changed, 46 insertions(+), 31 deletions(-) diff --git a/infrastructure.md b/infrastructure.md index ac8a2ac..15b0b30 100644 --- a/infrastructure.md +++ b/infrastructure.md @@ -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) diff --git a/instructions.md b/instructions.md index 607428a..91a0977 100644 --- a/instructions.md +++ b/instructions.md @@ -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 diff --git a/src/core/interfaces/callbacks.py b/src/core/interfaces/callbacks.py index 251895d..3a7837c 100644 --- a/src/core/interfaces/callbacks.py +++ b/src/core/interfaces/callbacks.py @@ -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, } diff --git a/tests/unit/test_callbacks.py b/tests/unit/test_callbacks.py index 24b17dc..6c44e56 100644 --- a/tests/unit/test_callbacks.py +++ b/tests/unit/test_callbacks.py @@ -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"], {})