backend/__init__ eagerly imports all backends; DRY the docker subprocess helpers #359

Open
opened 2026-07-13 16:33:43 -04:00 by didericis-claude · 0 comments
Collaborator

Two related cleanups surfaced in review of #358.

1. backend/__init__.py eagerly imports all three backends

bot_bottle/backend/__init__.py imports DockerBottleBackend, FirecrackerBottleBackend, and MacosContainerBottleBackend at module top (bottom of the file, # noqa: E402). So importing any backend submodule — even the lean backend.docker.util (aka docker_mod, only imports ...log) — runs the package __init__ and drags in the whole backend layer.

Measured: from bot_bottle.backend.docker import util pulls in ~76 bot_bottle modules, including the entire firecracker and macos backends plus the manifest/egress/git-gate/supervise/workspace framework.

Fix: lazy-load the backends — import them inside the get_bottle_backend factory (and/or expose them via module-level __getattr__ / PEP 562) instead of at import time. Then importing a backend util is cheap, and from bot_bottle.backend import DockerBottleBackend still works but only loads on access.

This is why the orchestrator's run_docker was placed in a lean top-level bot_bottle/docker_cmd.py rather than reusing docker_mod — routing the orchestrator through docker_mod today would make every orchestrator import load all three backends.

2. DRY the docker subprocess helpers onto docker_cmd

Once (1) makes backend utils cheap to import, thread backend/docker/util.py's ad-hoc subprocess.run(["docker", ...]) calls (and _silent_run, force_remove_container, container_exists, ...) through the shared bot_bottle/docker_cmd.run_docker, so there's one docker-subprocess primitive shared by the backend and the orchestrator.

Ordering: (1) then (2). Neither blocks the orchestrator stack (#352/#356/#357/#358).

Two related cleanups surfaced in review of #358. ## 1. `backend/__init__.py` eagerly imports all three backends `bot_bottle/backend/__init__.py` imports `DockerBottleBackend`, `FirecrackerBottleBackend`, and `MacosContainerBottleBackend` at module top (bottom of the file, `# noqa: E402`). So importing **any** backend submodule — even the lean `backend.docker.util` (aka `docker_mod`, only imports `...log`) — runs the package `__init__` and drags in the whole backend layer. Measured: `from bot_bottle.backend.docker import util` pulls in **~76 `bot_bottle` modules**, including the entire firecracker and macos backends plus the manifest/egress/git-gate/supervise/workspace framework. **Fix:** lazy-load the backends — import them inside the `get_bottle_backend` factory (and/or expose them via module-level `__getattr__` / PEP 562) instead of at import time. Then importing a backend util is cheap, and `from bot_bottle.backend import DockerBottleBackend` still works but only loads on access. This is why the orchestrator's `run_docker` was placed in a lean top-level `bot_bottle/docker_cmd.py` rather than reusing `docker_mod` — routing the orchestrator through `docker_mod` today would make every orchestrator import load all three backends. ## 2. DRY the docker subprocess helpers onto `docker_cmd` Once (1) makes backend utils cheap to import, thread `backend/docker/util.py`'s ad-hoc `subprocess.run(["docker", ...])` calls (and `_silent_run`, `force_remove_container`, `container_exists`, ...) through the shared `bot_bottle/docker_cmd.run_docker`, so there's one docker-subprocess primitive shared by the backend and the orchestrator. Ordering: (1) then (2). Neither blocks the orchestrator stack (#352/#356/#357/#358).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: didericis/bot-bottle#359