Add series key metadata to callback contexts
This commit is contained in:
parent
41a53bbf8f
commit
883f89b113
@ -811,6 +811,12 @@ A comprehensive callback system for real-time progress reporting, error handling
|
|||||||
- `ErrorContext`: Error details, recoverability, retry information
|
- `ErrorContext`: Error details, recoverability, retry information
|
||||||
- `CompletionContext`: Success status, results, and statistics
|
- `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
|
#### Enums
|
||||||
|
|
||||||
- `OperationType`: SCAN, DOWNLOAD, SEARCH, INITIALIZATION
|
- `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
|
- Coverage for all callback types
|
||||||
- Exception handling verification
|
- Exception handling verification
|
||||||
- Multiple callback registration tests
|
- 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)
|
### Core Logic Enhancement (October 2025)
|
||||||
|
|
||||||
|
|||||||
@ -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
|
### Phase 3: Service Layer
|
||||||
|
|
||||||
#### Task 3.1: Update DownloadService to Use Key
|
#### 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
|
- [ ] **Task 1.4: Update Provider Classes** ⭐ NEW
|
||||||
- [ ] Phase 2: Core Application Layer
|
- [ ] Phase 2: Core Application Layer
|
||||||
- [ ] Task 2.1: Update SeriesApp
|
- [ ] Task 2.1: Update SeriesApp
|
||||||
- [ ] **Task 2.2: Update Callback Interfaces** ⭐ NEW
|
|
||||||
- [ ] Phase 3: Service Layer
|
- [ ] Phase 3: Service Layer
|
||||||
- [ ] Task 3.1: Update DownloadService
|
- [ ] Task 3.1: Update DownloadService
|
||||||
- [ ] Task 3.2: Update AnimeService
|
- [ ] Task 3.2: Update AnimeService
|
||||||
|
|||||||
@ -47,6 +47,8 @@ class ProgressContext:
|
|||||||
percentage: Completion percentage (0.0 to 100.0)
|
percentage: Completion percentage (0.0 to 100.0)
|
||||||
message: Human-readable progress message
|
message: Human-readable progress message
|
||||||
details: Additional context-specific details
|
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
|
metadata: Extra metadata for specialized use cases
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -58,6 +60,8 @@ class ProgressContext:
|
|||||||
percentage: float
|
percentage: float
|
||||||
message: str
|
message: str
|
||||||
details: Optional[str] = None
|
details: Optional[str] = None
|
||||||
|
key: Optional[str] = None
|
||||||
|
folder: Optional[str] = None
|
||||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
@ -71,6 +75,8 @@ class ProgressContext:
|
|||||||
"percentage": round(self.percentage, 2),
|
"percentage": round(self.percentage, 2),
|
||||||
"message": self.message,
|
"message": self.message,
|
||||||
"details": self.details,
|
"details": self.details,
|
||||||
|
"key": self.key,
|
||||||
|
"folder": self.folder,
|
||||||
"metadata": self.metadata,
|
"metadata": self.metadata,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,6 +93,8 @@ class ErrorContext:
|
|||||||
message: Human-readable error message
|
message: Human-readable error message
|
||||||
recoverable: Whether the error is recoverable
|
recoverable: Whether the error is recoverable
|
||||||
retry_count: Number of retry attempts made
|
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
|
metadata: Additional error context
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -96,6 +104,8 @@ class ErrorContext:
|
|||||||
message: str
|
message: str
|
||||||
recoverable: bool = False
|
recoverable: bool = False
|
||||||
retry_count: int = 0
|
retry_count: int = 0
|
||||||
|
key: Optional[str] = None
|
||||||
|
folder: Optional[str] = None
|
||||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
@ -108,6 +118,8 @@ class ErrorContext:
|
|||||||
"message": self.message,
|
"message": self.message,
|
||||||
"recoverable": self.recoverable,
|
"recoverable": self.recoverable,
|
||||||
"retry_count": self.retry_count,
|
"retry_count": self.retry_count,
|
||||||
|
"key": self.key,
|
||||||
|
"folder": self.folder,
|
||||||
"metadata": self.metadata,
|
"metadata": self.metadata,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,6 +136,8 @@ class CompletionContext:
|
|||||||
message: Human-readable completion message
|
message: Human-readable completion message
|
||||||
result_data: Result data from the operation
|
result_data: Result data from the operation
|
||||||
statistics: Operation statistics (duration, items processed, etc.)
|
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
|
metadata: Additional completion context
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -133,6 +147,8 @@ class CompletionContext:
|
|||||||
message: str
|
message: str
|
||||||
result_data: Optional[Any] = None
|
result_data: Optional[Any] = None
|
||||||
statistics: Dict[str, Any] = field(default_factory=dict)
|
statistics: Dict[str, Any] = field(default_factory=dict)
|
||||||
|
key: Optional[str] = None
|
||||||
|
folder: Optional[str] = None
|
||||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
@ -143,6 +159,8 @@ class CompletionContext:
|
|||||||
"success": self.success,
|
"success": self.success,
|
||||||
"message": self.message,
|
"message": self.message,
|
||||||
"statistics": self.statistics,
|
"statistics": self.statistics,
|
||||||
|
"key": self.key,
|
||||||
|
"folder": self.folder,
|
||||||
"metadata": self.metadata,
|
"metadata": self.metadata,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -34,6 +34,8 @@ class TestProgressContext(unittest.TestCase):
|
|||||||
percentage=50.0,
|
percentage=50.0,
|
||||||
message="Downloading...",
|
message="Downloading...",
|
||||||
details="Episode 5",
|
details="Episode 5",
|
||||||
|
key="attack-on-titan",
|
||||||
|
folder="Attack on Titan (2013)",
|
||||||
metadata={"series": "Test"}
|
metadata={"series": "Test"}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -45,6 +47,8 @@ class TestProgressContext(unittest.TestCase):
|
|||||||
self.assertEqual(context.percentage, 50.0)
|
self.assertEqual(context.percentage, 50.0)
|
||||||
self.assertEqual(context.message, "Downloading...")
|
self.assertEqual(context.message, "Downloading...")
|
||||||
self.assertEqual(context.details, "Episode 5")
|
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"})
|
self.assertEqual(context.metadata, {"series": "Test"})
|
||||||
|
|
||||||
def test_progress_context_to_dict(self):
|
def test_progress_context_to_dict(self):
|
||||||
@ -69,6 +73,8 @@ class TestProgressContext(unittest.TestCase):
|
|||||||
self.assertEqual(result["percentage"], 100.0)
|
self.assertEqual(result["percentage"], 100.0)
|
||||||
self.assertEqual(result["message"], "Scan complete")
|
self.assertEqual(result["message"], "Scan complete")
|
||||||
self.assertIsNone(result["details"])
|
self.assertIsNone(result["details"])
|
||||||
|
self.assertIsNone(result["key"])
|
||||||
|
self.assertIsNone(result["folder"])
|
||||||
self.assertEqual(result["metadata"], {})
|
self.assertEqual(result["metadata"], {})
|
||||||
|
|
||||||
def test_progress_context_default_metadata(self):
|
def test_progress_context_default_metadata(self):
|
||||||
@ -100,6 +106,8 @@ class TestErrorContext(unittest.TestCase):
|
|||||||
message="Download failed",
|
message="Download failed",
|
||||||
recoverable=True,
|
recoverable=True,
|
||||||
retry_count=2,
|
retry_count=2,
|
||||||
|
key="jujutsu-kaisen",
|
||||||
|
folder="Jujutsu Kaisen",
|
||||||
metadata={"attempt": 3}
|
metadata={"attempt": 3}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -109,6 +117,8 @@ class TestErrorContext(unittest.TestCase):
|
|||||||
self.assertEqual(context.message, "Download failed")
|
self.assertEqual(context.message, "Download failed")
|
||||||
self.assertTrue(context.recoverable)
|
self.assertTrue(context.recoverable)
|
||||||
self.assertEqual(context.retry_count, 2)
|
self.assertEqual(context.retry_count, 2)
|
||||||
|
self.assertEqual(context.key, "jujutsu-kaisen")
|
||||||
|
self.assertEqual(context.folder, "Jujutsu Kaisen")
|
||||||
self.assertEqual(context.metadata, {"attempt": 3})
|
self.assertEqual(context.metadata, {"attempt": 3})
|
||||||
|
|
||||||
def test_error_context_to_dict(self):
|
def test_error_context_to_dict(self):
|
||||||
@ -131,6 +141,8 @@ class TestErrorContext(unittest.TestCase):
|
|||||||
self.assertEqual(result["message"], "Scan error occurred")
|
self.assertEqual(result["message"], "Scan error occurred")
|
||||||
self.assertFalse(result["recoverable"])
|
self.assertFalse(result["recoverable"])
|
||||||
self.assertEqual(result["retry_count"], 0)
|
self.assertEqual(result["retry_count"], 0)
|
||||||
|
self.assertIsNone(result["key"])
|
||||||
|
self.assertIsNone(result["folder"])
|
||||||
self.assertEqual(result["metadata"], {})
|
self.assertEqual(result["metadata"], {})
|
||||||
|
|
||||||
|
|
||||||
@ -146,6 +158,8 @@ class TestCompletionContext(unittest.TestCase):
|
|||||||
message="Download completed successfully",
|
message="Download completed successfully",
|
||||||
result_data={"file": "episode.mp4"},
|
result_data={"file": "episode.mp4"},
|
||||||
statistics={"size": 1024, "time": 60},
|
statistics={"size": 1024, "time": 60},
|
||||||
|
key="bleach",
|
||||||
|
folder="Bleach (2004)",
|
||||||
metadata={"quality": "HD"}
|
metadata={"quality": "HD"}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -155,6 +169,8 @@ class TestCompletionContext(unittest.TestCase):
|
|||||||
self.assertEqual(context.message, "Download completed successfully")
|
self.assertEqual(context.message, "Download completed successfully")
|
||||||
self.assertEqual(context.result_data, {"file": "episode.mp4"})
|
self.assertEqual(context.result_data, {"file": "episode.mp4"})
|
||||||
self.assertEqual(context.statistics, {"size": 1024, "time": 60})
|
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"})
|
self.assertEqual(context.metadata, {"quality": "HD"})
|
||||||
|
|
||||||
def test_completion_context_to_dict(self):
|
def test_completion_context_to_dict(self):
|
||||||
@ -173,6 +189,8 @@ class TestCompletionContext(unittest.TestCase):
|
|||||||
self.assertFalse(result["success"])
|
self.assertFalse(result["success"])
|
||||||
self.assertEqual(result["message"], "Scan failed")
|
self.assertEqual(result["message"], "Scan failed")
|
||||||
self.assertEqual(result["statistics"], {})
|
self.assertEqual(result["statistics"], {})
|
||||||
|
self.assertIsNone(result["key"])
|
||||||
|
self.assertIsNone(result["folder"])
|
||||||
self.assertEqual(result["metadata"], {})
|
self.assertEqual(result["metadata"], {})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user