refactor(bottles): BottlePlatform becomes ABC; DockerBottlePlatform in docker.py
test / run tests/run_tests.py (pull_request) Successful in 18s
test / run tests/run_tests.py (pull_request) Successful in 18s
Mirror the BottlePlan -> DockerBottlePlan hierarchy at the platform layer. BottlePlatform is now an abstract base with abstract `prepare` and `launch` methods; DockerBottlePlatform lives alongside the rest of the Docker code in bottles/docker.py and supplies the concrete impls. The registry in bottles/__init__.py now holds an instance of each concrete platform class. Future per-platform state (region, api token, cleanup primitives) has a natural home on the subclass rather than being stitched onto a dataclass struct. No behavior change. Tests pass; dry-run output unchanged.
This commit is contained in:
@@ -24,7 +24,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 Callable, Protocol
|
from typing import Protocol
|
||||||
|
|
||||||
from ..log import die
|
from ..log import die
|
||||||
from ..manifest import Manifest
|
from ..manifest import Manifest
|
||||||
@@ -58,12 +58,6 @@ class BottlePlan(ABC):
|
|||||||
"""Render the y/N preflight summary to stderr."""
|
"""Render the y/N preflight summary to stderr."""
|
||||||
|
|
||||||
|
|
||||||
# Import concrete platform factories AFTER the base types are defined,
|
|
||||||
# so each platform module can pull BottleSpec / BottlePlan via
|
|
||||||
# `from . import ...` without hitting a partially-initialized module.
|
|
||||||
from .docker import launch_docker_bottle, prepare_docker_bottle # noqa: E402
|
|
||||||
|
|
||||||
|
|
||||||
class Bottle(Protocol):
|
class Bottle(Protocol):
|
||||||
"""Handle to a running bottle. Yielded by a platform's launch step.
|
"""Handle to a running bottle. Yielded by a platform's launch step.
|
||||||
|
|
||||||
@@ -79,21 +73,31 @@ class Bottle(Protocol):
|
|||||||
def close(self) -> None: ...
|
def close(self) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
class BottlePlatform(ABC):
|
||||||
class BottlePlatform:
|
"""Abstract base for selectable bottle platforms. Concrete subclasses
|
||||||
"""Bundles a platform's two-phase factory under one selectable name."""
|
(e.g. DockerBottlePlatform) own their own prepare/launch impls.
|
||||||
|
Symmetric with the BottlePlan → DockerBottlePlan hierarchy."""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
prepare: Callable[..., BottlePlan]
|
|
||||||
launch: Callable[..., AbstractContextManager[Bottle]]
|
@abstractmethod
|
||||||
|
def prepare(self, spec: BottleSpec, *, stage_dir: Path) -> BottlePlan:
|
||||||
|
"""Resolve names, validate host-side prerequisites, write
|
||||||
|
scratch files. No remote/runtime resources created yet."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def launch(self, plan: BottlePlan) -> AbstractContextManager[Bottle]:
|
||||||
|
"""Build/run the bottle and yield a handle; tear down on exit."""
|
||||||
|
|
||||||
|
|
||||||
|
# Import concrete platform classes AFTER the base types are defined, so
|
||||||
|
# each platform module can pull BottleSpec / BottlePlan / BottlePlatform
|
||||||
|
# via `from . import ...` without hitting a partially-initialized module.
|
||||||
|
from .docker import DockerBottlePlatform # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
_PLATFORMS: dict[str, BottlePlatform] = {
|
_PLATFORMS: dict[str, BottlePlatform] = {
|
||||||
"docker": BottlePlatform(
|
"docker": DockerBottlePlatform(),
|
||||||
name="docker",
|
|
||||||
prepare=prepare_docker_bottle,
|
|
||||||
launch=launch_docker_bottle,
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from __future__ import annotations
|
|||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from contextlib import contextmanager
|
from contextlib import AbstractContextManager, contextmanager
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
@@ -34,7 +34,7 @@ from .. import skills as skills_mod
|
|||||||
from .. import ssh as ssh_mod
|
from .. import ssh as ssh_mod
|
||||||
from ..env_resolve import env_resolve
|
from ..env_resolve import env_resolve
|
||||||
from ..log import die, info
|
from ..log import die, info
|
||||||
from . import BottlePlan, BottleSpec
|
from . import BottlePlan, BottlePlatform, BottleSpec
|
||||||
|
|
||||||
|
|
||||||
# --- Runtime detection -----------------------------------------------------
|
# --- Runtime detection -----------------------------------------------------
|
||||||
@@ -434,3 +434,24 @@ def _provision_container(plan: DockerBottlePlan, container: str) -> str | None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
return in_container_prompt_path if agent.prompt else None
|
return in_container_prompt_path if agent.prompt else None
|
||||||
|
|
||||||
|
|
||||||
|
# --- Platform --------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
class DockerBottlePlatform(BottlePlatform):
|
||||||
|
"""Docker platform implementation. Selected by CLAUDE_BOTTLE_PLATFORM
|
||||||
|
(default). The methods delegate to the module-level prepare/launch
|
||||||
|
functions so the platform class itself stays a thin dispatch layer."""
|
||||||
|
|
||||||
|
name = "docker"
|
||||||
|
|
||||||
|
def prepare(self, spec: BottleSpec, *, stage_dir: Path) -> BottlePlan:
|
||||||
|
return prepare_docker_bottle(spec, stage_dir=stage_dir)
|
||||||
|
|
||||||
|
def launch(self, plan: BottlePlan) -> AbstractContextManager[_DockerBottle]:
|
||||||
|
assert isinstance(plan, DockerBottlePlan), (
|
||||||
|
f"DockerBottlePlatform.launch expects DockerBottlePlan, "
|
||||||
|
f"got {type(plan).__name__}"
|
||||||
|
)
|
||||||
|
return launch_docker_bottle(plan)
|
||||||
|
|||||||
Reference in New Issue
Block a user