From b05775b581f1d51802573687e59d2ba16b8bf816 Mon Sep 17 00:00:00 2001 From: claude Date: Tue, 14 Jul 2026 09:07:21 +0000 Subject: [PATCH] fix(backend): fix pyright errors in lazy-load implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename _BACKENDS → _backends: pyright treats uppercase module-level names as constants and flags the reassignment in _get_backends() as reportConstantRedefinition; lowercase avoids this. - Add TYPE_CHECKING guard importing CommitCancelled/Freezer/get_freezer from .freeze: pyright cannot see module-level __getattr__ bindings, so reportUnsupportedDunderAll fired for those three __all__ entries; the guard makes them visible to the type checker without running at import time. - Update test_backend_selection.py to patch _backends (lowercase). --- bot_bottle/backend/__init__.py | 25 ++++++++++++++----------- tests/unit/test_backend_selection.py | 16 ++++++++-------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/bot_bottle/backend/__init__.py b/bot_bottle/backend/__init__.py index e10e394..c28774f 100644 --- a/bot_bottle/backend/__init__.py +++ b/bot_bottle/backend/__init__.py @@ -40,7 +40,7 @@ from abc import ABC, abstractmethod from contextlib import AbstractContextManager from dataclasses import dataclass from pathlib import Path -from typing import Any, Generic, Sequence, TypeVar +from typing import TYPE_CHECKING, Any, Generic, Sequence, TypeVar from ..agent_provider import AgentProvisionPlan, get_provider, build_agent_provision_plan from ..egress import EgressPlan @@ -54,6 +54,9 @@ from ..workspace import WorkspacePlan, workspace_plan from .print_util import print_multi, visible_agent_env_names from .util import host_skill_dir +if TYPE_CHECKING: + from .freeze import CommitCancelled, Freezer, get_freezer + @dataclass(frozen=True) class BottleSpec: @@ -572,31 +575,31 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]): Not called by the launch path or the test suite.""" -# _BACKENDS is None until the first call to _get_backends(), at which +# _backends is None until the first call to _get_backends(), at which # point all three concrete backend classes are imported and instantiated. # Keeping the imports out of module scope means that importing any # backend sub-module (e.g. `backend.docker.util`) no longer drags the # firecracker and macos-container implementations into memory. # -# Tests may replace _BACKENDS with a {name: fake} dict via patch.object; +# Tests may replace _backends with a {name: fake} dict via patch.object; # _get_backends() returns the current module-level value as-is when it # is not None, so test fakes take effect without triggering real imports. -_BACKENDS: dict[str, BottleBackend[Any, Any]] | None = None +_backends: dict[str, BottleBackend[Any, Any]] | None = None def _get_backends() -> dict[str, BottleBackend[Any, Any]]: """Return the registry of all backend instances, loading lazily on first call.""" - global _BACKENDS # pylint: disable=global-statement - if _BACKENDS is None: + global _backends # pylint: disable=global-statement + if _backends is None: from .docker import DockerBottleBackend from .firecracker import FirecrackerBottleBackend from .macos_container import MacosContainerBottleBackend - _BACKENDS = { + _backends = { "docker": DockerBottleBackend(), "firecracker": FirecrackerBottleBackend(), "macos-container": MacosContainerBottleBackend(), } - return _BACKENDS + return _backends def __getattr__(name: str) -> Any: @@ -717,12 +720,12 @@ __all__ = [ "BottleCleanupPlan", "BottlePlan", "BottleSpec", - "CommitCancelled", # pylint: disable=undefined-all-variable + "CommitCancelled", "ExecResult", - "Freezer", # pylint: disable=undefined-all-variable + "Freezer", "enumerate_active_agents", "get_bottle_backend", - "get_freezer", # pylint: disable=undefined-all-variable + "get_freezer", "has_backend", "known_backend_names", ] diff --git a/tests/unit/test_backend_selection.py b/tests/unit/test_backend_selection.py index f6718c9..b610c1a 100644 --- a/tests/unit/test_backend_selection.py +++ b/tests/unit/test_backend_selection.py @@ -40,7 +40,7 @@ class TestGetBottleBackend(unittest.TestCase): return True with patch.dict(os.environ, {}, clear=True), \ - patch.object(backend_mod, "_BACKENDS", { + patch.object(backend_mod, "_backends", { "macos-container": _FakeBackend(), "docker": _FakeBackend(), }): @@ -61,7 +61,7 @@ class TestGetBottleBackend(unittest.TestCase): with patch.dict(os.environ, {}, clear=True), \ patch.object(backend_mod.FirecrackerBottleBackend, "is_host_capable", classmethod(lambda cls: False)), \ - patch.object(backend_mod, "_BACKENDS", { + patch.object(backend_mod, "_backends", { "macos-container": _FakeBackend("macos-container", False), "docker": _FakeBackend("docker", True), }): @@ -83,7 +83,7 @@ class TestGetBottleBackend(unittest.TestCase): with patch.dict(os.environ, {}, clear=True), \ patch.object(backend_mod.FirecrackerBottleBackend, "is_host_capable", classmethod(lambda cls: True)), \ - patch.object(backend_mod, "_BACKENDS", { + patch.object(backend_mod, "_backends", { "macos-container": _FakeBackend("macos-container", False), "firecracker": _FakeBackend("firecracker", False), "docker": _FakeBackend("docker", True), @@ -133,7 +133,7 @@ class TestEnumerateActiveAgents(unittest.TestCase): return self._items with patch.object( - backend_mod, "_BACKENDS", + backend_mod, "_backends", {"docker": _FakeBackend([a]), "firecracker": _FakeBackend([b])}, ): self.assertEqual([a, b], enumerate_active_agents()) @@ -167,7 +167,7 @@ class TestEnumerateActiveAgents(unittest.TestCase): return self._items with patch.object( - backend_mod, "_BACKENDS", + backend_mod, "_backends", { "docker": _FakeBackend([newer, tie_b]), "firecracker": _FakeBackend([missing_metadata, tie_a]), @@ -187,7 +187,7 @@ class TestEnumerateActiveAgents(unittest.TestCase): return [] with patch.object( - backend_mod, "_BACKENDS", + backend_mod, "_backends", {"docker": _FakeBackend(), "firecracker": _FakeBackend()}, ): self.assertEqual([], enumerate_active_agents()) @@ -218,7 +218,7 @@ class TestEnumerateActiveAgents(unittest.TestCase): return self._items with patch.object( - backend_mod, "_BACKENDS", + backend_mod, "_backends", { "docker": _FakeBackend([present], available=True), "firecracker": _FakeBackend([hidden], available=False), @@ -234,7 +234,7 @@ class TestHasBackend(unittest.TestCase): return False with patch.object( - backend_mod, "_BACKENDS", {"docker": _FakeBackend()}, + backend_mod, "_backends", {"docker": _FakeBackend()}, ): from bot_bottle.backend import has_backend self.assertFalse(has_backend("docker"))