fix(backend): fix pyright errors in lazy-load implementation
lint / lint (push) Successful in 2m13s
test / unit (pull_request) Successful in 1m6s
test / integration (pull_request) Successful in 24s
test / coverage (pull_request) Successful in 1m18s

- 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).
This commit is contained in:
2026-07-14 09:07:21 +00:00
parent 2a2669b69f
commit b05775b581
2 changed files with 22 additions and 19 deletions
+14 -11
View File
@@ -40,7 +40,7 @@ from abc import ABC, abstractmethod
from contextlib import AbstractContextManager from contextlib import AbstractContextManager
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path 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 ..agent_provider import AgentProvisionPlan, get_provider, build_agent_provision_plan
from ..egress import EgressPlan 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 .print_util import print_multi, visible_agent_env_names
from .util import host_skill_dir from .util import host_skill_dir
if TYPE_CHECKING:
from .freeze import CommitCancelled, Freezer, get_freezer
@dataclass(frozen=True) @dataclass(frozen=True)
class BottleSpec: class BottleSpec:
@@ -572,31 +575,31 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]):
Not called by the launch path or the test suite.""" 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. # point all three concrete backend classes are imported and instantiated.
# Keeping the imports out of module scope means that importing any # Keeping the imports out of module scope means that importing any
# backend sub-module (e.g. `backend.docker.util`) no longer drags the # backend sub-module (e.g. `backend.docker.util`) no longer drags the
# firecracker and macos-container implementations into memory. # 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 # _get_backends() returns the current module-level value as-is when it
# is not None, so test fakes take effect without triggering real imports. # 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]]: def _get_backends() -> dict[str, BottleBackend[Any, Any]]:
"""Return the registry of all backend instances, loading lazily on first call.""" """Return the registry of all backend instances, loading lazily on first call."""
global _BACKENDS # pylint: disable=global-statement global _backends # pylint: disable=global-statement
if _BACKENDS is None: if _backends is None:
from .docker import DockerBottleBackend from .docker import DockerBottleBackend
from .firecracker import FirecrackerBottleBackend from .firecracker import FirecrackerBottleBackend
from .macos_container import MacosContainerBottleBackend from .macos_container import MacosContainerBottleBackend
_BACKENDS = { _backends = {
"docker": DockerBottleBackend(), "docker": DockerBottleBackend(),
"firecracker": FirecrackerBottleBackend(), "firecracker": FirecrackerBottleBackend(),
"macos-container": MacosContainerBottleBackend(), "macos-container": MacosContainerBottleBackend(),
} }
return _BACKENDS return _backends
def __getattr__(name: str) -> Any: def __getattr__(name: str) -> Any:
@@ -717,12 +720,12 @@ __all__ = [
"BottleCleanupPlan", "BottleCleanupPlan",
"BottlePlan", "BottlePlan",
"BottleSpec", "BottleSpec",
"CommitCancelled", # pylint: disable=undefined-all-variable "CommitCancelled",
"ExecResult", "ExecResult",
"Freezer", # pylint: disable=undefined-all-variable "Freezer",
"enumerate_active_agents", "enumerate_active_agents",
"get_bottle_backend", "get_bottle_backend",
"get_freezer", # pylint: disable=undefined-all-variable "get_freezer",
"has_backend", "has_backend",
"known_backend_names", "known_backend_names",
] ]
+8 -8
View File
@@ -40,7 +40,7 @@ class TestGetBottleBackend(unittest.TestCase):
return True return True
with patch.dict(os.environ, {}, clear=True), \ with patch.dict(os.environ, {}, clear=True), \
patch.object(backend_mod, "_BACKENDS", { patch.object(backend_mod, "_backends", {
"macos-container": _FakeBackend(), "macos-container": _FakeBackend(),
"docker": _FakeBackend(), "docker": _FakeBackend(),
}): }):
@@ -61,7 +61,7 @@ class TestGetBottleBackend(unittest.TestCase):
with patch.dict(os.environ, {}, clear=True), \ with patch.dict(os.environ, {}, clear=True), \
patch.object(backend_mod.FirecrackerBottleBackend, patch.object(backend_mod.FirecrackerBottleBackend,
"is_host_capable", classmethod(lambda cls: False)), \ "is_host_capable", classmethod(lambda cls: False)), \
patch.object(backend_mod, "_BACKENDS", { patch.object(backend_mod, "_backends", {
"macos-container": _FakeBackend("macos-container", False), "macos-container": _FakeBackend("macos-container", False),
"docker": _FakeBackend("docker", True), "docker": _FakeBackend("docker", True),
}): }):
@@ -83,7 +83,7 @@ class TestGetBottleBackend(unittest.TestCase):
with patch.dict(os.environ, {}, clear=True), \ with patch.dict(os.environ, {}, clear=True), \
patch.object(backend_mod.FirecrackerBottleBackend, patch.object(backend_mod.FirecrackerBottleBackend,
"is_host_capable", classmethod(lambda cls: True)), \ "is_host_capable", classmethod(lambda cls: True)), \
patch.object(backend_mod, "_BACKENDS", { patch.object(backend_mod, "_backends", {
"macos-container": _FakeBackend("macos-container", False), "macos-container": _FakeBackend("macos-container", False),
"firecracker": _FakeBackend("firecracker", False), "firecracker": _FakeBackend("firecracker", False),
"docker": _FakeBackend("docker", True), "docker": _FakeBackend("docker", True),
@@ -133,7 +133,7 @@ class TestEnumerateActiveAgents(unittest.TestCase):
return self._items return self._items
with patch.object( with patch.object(
backend_mod, "_BACKENDS", backend_mod, "_backends",
{"docker": _FakeBackend([a]), "firecracker": _FakeBackend([b])}, {"docker": _FakeBackend([a]), "firecracker": _FakeBackend([b])},
): ):
self.assertEqual([a, b], enumerate_active_agents()) self.assertEqual([a, b], enumerate_active_agents())
@@ -167,7 +167,7 @@ class TestEnumerateActiveAgents(unittest.TestCase):
return self._items return self._items
with patch.object( with patch.object(
backend_mod, "_BACKENDS", backend_mod, "_backends",
{ {
"docker": _FakeBackend([newer, tie_b]), "docker": _FakeBackend([newer, tie_b]),
"firecracker": _FakeBackend([missing_metadata, tie_a]), "firecracker": _FakeBackend([missing_metadata, tie_a]),
@@ -187,7 +187,7 @@ class TestEnumerateActiveAgents(unittest.TestCase):
return [] return []
with patch.object( with patch.object(
backend_mod, "_BACKENDS", backend_mod, "_backends",
{"docker": _FakeBackend(), "firecracker": _FakeBackend()}, {"docker": _FakeBackend(), "firecracker": _FakeBackend()},
): ):
self.assertEqual([], enumerate_active_agents()) self.assertEqual([], enumerate_active_agents())
@@ -218,7 +218,7 @@ class TestEnumerateActiveAgents(unittest.TestCase):
return self._items return self._items
with patch.object( with patch.object(
backend_mod, "_BACKENDS", backend_mod, "_backends",
{ {
"docker": _FakeBackend([present], available=True), "docker": _FakeBackend([present], available=True),
"firecracker": _FakeBackend([hidden], available=False), "firecracker": _FakeBackend([hidden], available=False),
@@ -234,7 +234,7 @@ class TestHasBackend(unittest.TestCase):
return False return False
with patch.object( with patch.object(
backend_mod, "_BACKENDS", {"docker": _FakeBackend()}, backend_mod, "_backends", {"docker": _FakeBackend()},
): ):
from bot_bottle.backend import has_backend from bot_bottle.backend import has_backend
self.assertFalse(has_backend("docker")) self.assertFalse(has_backend("docker"))