diff --git a/bot_bottle/backend/docker/util.py b/bot_bottle/backend/docker/util.py index 0124c982..b5e20430 100644 --- a/bot_bottle/backend/docker/util.py +++ b/bot_bottle/backend/docker/util.py @@ -11,7 +11,12 @@ import subprocess from typing import Iterator from ...log import die, info -from ...util import slugify +from ...util import slugify as _slugify + + +def slugify(name: str) -> str: + """Compatibility wrapper; new generic callers import ``bot_bottle.util``.""" + return _slugify(name) def run_docker( diff --git a/bot_bottle/backend/preparation.py b/bot_bottle/backend/preparation.py index 6b445964..756011c4 100644 --- a/bot_bottle/backend/preparation.py +++ b/bot_bottle/backend/preparation.py @@ -67,11 +67,14 @@ class BottlePreparationPlanner: def prepare(self, spec: BottleSpec) -> PreparedBottle: backend = self._backend - manifest = backend._validate(spec) + # These are deliberately protected backend hooks: only this shared + # planner orchestrates them, while concrete backends provide the + # implementation. + manifest = backend._validate(spec) # pylint: disable=protected-access if not backend.supports_nested_containers: reject_nested_containers(backend.name, manifest) - backend._preflight() + backend._preflight() # pylint: disable=protected-access manifest = GitGate().preflight_host_keys( manifest, headless=spec.headless, @@ -98,7 +101,9 @@ class BottlePreparationPlanner: state_dir=agent_dir, instance_name=f"bot-bottle-{slug}", prompt_file=prompt_file, - guest_env=backend._build_guest_env(resolved_env), + guest_env=backend._build_guest_env( # pylint: disable=protected-access + resolved_env + ), forward_host_credentials=provider_config.forward_host_credentials, auth_token=provider_config.auth_token, host_env=dict(os.environ), diff --git a/bot_bottle/cli/commands/supervise.py b/bot_bottle/cli/commands/supervise.py index cd2c5b7d..05ce18c8 100644 --- a/bot_bottle/cli/commands/supervise.py +++ b/bot_bottle/cli/commands/supervise.py @@ -508,7 +508,10 @@ def _detail_view( return "reject aborted (empty reason)" -def _modify(stdscr: "curses._CursesWindow", qp: QueuedProposal) -> str | None: # type: ignore # pragma: no cover +def _modify( + stdscr: "curses._CursesWindow", # type: ignore + qp: QueuedProposal, +) -> str | None: # pragma: no cover """Suspend curses, open $EDITOR on the proposed file, return edited content.""" suffix = _suffix_for_tool(qp.proposal.tool) curses.endwin() diff --git a/bot_bottle/gateway/egress/matching.py b/bot_bottle/gateway/egress/matching.py index 2367e84e..455d3c70 100644 --- a/bot_bottle/gateway/egress/matching.py +++ b/bot_bottle/gateway/egress/matching.py @@ -16,7 +16,11 @@ def _path_matches(pm: PathMatch, request_path: str) -> bool: if not pm.value.endswith("/"): return request_path.startswith(pm.value + "/") return request_path.startswith(pm.value) - return pm.type == "regex" and pm.compiled is not None and pm.compiled.search(request_path) is not None + return ( + pm.type == "regex" + and pm.compiled is not None + and pm.compiled.search(request_path) is not None + ) def _entry_matches( diff --git a/bot_bottle/gateway/egress/schema.py b/bot_bottle/gateway/egress/schema.py index be773ebe..2d1b2698 100644 --- a/bot_bottle/gateway/egress/schema.py +++ b/bot_bottle/gateway/egress/schema.py @@ -347,5 +347,3 @@ def load_config(text: str) -> "Config": except YamlSubsetError as e: raise ValueError(f"routes payload: invalid YAML: {e}") from e return parse_config(payload) - - diff --git a/tests/unit/test_architecture_guardrails.py b/tests/unit/test_architecture_guardrails.py index d937d188..c4a0ca83 100644 --- a/tests/unit/test_architecture_guardrails.py +++ b/tests/unit/test_architecture_guardrails.py @@ -21,9 +21,12 @@ class TestCliBackendBoundaries(unittest.TestCase): for path in (ROOT / "bot_bottle" / "cli").rglob("*.py"): tree = ast.parse(path.read_text(), filename=str(path)) for node in ast.walk(tree): - module = node.module if isinstance(node, ast.ImportFrom) else None - if module and module.startswith(forbidden): - violations.append(f"{path.relative_to(ROOT)}:{node.lineno}: {module}") + if isinstance(node, ast.ImportFrom): + module = node.module + if module and module.startswith(forbidden): + violations.append( + f"{path.relative_to(ROOT)}:{node.lineno}: {module}" + ) if isinstance(node, ast.Import): violations.extend( f"{path.relative_to(ROOT)}:{node.lineno}: {alias.name}" diff --git a/tests/unit/test_orchestrator_server.py b/tests/unit/test_orchestrator_server.py index e2ecadb4..50657485 100644 --- a/tests/unit/test_orchestrator_server.py +++ b/tests/unit/test_orchestrator_server.py @@ -672,13 +672,13 @@ class TestReconcileRoute(unittest.TestCase): self.orch, "POST", "/reconcile", _body({"live_source_ips": [None, 7, "10.0.0.9"]})) self.assertEqual(400, status) - self.assertIn("live_source_ips", payload["error"]) + self.assertIn("live_source_ips", str(payload["error"])) def test_empty_live_source_ip_is_rejected(self) -> None: status, payload = dispatch( self.orch, "POST", "/reconcile", _body({"live_source_ips": [""]})) self.assertEqual(400, status) - self.assertIn("live_source_ips", payload["error"]) + self.assertIn("live_source_ips", str(payload["error"])) def test_invalid_grace_seconds_is_rejected(self) -> None: for value in (True, "30", -1, float("inf"), float("nan")): @@ -687,4 +687,4 @@ class TestReconcileRoute(unittest.TestCase): self.orch, "POST", "/reconcile", _body({"live_source_ips": [], "grace_seconds": value})) self.assertEqual(400, status) - self.assertIn("grace_seconds", payload["error"]) + self.assertIn("grace_seconds", str(payload["error"]))