From cfeb333546de2607e99a476397d83fc12482f79d Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Sat, 8 Aug 2026 19:03:16 +0200 Subject: [PATCH] refactor: centralize model cache invalidation --- core/download/api.py | 8 ++------ core/download/aria2_backend.py | 3 +-- core/download/huggingface_xet.py | 3 +-- core/download/orchestrator.py | 3 +-- core/resolver.py | 8 +++++++- core/routes/registry.py | 7 ++++++- core/services/search_support_service.py | 10 ++-------- tests/test_refactoring_unification.py | 18 +++++++++++++++++- tests/test_search_support_routes.py | 6 ++---- 9 files changed, 39 insertions(+), 27 deletions(-) diff --git a/core/download/api.py b/core/download/api.py index 980d53e..e90d375 100644 --- a/core/download/api.py +++ b/core/download/api.py @@ -30,10 +30,7 @@ from ..path_utils import ( is_path_within, write_json_atomic, ) -from ..resolver import ( - invalidate_local_hash_match_cache, -) -from ..scanner import invalidate_model_files_cache +from ..resolver import invalidate_model_caches from ..settings import ( load_settings, normalize_download_backend, @@ -144,8 +141,7 @@ context.get_path_identity = get_path_identity context.is_path_within = is_path_within context.write_json_atomic = write_json_atomic context.get_category_folder_keys = get_category_folder_keys -context.invalidate_model_files_cache = invalidate_model_files_cache -context.invalidate_local_hash_match_cache = invalidate_local_hash_match_cache +context.invalidate_model_caches = invalidate_model_caches context._extract_expected_sha256 = metadata._extract_expected_sha256 context.normalize_metadata_file_path = metadata.normalize_metadata_file_path diff --git a/core/download/aria2_backend.py b/core/download/aria2_backend.py index 87fe61b..dd6e38b 100644 --- a/core/download/aria2_backend.py +++ b/core/download/aria2_backend.py @@ -967,8 +967,7 @@ def download_file_with_aria2( f"Size: {facade.format_bytes(size)}, Time: {elapsed:.1f}s, " f"Avg speed: {facade.format_bytes(int(avg_speed))}/s" ) - facade.invalidate_model_files_cache() - facade.invalidate_local_hash_match_cache() + facade.invalidate_model_caches() return result if state == "error": diff --git a/core/download/huggingface_xet.py b/core/download/huggingface_xet.py index 0790b2a..d6181f5 100644 --- a/core/download/huggingface_xet.py +++ b/core/download/huggingface_xet.py @@ -487,8 +487,7 @@ def download_huggingface_xet( f"Size: {facade.format_bytes(size)}, Time: {elapsed:.1f}s, " f"Avg speed: {facade.format_bytes(int(avg_speed))}/s" ) - facade.invalidate_model_files_cache() - facade.invalidate_local_hash_match_cache() + facade.invalidate_model_caches() return result except Exception as exc: was_cancelled = ( diff --git a/core/download/orchestrator.py b/core/download/orchestrator.py index 16f7448..231e5f2 100644 --- a/core/download/orchestrator.py +++ b/core/download/orchestrator.py @@ -296,8 +296,7 @@ def download_file( f"Size: {facade.format_bytes(downloaded)}, Time: {elapsed:.1f}s, " f"Avg speed: {facade.format_bytes(int(avg_speed))}/s" ) - facade.invalidate_model_files_cache() - facade.invalidate_local_hash_match_cache() + facade.invalidate_model_caches() except requests.exceptions.RequestException as exc: error_msg = facade._sanitize_download_error(exc) diff --git a/core/resolver.py b/core/resolver.py index 14d5fd2..6596e47 100644 --- a/core/resolver.py +++ b/core/resolver.py @@ -22,7 +22,7 @@ log = create_module_logger(__name__) from .local_hash_matches import collect_local_hash_matches_for_result from .matcher import find_matches, strip_known_model_extension -from .scanner import get_model_files +from .scanner import get_model_files, invalidate_model_files_cache from .type_utils import MODEL_EXTENSIONS as _MODEL_EXTENSIONS from .type_utils import ( as_dict, @@ -74,6 +74,12 @@ def invalidate_local_hash_match_cache() -> None: _LOCAL_HASH_MATCH_CACHE = None +def invalidate_model_caches() -> None: + """Clear all in-memory caches that depend on the local model inventory.""" + invalidate_model_files_cache() + invalidate_local_hash_match_cache() + + def _clone_hash_match(match: Dict[str, Any]) -> Dict[str, Any]: cloned = dict(match) if isinstance(cloned.get("model"), dict): diff --git a/core/routes/registry.py b/core/routes/registry.py index 73c3ed7..33f42dd 100644 --- a/core/routes/registry.py +++ b/core/routes/registry.py @@ -87,10 +87,15 @@ def register_routes(self): apply_resolution, get_local_model_hash_metadata, invalidate_local_hash_match_cache, + invalidate_model_caches, search_local_matches, search_local_matches_by_hash, ) - from ..scanner import find_local_file_path, get_model_files, invalidate_model_files_cache + from ..scanner import ( + find_local_file_path, + get_model_files, + invalidate_model_files_cache, + ) from ..services.model_service import ModelService from ..settings import ( TEMPLATE_KEY_ALIASES, diff --git a/core/services/search_support_service.py b/core/services/search_support_service.py index a7787de..b0372c7 100644 --- a/core/services/search_support_service.py +++ b/core/services/search_support_service.py @@ -26,12 +26,7 @@ class SearchSupportService: self.get_model_list_update_status = context.require( "get_model_list_update_status" ) - self.invalidate_local_hash_match_cache = context.require( - "invalidate_local_hash_match_cache" - ) - self.invalidate_model_files_cache = context.require( - "invalidate_model_files_cache" - ) + self.invalidate_model_caches = context.require("invalidate_model_caches") self.refresh_known_author_fallback_indexes = context.require( "refresh_known_author_fallback_indexes" ) @@ -48,8 +43,7 @@ class SearchSupportService: self.clear_all_search_caches() self.reload_popular_databases() self.reload_model_list() - self.invalidate_model_files_cache() - self.invalidate_local_hash_match_cache() + self.invalidate_model_caches() self.extension.search_result_timestamps.clear() self.extension.logger.info("Cleared backend search caches") return self.web.json_response({"success": True, "cleared": "all"}) diff --git a/tests/test_refactoring_unification.py b/tests/test_refactoring_unification.py index 9da3426..f80dde5 100644 --- a/tests/test_refactoring_unification.py +++ b/tests/test_refactoring_unification.py @@ -3,7 +3,7 @@ import os import sys import unittest from pathlib import Path -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock, call, patch parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if parent_dir not in sys.path: @@ -26,6 +26,22 @@ class TestRefactoringUnification(unittest.TestCase): def test_thresholds(self): self.assertEqual(MODEL_TITLE_MATCH_THRESHOLD, 82.0) + def test_invalidate_model_caches_clears_inventory_then_hash_matches(self): + from core import resolver + + calls = MagicMock() + with patch.object(resolver, "invalidate_model_files_cache") as invalidate_inventory: + with patch.object( + resolver, "invalidate_local_hash_match_cache" + ) as invalidate_hash_matches: + calls.attach_mock(invalidate_inventory, "inventory") + calls.attach_mock(invalidate_hash_matches, "hash_matches") + resolver.invalidate_model_caches() + + calls.assert_has_calls([call.inventory(), call.hash_matches()]) + invalidate_inventory.assert_called_once_with() + invalidate_hash_matches.assert_called_once_with() + def test_normalize_lora_manager_type(self): self.assertEqual(normalize_lora_manager_type("loras"), "lora") self.assertEqual(normalize_lora_manager_type("checkpoints"), "checkpoint") diff --git a/tests/test_search_support_routes.py b/tests/test_search_support_routes.py index beb82e1..c5088e6 100644 --- a/tests/test_search_support_routes.py +++ b/tests/test_search_support_routes.py @@ -72,8 +72,7 @@ def _build_routes(overrides=None): "get_model_list_update_status": MagicMock( return_value={"loaded": True} ), - "invalidate_local_hash_match_cache": MagicMock(), - "invalidate_model_files_cache": MagicMock(), + "invalidate_model_caches": MagicMock(), "json_api_endpoint": _json_api_endpoint, "refresh_known_author_fallback_indexes": MagicMock( return_value={"refreshed": True} @@ -106,8 +105,7 @@ async def test_clear_search_cache_route_clears_all_state(): values["clear_all_search_caches"].assert_called_once_with() values["reload_popular_databases"].assert_called_once_with() values["reload_model_list"].assert_called_once_with() - values["invalidate_model_files_cache"].assert_called_once_with() - values["invalidate_local_hash_match_cache"].assert_called_once_with() + values["invalidate_model_caches"].assert_called_once_with() assert values["self"].search_result_timestamps == {}