feat(cache): Implement unified caching subsystem with decorators, strategies, and management

- Added core caching functionality with `Cache` class supporting in-memory and file-based caching.
- Introduced `CacheManager` for high-level cache operations and statistics.
- Created various cache key generation strategies: `FilepathMethodStrategy`, `APIRequestStrategy`, `SimpleKeyStrategy`, and `CustomStrategy`.
- Developed decorators for easy method caching: `cached`, `cached_method`, `cached_api`, and `cached_property`.
- Implemented type definitions for cache entries and statistics.
- Added comprehensive tests for cache operations, strategies, and decorators to ensure functionality and backward compatibility.
This commit is contained in:
sha
2025-12-31 02:29:10 +00:00
parent 3fbf45083f
commit b50b9bc165
16 changed files with 1851 additions and 259 deletions
+26 -17
View File
@@ -1,5 +1,6 @@
import pytest
from pathlib import Path
from unittest.mock import MagicMock
from renamer.extractors.mediainfo_extractor import MediaInfoExtractor
import json
@@ -17,7 +18,14 @@ class TestMediaInfoExtractor:
@pytest.fixture
def frame_class_cases(self):
"""Load test cases for frame class extraction"""
# Try the expected file first, fallback to the main frame class test file
cases_file = Path(__file__).parent / "test_mediainfo_frame_class_cases.json"
if not cases_file.exists():
cases_file = Path(__file__).parent / "test_mediainfo_frame_class.json"
if not cases_file.exists():
pytest.skip(f"Test case file not found: {cases_file}")
with open(cases_file, 'r') as f:
return json.load(f)
@@ -57,20 +65,21 @@ class TestMediaInfoExtractor:
# Text files don't have video tracks
assert is_3d is False
@pytest.mark.parametrize("case", [
pytest.param(case, id=case["testname"])
for case in json.load(open(Path(__file__).parent / "test_mediainfo_frame_class_cases.json"))
])
def test_extract_frame_class(self, case):
"""Test extracting frame class from various resolutions"""
# Create a mock extractor with the test resolution
extractor = MediaInfoExtractor.__new__(MediaInfoExtractor)
extractor.video_tracks = [{
'width': case["resolution"][0],
'height': case["resolution"][1],
'interlaced': 'Yes' if case["interlaced"] else None
}]
result = extractor.extract_frame_class()
print(f"Case: {case['testname']}, resolution: {case['resolution']}, expected: {case['expected_frame_class']}, got: {result}")
assert result == case["expected_frame_class"], f"Failed for {case['testname']}: expected {case['expected_frame_class']}, got {result}"
def test_extract_frame_class_parametrized(self, frame_class_cases):
"""Test extracting frame class from various resolutions using fixture"""
for case in frame_class_cases:
# Create a mock extractor with the test resolution
extractor = MagicMock(spec=MediaInfoExtractor)
extractor.file_path = Path(f"test_{case['testname']}")
# Mock the video_tracks with proper attributes
mock_track = MagicMock()
mock_track.height = case["resolution"][1]
mock_track.width = case["resolution"][0]
mock_track.interlaced = 'Yes' if case["interlaced"] else 'No'
extractor.video_tracks = [mock_track]
# Call the actual method
result = MediaInfoExtractor.extract_frame_class(extractor)
assert result == case["expected_frame_class"], f"Failed for {case['testname']}: expected {case['expected_frame_class']}, got {result}"