From 6ff887f004f820dcf6362c7de9a5a76ea01f7142 Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Sat, 1 Aug 2026 23:09:51 +0200 Subject: [PATCH] refactor: introduce RouteContext for route dependency registration --- core/routes/context.py | 48 +++++++++++++++++++++++++++++++ core/routes/directories.py | 5 +++- core/routes/downloads.py | 5 +++- core/routes/metadata.py | 5 +++- core/routes/model_info.py | 5 +++- core/routes/registry.py | 3 +- core/routes/search.py | 5 +++- core/routes/workflow.py | 7 +++-- tests/test_refactoring_targets.py | 21 ++++++++++++++ 9 files changed, 96 insertions(+), 8 deletions(-) create mode 100644 core/routes/context.py diff --git a/core/routes/context.py b/core/routes/context.py new file mode 100644 index 0000000..8fede6d --- /dev/null +++ b/core/routes/context.py @@ -0,0 +1,48 @@ +"""Dependency container shared by route registration modules.""" + +from collections.abc import Mapping +from dataclasses import dataclass +from types import MappingProxyType +from typing import Any + + +@dataclass(frozen=True, slots=True) +class RouteContext: + """Immutable, read-only collection of dependencies used by API routes.""" + + _values: Mapping[str, Any] + + def __post_init__(self): + object.__setattr__( + self, + "_values", + MappingProxyType(dict(self._values)), + ) + + @classmethod + def from_namespaces( + cls, + *namespaces: Mapping[str, Any], + ) -> "RouteContext": + """Build a context, with later namespaces taking precedence.""" + values = {} + for namespace in namespaces: + values.update(namespace) + return cls(values) + + def get(self, key: str, default: Any = None) -> Any: + """Return a dependency or the supplied default when it is optional.""" + return self._values.get(key, default) + + def require(self, key: str) -> Any: + """Return a dependency or fail with a descriptive configuration error.""" + try: + return self._values[key] + except KeyError as exc: + raise KeyError(f"Missing route dependency: {key}") from exc + + def __contains__(self, key: str) -> bool: + return key in self._values + + def __len__(self) -> int: + return len(self._values) diff --git a/core/routes/directories.py b/core/routes/directories.py index 63aa843..1a2382c 100644 --- a/core/routes/directories.py +++ b/core/routes/directories.py @@ -1,6 +1,9 @@ """Directories route registration.""" -def register_directory_routes(context): +from .context import RouteContext + + +def register_directory_routes(context: RouteContext): TEMPLATE_KEY_ALIASES = context.get('TEMPLATE_KEY_ALIASES') asyncio = context.get('asyncio') dedupe_local_base_directories = context.get('dedupe_local_base_directories') diff --git a/core/routes/downloads.py b/core/routes/downloads.py index f38097d..d42b3e7 100644 --- a/core/routes/downloads.py +++ b/core/routes/downloads.py @@ -1,6 +1,9 @@ """Downloads route registration.""" -def register_download_routes(context): +from .context import RouteContext + + +def register_download_routes(context: RouteContext): Aria2InstallError = context.get('Aria2InstallError') UnsafeUrlError = context.get('UnsafeUrlError') asyncio = context.get('asyncio') diff --git a/core/routes/metadata.py b/core/routes/metadata.py index 1ef4651..79f4cd6 100644 --- a/core/routes/metadata.py +++ b/core/routes/metadata.py @@ -1,6 +1,9 @@ """Metadata route registration.""" -def register_metadata_routes(context): +from .context import RouteContext + + +def register_metadata_routes(context: RouteContext): asyncio = context.get('asyncio') audit_metadata_sizes = context.get('audit_metadata_sizes') build_missing_local_metadata = context.get('build_missing_local_metadata') diff --git a/core/routes/model_info.py b/core/routes/model_info.py index a797b40..31a1d38 100644 --- a/core/routes/model_info.py +++ b/core/routes/model_info.py @@ -1,6 +1,9 @@ """Model Info route registration.""" -def register_model_info_routes(context): +from .context import RouteContext + + +def register_model_info_routes(context: RouteContext): UnsafeUrlError = context.get('UnsafeUrlError') asyncio = context.get('asyncio') build_civarchive_custom_result = context.get('build_civarchive_custom_result') diff --git a/core/routes/registry.py b/core/routes/registry.py index ec4900d..768f866 100644 --- a/core/routes/registry.py +++ b/core/routes/registry.py @@ -20,6 +20,7 @@ from ..path_utils import ( get_model_resolver_sidecar_path, get_safe_model_resolver_sidecar_path, ) +from .context import RouteContext def register_routes(self): @@ -240,7 +241,7 @@ def register_routes(self): ) register_base_model_routes(routes, web, json_api_endpoint) - route_context = {**globals(), **locals()} + route_context = RouteContext.from_namespaces(globals(), locals()) register_workflow_routes(route_context) register_metadata_routes(route_context) register_loaded_model_routes(route_context) diff --git a/core/routes/search.py b/core/routes/search.py index 30d9d7c..db23ac7 100644 --- a/core/routes/search.py +++ b/core/routes/search.py @@ -1,6 +1,9 @@ """Search route registration.""" -def register_search_routes(context): +from .context import RouteContext + + +def register_search_routes(context: RouteContext): CivArchiveSearchError = context.get('CivArchiveSearchError') asyncio = context.get('asyncio') build_search_result = context.get('build_search_result') diff --git a/core/routes/workflow.py b/core/routes/workflow.py index a45eb25..b241d36 100644 --- a/core/routes/workflow.py +++ b/core/routes/workflow.py @@ -1,6 +1,9 @@ """Workflow and loaded-model route registration.""" -def register_workflow_routes(context): +from .context import RouteContext + + +def register_workflow_routes(context: RouteContext): FileManagerError = context.get('FileManagerError') FileManagerUnavailableError = context.get('FileManagerUnavailableError') MODEL_RESOLVER_METADATA_SCHEMA = context.get('MODEL_RESOLVER_METADATA_SCHEMA') @@ -914,7 +917,7 @@ def register_workflow_routes(context): cancel_message="Stopping hash calculation..." ) -def register_loaded_model_routes(context): +def register_loaded_model_routes(context: RouteContext): adapt_custom_node_loaded_model = context.get('adapt_custom_node_loaded_model') asyncio = context.get('asyncio') get_filename_from_path = context.get('get_filename_from_path') diff --git a/tests/test_refactoring_targets.py b/tests/test_refactoring_targets.py index beb0e83..4bf967b 100644 --- a/tests/test_refactoring_targets.py +++ b/tests/test_refactoring_targets.py @@ -245,6 +245,27 @@ class TestRefactoringTargets(unittest.IsolatedAsyncioTestCase): self.assertEqual(node_mod._version_sort_key("v1.10.2-beta"), (1, 10, 2)) self.assertEqual(node_mod._version_sort_key("unknown"), ()) + def test_route_context_merges_namespaces_and_validates_required_values(self): + context_module = importlib.import_module( + "comfyui-model-resolver.core.routes.context" + ) + values = {"shared": "first", "left": 1} + context = context_module.RouteContext.from_namespaces( + values, + {"shared": "second", "right": 2}, + ) + values["left"] = 99 + + self.assertEqual(context.get("shared"), "second") + self.assertEqual(context.get("left"), 1) + self.assertEqual(context.get("missing"), None) + self.assertEqual(context.get("missing", "fallback"), "fallback") + self.assertEqual(context.require("right"), 2) + self.assertIn("right", context) + self.assertEqual(len(context), 3) + with self.assertRaisesRegex(KeyError, "Missing route dependency: missing"): + context.require("missing") + def test_project_version_info_reports_remote_update(self): version_module = importlib.import_module("comfyui-model-resolver.core.version") network_module = importlib.import_module("comfyui-model-resolver.core.network_utils")