chore(types): add pyright strict config and fix resulting errors
Adds pyrightconfig.json (strict, Python 3.11) covering cli.py, claude_bottle/, and tests/. Fixes the 49 strict-mode errors: - Type DockerBottle.teardown as Callable[[], None]. - ResolvedEnv default_factory uses parameterized list[str] / dict[str, str]. - Erase BottleBackend generics at the registry boundary (BottleBackend[Any, Any]) since selection is runtime-driven and callers use the unparameterized interface. - DockerBottleBackend.launch returns Generator[DockerBottle, None, None]; @contextmanager now flags Iterator returns as deprecated. - Sidestep cli.list submodule shadowing builtins.list in main()'s argv annotation via an aliased re-import in cli/__init__.py. - Cast cfg[...] results in test_pipelock_yaml at the dict[str, object] boundary. - Annotate write_fixture's fn parameter and _manifest_with_runtime's return type.
This commit is contained in:
@@ -34,7 +34,7 @@ from abc import ABC, abstractmethod
|
||||
from contextlib import AbstractContextManager
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Generic, TypeVar
|
||||
from typing import Any, Generic, TypeVar
|
||||
|
||||
from ..log import die
|
||||
from ..manifest import Manifest
|
||||
@@ -199,14 +199,16 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]):
|
||||
from .docker import DockerBottleBackend # noqa: E402
|
||||
|
||||
|
||||
# The dict carries heterogeneous BottleBackend specializations; callers
|
||||
# use it through the unparameterized BottleBackend interface.
|
||||
_BACKENDS: dict[str, BottleBackend] = {
|
||||
# The dict is heterogeneous: each value is a BottleBackend specialized
|
||||
# over its own plan type. Concrete plan types are erased here because
|
||||
# the registry is selected at runtime and the CLI only needs the
|
||||
# unparameterized methods (prepare → plan → launch(plan), cleanup, etc.).
|
||||
_BACKENDS: dict[str, BottleBackend[Any, Any]] = {
|
||||
"docker": DockerBottleBackend(),
|
||||
}
|
||||
|
||||
|
||||
def get_bottle_backend() -> BottleBackend:
|
||||
def get_bottle_backend() -> BottleBackend[Any, Any]:
|
||||
"""Resolve the bottle backend for the active environment. Dies with
|
||||
a pointer at the known backends if CLAUDE_BOTTLE_BACKEND names an
|
||||
unimplemented one."""
|
||||
|
||||
@@ -16,7 +16,7 @@ import subprocess
|
||||
import sys
|
||||
from contextlib import ExitStack, contextmanager
|
||||
from pathlib import Path
|
||||
from typing import Iterator, Sequence
|
||||
from typing import Generator, Sequence
|
||||
|
||||
from ... import pipelock
|
||||
from ...env import ResolvedEnv, resolve_env
|
||||
@@ -165,7 +165,7 @@ class DockerBottleBackend(BottleBackend["DockerBottlePlan", "DockerBottleCleanup
|
||||
env_file.chmod(0o600)
|
||||
|
||||
@contextmanager
|
||||
def launch(self, plan: DockerBottlePlan) -> Iterator[DockerBottle]:
|
||||
def launch(self, plan: DockerBottlePlan) -> Generator[DockerBottle, None, None]:
|
||||
"""Build, launch, and provision a Docker bottle. Teardown on exit."""
|
||||
stack = ExitStack()
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ prompt was provisioned.
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
from typing import Callable
|
||||
|
||||
from .. import Bottle
|
||||
|
||||
@@ -16,7 +17,12 @@ from .. import Bottle
|
||||
class DockerBottle(Bottle):
|
||||
"""Concrete Bottle for Docker."""
|
||||
|
||||
def __init__(self, container: str, teardown, prompt_path_in_container: str | None):
|
||||
def __init__(
|
||||
self,
|
||||
container: str,
|
||||
teardown: Callable[[], None],
|
||||
prompt_path_in_container: str | None,
|
||||
):
|
||||
self.name = container
|
||||
self._teardown = teardown
|
||||
self._prompt_path = prompt_path_in_container
|
||||
|
||||
@@ -9,13 +9,15 @@ import sys
|
||||
|
||||
from ..log import Die, die
|
||||
from ._common import PROG
|
||||
from . import list as _list_mod
|
||||
from .cleanup import cmd_cleanup
|
||||
from .edit import cmd_edit
|
||||
from .info import cmd_info
|
||||
from .init import cmd_init
|
||||
from .list import cmd_list
|
||||
from .start import cmd_start
|
||||
|
||||
cmd_list = _list_mod.cmd_list
|
||||
|
||||
COMMANDS = {
|
||||
"cleanup": cmd_cleanup,
|
||||
"edit": cmd_edit,
|
||||
|
||||
@@ -47,8 +47,8 @@ class ResolvedEnv:
|
||||
resolve_env; the backend forwards by-name. `literals` carry their
|
||||
values verbatim and are serialized by the backend."""
|
||||
|
||||
forwarded: list[str] = field(default_factory=list)
|
||||
literals: dict[str, str] = field(default_factory=dict)
|
||||
forwarded: list[str] = field(default_factory=list[str])
|
||||
literals: dict[str, str] = field(default_factory=dict[str, str])
|
||||
|
||||
|
||||
def env_entry_kind(raw: str) -> str:
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"include": [
|
||||
"cli.py",
|
||||
"claude_bottle",
|
||||
"tests"
|
||||
],
|
||||
"exclude": [
|
||||
"**/__pycache__",
|
||||
"**/.venv",
|
||||
"**/venv"
|
||||
],
|
||||
"pythonVersion": "3.11",
|
||||
"typeCheckingMode": "strict",
|
||||
"reportMissingTypeStubs": "none"
|
||||
}
|
||||
+2
-2
@@ -8,7 +8,7 @@ from __future__ import annotations
|
||||
import json
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from typing import Any, Callable
|
||||
|
||||
from claude_bottle.manifest import Manifest
|
||||
|
||||
@@ -77,7 +77,7 @@ def fixture_with_ssh() -> Manifest:
|
||||
return Manifest.from_json_obj(fixture_with_ssh_dict())
|
||||
|
||||
|
||||
def write_fixture(fn) -> Path:
|
||||
def write_fixture(fn: Callable[[], dict[str, Any]]) -> Path:
|
||||
"""Write fixture JSON to a temp file; return the path. Caller must rm.
|
||||
Accepts a function returning either a dict (JSON shape) or a Manifest;
|
||||
only the dict form is supported here since we need to serialize."""
|
||||
|
||||
@@ -5,12 +5,13 @@ the legacy 'runtime' field must fail, regardless of value, rather than
|
||||
silently ignoring."""
|
||||
|
||||
import unittest
|
||||
from typing import Any
|
||||
|
||||
from claude_bottle.log import Die
|
||||
from claude_bottle.manifest import Bottle, Manifest
|
||||
|
||||
|
||||
def _manifest_with_runtime(value: object) -> dict:
|
||||
def _manifest_with_runtime(value: object) -> dict[str, Any]:
|
||||
return {
|
||||
"bottles": {"dev": {"runtime": value}},
|
||||
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
||||
|
||||
@@ -10,6 +10,7 @@ import os
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from typing import Any, cast
|
||||
|
||||
from claude_bottle.backend.docker.pipelock import DockerPipelockProxy
|
||||
from claude_bottle.manifest import Manifest
|
||||
@@ -27,19 +28,22 @@ class TestBuildConfig(unittest.TestCase):
|
||||
{"include_defaults": True, "scan_env": True}, cfg["dlp"]
|
||||
)
|
||||
# Baked defaults always present.
|
||||
self.assertIn("api.anthropic.com", cfg["api_allowlist"])
|
||||
self.assertIn("raw.githubusercontent.com", cfg["api_allowlist"])
|
||||
self.assertIn("api.anthropic.com", cast(list[str], cfg["api_allowlist"]))
|
||||
self.assertIn("raw.githubusercontent.com", cast(list[str], cfg["api_allowlist"]))
|
||||
# No SSH entries → no trusted_domains, no ssrf.
|
||||
self.assertNotIn("trusted_domains", cfg)
|
||||
self.assertNotIn("ssrf", cfg)
|
||||
|
||||
def test_ssh_shape(self):
|
||||
cfg = pipelock_build_config(fixture_with_ssh().bottles["dev"])
|
||||
self.assertIn("github.com", cfg["trusted_domains"])
|
||||
self.assertNotIn("100.78.141.42", cfg["trusted_domains"])
|
||||
self.assertIn("100.78.141.42/32", cfg["ssrf"]["ip_allowlist"])
|
||||
self.assertIn("github.com", cast(list[str], cfg["trusted_domains"]))
|
||||
self.assertNotIn("100.78.141.42", cast(list[str], cfg["trusted_domains"]))
|
||||
self.assertIn(
|
||||
"100.78.141.42/32",
|
||||
cast(dict[str, Any], cfg["ssrf"])["ip_allowlist"],
|
||||
)
|
||||
# Strict mode: IPv4 host is also in the api_allowlist union.
|
||||
self.assertIn("100.78.141.42", cfg["api_allowlist"])
|
||||
self.assertIn("100.78.141.42", cast(list[str], cfg["api_allowlist"]))
|
||||
|
||||
|
||||
class TestRenderAndWrite(unittest.TestCase):
|
||||
|
||||
Reference in New Issue
Block a user