fix: resolve pylint/pyright issues in runner, sidecar, and test_runner
runner.py: use 'from bot_bottle import api' (satisfies R0402) with type: ignore and pylint disable for the cross-branch dependency on bot_bottle.api (added in PR #318, which merges before this one). sidecar.py: add pylint disable for intentional broad-exception-caught. test_runner.py: annotate _make_api_stub(**overrides: object) -> Any and type stub variable as Any to allow attribute assignment without type: ignore per-line. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -47,9 +47,10 @@ def slugify(label: str) -> str:
|
|||||||
class ProgrammaticBottleRunner:
|
class ProgrammaticBottleRunner:
|
||||||
"""Calls into the bot_bottle Python API directly — no subprocess.
|
"""Calls into the bot_bottle Python API directly — no subprocess.
|
||||||
|
|
||||||
Imports are deferred to call time so this module can be imported
|
Imports are deferred to call time so tests can inject a mock into
|
||||||
before `bot_bottle.api` is available (e.g. in isolated test runs
|
sys.modules['bot_bottle.api'] before calling runner methods.
|
||||||
that mock the API surface)."""
|
bot_bottle.api is added in the forge-native-integration PR (#318),
|
||||||
|
which merges before this one."""
|
||||||
|
|
||||||
def start(
|
def start(
|
||||||
self,
|
self,
|
||||||
@@ -60,7 +61,7 @@ class ProgrammaticBottleRunner:
|
|||||||
prompt: str,
|
prompt: str,
|
||||||
forge_env: dict[str, str],
|
forge_env: dict[str, str],
|
||||||
) -> str:
|
) -> str:
|
||||||
import bot_bottle.api as api
|
from bot_bottle import api # type: ignore[import-not-found] # pylint: disable=import-error,no-name-in-module
|
||||||
return api.start_headless(
|
return api.start_headless(
|
||||||
agent,
|
agent,
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
@@ -70,13 +71,13 @@ class ProgrammaticBottleRunner:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def freeze(self, slug: str) -> None:
|
def freeze(self, slug: str) -> None:
|
||||||
import bot_bottle.api as api
|
from bot_bottle import api # type: ignore[import-not-found] # pylint: disable=import-error,no-name-in-module
|
||||||
api.freeze(slug)
|
api.freeze(slug)
|
||||||
|
|
||||||
def resume(self, slug: str, prompt: str) -> None:
|
def resume(self, slug: str, prompt: str) -> None:
|
||||||
import bot_bottle.api as api
|
from bot_bottle import api # type: ignore[import-not-found] # pylint: disable=import-error,no-name-in-module
|
||||||
api.resume_headless(slug, prompt=prompt)
|
api.resume_headless(slug, prompt=prompt)
|
||||||
|
|
||||||
def destroy(self, slug: str) -> None:
|
def destroy(self, slug: str) -> None:
|
||||||
import bot_bottle.api as api
|
from bot_bottle import api # type: ignore[import-not-found] # pylint: disable=import-error,no-name-in-module
|
||||||
api.destroy(slug)
|
api.destroy(slug)
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ class ForgeSidecar:
|
|||||||
def dispatch(self, method: str, params: dict[str, Any]) -> dict[str, Any]:
|
def dispatch(self, method: str, params: dict[str, Any]) -> dict[str, Any]:
|
||||||
try:
|
try:
|
||||||
result = self._invoke(method, params)
|
result = self._invoke(method, params)
|
||||||
except Exception as exc: # noqa: BLE001 — surface as JSON-RPC error
|
except Exception as exc: # noqa: BLE001 # pylint: disable=broad-exception-caught
|
||||||
self._log.record(method, params.get("number"), f"error: {exc}")
|
self._log.record(method, params.get("number"), f"error: {exc}")
|
||||||
return {"ok": False, "error": str(exc)}
|
return {"ok": False, "error": str(exc)}
|
||||||
return {"ok": True, "result": result}
|
return {"ok": True, "result": result}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|||||||
import sys
|
import sys
|
||||||
import types
|
import types
|
||||||
import unittest
|
import unittest
|
||||||
|
from typing import Any
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
from bot_bottle.orchestrator.runner import ProgrammaticBottleRunner, slugify
|
from bot_bottle.orchestrator.runner import ProgrammaticBottleRunner, slugify
|
||||||
@@ -19,9 +20,9 @@ class SlugifyTest(unittest.TestCase):
|
|||||||
self.assertEqual("a-b-c", slugify(" A_B/C!! "))
|
self.assertEqual("a-b-c", slugify(" A_B/C!! "))
|
||||||
|
|
||||||
|
|
||||||
def _make_api_stub(**overrides):
|
def _make_api_stub(**overrides: object) -> Any:
|
||||||
"""Return a mock bot_bottle.api module with sensible defaults."""
|
"""Return a mock bot_bottle.api module with sensible defaults."""
|
||||||
stub = types.ModuleType("bot_bottle.api")
|
stub: Any = types.ModuleType("bot_bottle.api")
|
||||||
stub.start_headless = MagicMock(return_value="impl-r-17")
|
stub.start_headless = MagicMock(return_value="impl-r-17")
|
||||||
stub.freeze = MagicMock()
|
stub.freeze = MagicMock()
|
||||||
stub.resume_headless = MagicMock()
|
stub.resume_headless = MagicMock()
|
||||||
@@ -32,22 +33,22 @@ def _make_api_stub(**overrides):
|
|||||||
|
|
||||||
|
|
||||||
class ProgrammaticRunnerTest(unittest.TestCase):
|
class ProgrammaticRunnerTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self) -> None:
|
||||||
self._api = _make_api_stub()
|
self._api: Any = _make_api_stub()
|
||||||
sys.modules["bot_bottle.api"] = self._api
|
sys.modules["bot_bottle.api"] = self._api
|
||||||
self.runner = ProgrammaticBottleRunner()
|
self.runner = ProgrammaticBottleRunner()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self) -> None:
|
||||||
sys.modules.pop("bot_bottle.api", None)
|
sys.modules.pop("bot_bottle.api", None)
|
||||||
|
|
||||||
def test_start_returns_slug_from_api(self):
|
def test_start_returns_slug_from_api(self) -> None:
|
||||||
slug = self.runner.start(
|
slug = self.runner.start(
|
||||||
agent="impl", bottles=["claude", "dev"], label="impl-r-17",
|
agent="impl", bottles=["claude", "dev"], label="impl-r-17",
|
||||||
prompt="do it", forge_env={"FORGE_OWNER": "didericis"},
|
prompt="do it", forge_env={"FORGE_OWNER": "didericis"},
|
||||||
)
|
)
|
||||||
self.assertEqual("impl-r-17", slug)
|
self.assertEqual("impl-r-17", slug)
|
||||||
|
|
||||||
def test_start_forwards_all_args(self):
|
def test_start_forwards_all_args(self) -> None:
|
||||||
self.runner.start(
|
self.runner.start(
|
||||||
agent="impl", bottles=["claude", "dev"], label="impl-r-17",
|
agent="impl", bottles=["claude", "dev"], label="impl-r-17",
|
||||||
prompt="do it", forge_env={"FORGE_OWNER": "didericis"},
|
prompt="do it", forge_env={"FORGE_OWNER": "didericis"},
|
||||||
@@ -60,32 +61,32 @@ class ProgrammaticRunnerTest(unittest.TestCase):
|
|||||||
forge_env={"FORGE_OWNER": "didericis"},
|
forge_env={"FORGE_OWNER": "didericis"},
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_start_no_bottles_passes_none(self):
|
def test_start_no_bottles_passes_none(self) -> None:
|
||||||
self.runner.start(agent="impl", bottles=[], label="l", prompt="p", forge_env={})
|
self.runner.start(agent="impl", bottles=[], label="l", prompt="p", forge_env={})
|
||||||
call_kwargs = self._api.start_headless.call_args[1]
|
call_kwargs = self._api.start_headless.call_args[1]
|
||||||
self.assertIsNone(call_kwargs["bottles"])
|
self.assertIsNone(call_kwargs["bottles"])
|
||||||
|
|
||||||
def test_freeze_delegates_to_api(self):
|
def test_freeze_delegates_to_api(self) -> None:
|
||||||
self.runner.freeze("slug-1")
|
self.runner.freeze("slug-1")
|
||||||
self._api.freeze.assert_called_once_with("slug-1")
|
self._api.freeze.assert_called_once_with("slug-1")
|
||||||
|
|
||||||
def test_freeze_returns_none(self):
|
def test_freeze_returns_none(self) -> None:
|
||||||
result = self.runner.freeze("slug-1")
|
result = self.runner.freeze("slug-1")
|
||||||
self.assertIsNone(result)
|
self.assertIsNone(result)
|
||||||
|
|
||||||
def test_resume_delegates_to_api(self):
|
def test_resume_delegates_to_api(self) -> None:
|
||||||
self.runner.resume("slug-1", "address review")
|
self.runner.resume("slug-1", "address review")
|
||||||
self._api.resume_headless.assert_called_once_with("slug-1", prompt="address review")
|
self._api.resume_headless.assert_called_once_with("slug-1", prompt="address review")
|
||||||
|
|
||||||
def test_resume_returns_none(self):
|
def test_resume_returns_none(self) -> None:
|
||||||
result = self.runner.resume("slug-1", "p")
|
result = self.runner.resume("slug-1", "p")
|
||||||
self.assertIsNone(result)
|
self.assertIsNone(result)
|
||||||
|
|
||||||
def test_destroy_delegates_to_api(self):
|
def test_destroy_delegates_to_api(self) -> None:
|
||||||
self.runner.destroy("slug-7")
|
self.runner.destroy("slug-7")
|
||||||
self._api.destroy.assert_called_once_with("slug-7")
|
self._api.destroy.assert_called_once_with("slug-7")
|
||||||
|
|
||||||
def test_destroy_returns_none(self):
|
def test_destroy_returns_none(self) -> None:
|
||||||
result = self.runner.destroy("slug-7")
|
result = self.runner.destroy("slug-7")
|
||||||
self.assertIsNone(result)
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user