From 87c4c2df089d764c3d9f66a65478b9940a2d41a4 Mon Sep 17 00:00:00 2001 From: rookiestar28 Date: Mon, 3 Aug 2026 16:41:28 +0800 Subject: [PATCH] fix(startup): restore packaged route imports --- services/bootstrap/registration.py | 4 ++- services/route_bootstrap_contract.py | 7 +++- tests/test_r188_startup_lifecycle.py | 3 +- tests/test_r233_service_domain_packages.py | 41 ++++++++++++++++++++++ 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/services/bootstrap/registration.py b/services/bootstrap/registration.py index 4c0e18a..406fb37 100644 --- a/services/bootstrap/registration.py +++ b/services/bootstrap/registration.py @@ -217,7 +217,9 @@ def _do_full_registration(server) -> None: from ..scheduler.runner import get_scheduler_runner, start_scheduler from ..templates import get_template_service - contract = load_route_bootstrap_contract(__package__) + # IMPORTANT: the contract owns its stable relative-import anchor; forwarding this + # nested package resolves `..api` to the nonexistent `services.api` namespace. + contract = load_route_bootstrap_contract() register_approval_routes = contract["register_approval_routes"] BridgeHandlers = contract["BridgeHandlers"] register_preset_routes = contract["register_preset_routes"] diff --git a/services/route_bootstrap_contract.py b/services/route_bootstrap_contract.py index a02f8bc..9f67f69 100644 --- a/services/route_bootstrap_contract.py +++ b/services/route_bootstrap_contract.py @@ -81,7 +81,12 @@ def _validate_symbol(spec: BootstrapSymbolSpec, value: Any) -> None: ) -def load_route_bootstrap_contract(package_name: str | None) -> dict[str, Any]: +def load_route_bootstrap_contract( + package_name: str | None = None, +) -> dict[str, Any]: + # IMPORTANT: default to this owner module. A nested registration caller's package + # resolves `..api` as `services.api` and breaks ComfyUI route registration. + package_name = __package__ if package_name is None else package_name contract: dict[str, Any] = {} for spec in ROUTE_BOOTSTRAP_SPECS: (value,) = import_attrs_dual( diff --git a/tests/test_r188_startup_lifecycle.py b/tests/test_r188_startup_lifecycle.py index 959565e..5d8be1d 100644 --- a/tests/test_r188_startup_lifecycle.py +++ b/tests/test_r188_startup_lifecycle.py @@ -178,7 +178,7 @@ class TestRouteBootstrapWarmupBoundary(unittest.TestCase): patch( "services.route_bootstrap_contract.load_route_bootstrap_contract", return_value=contract, - ), + ) as contract_loader, patch("services.scheduler.runner.get_scheduler_runner") as get_runner, patch("services.scheduler.runner.start_scheduler"), patch( @@ -193,6 +193,7 @@ class TestRouteBootstrapWarmupBoundary(unittest.TestCase): route_bootstrap._mark_startup_ready_and_start_warmups() elapsed = time.monotonic() - started_at + contract_loader.assert_called_once_with() diagnostics = get_startup_diagnostics() release.set() diff --git a/tests/test_r233_service_domain_packages.py b/tests/test_r233_service_domain_packages.py index ffec2b3..a9f1179 100644 --- a/tests/test_r233_service_domain_packages.py +++ b/tests/test_r233_service_domain_packages.py @@ -63,6 +63,47 @@ class ServiceDomainPackageContractTests(unittest.TestCase): ): sys.modules.pop(module_name, None) + def test_packaged_route_contract_defaults_to_owning_services_namespace(self): + package_name = "r233_comfyui_route_contract_probe" + package = types.ModuleType(package_name) + package.__path__ = [str(ROOT)] + package.__package__ = package_name + package.__spec__ = importlib.machinery.ModuleSpec( + package_name, + loader=None, + is_package=True, + ) + sys.modules[package_name] = package + try: + contract_module = importlib.import_module( + f"{package_name}.services.route_bootstrap_contract" + ) + + contract = contract_module.load_route_bootstrap_contract() + + self.assertEqual( + set(contract), + { + "BridgeHandlers", + "register_approval_routes", + "register_preset_routes", + "register_routes", + "register_schedule_routes", + "register_trigger_routes", + }, + ) + for value in contract.values(): + self.assertTrue( + value.__module__.startswith(f"{package_name}.api."), + value.__module__, + ) + finally: + for module_name in tuple(sys.modules): + if module_name == package_name or module_name.startswith( + f"{package_name}." + ): + sys.modules.pop(module_name, None) + def test_lifecycle_and_posture_singletons_are_not_duplicated(self): legacy_lifecycle = importlib.import_module("services.startup_lifecycle") owned_lifecycle = importlib.import_module("services.bootstrap.lifecycle")