axio-tools-docker 0.2.3__tar.gz → 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (22) hide show
  1. axio_tools_docker-0.3.2/.gitignore +12 -0
  2. {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/Makefile +1 -1
  3. {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/PKG-INFO +1 -1
  4. {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/pyproject.toml +1 -1
  5. {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/src/axio_tools_docker/plugin.py +1 -1
  6. axio_tools_docker-0.3.2/src/axio_tools_docker/settings.py +5 -0
  7. axio_tools_docker-0.3.2/src/axio_tools_docker/tui/__init__.py +15 -0
  8. axio_tools_docker-0.3.2/src/axio_tools_docker/tui/docker.py +152 -0
  9. axio_tools_docker-0.2.3/src/axio_tools_docker/settings.py +0 -170
  10. {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/.github/workflows/publish.yml +0 -0
  11. {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/.github/workflows/tests.yml +0 -0
  12. {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/LICENSE +0 -0
  13. {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/README.md +0 -0
  14. {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/src/axio_tools_docker/__init__.py +0 -0
  15. {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/src/axio_tools_docker/config.py +0 -0
  16. {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/src/axio_tools_docker/handler.py +0 -0
  17. {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/src/axio_tools_docker/manager.py +0 -0
  18. {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/tests/test_config.py +0 -0
  19. {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/tests/test_handler.py +0 -0
  20. {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/tests/test_manager.py +0 -0
  21. {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/tests/test_plugin.py +0 -0
  22. {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/uv.lock +0 -0
@@ -0,0 +1,12 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ *.pyo
5
+ .mypy_cache/
6
+ .ruff_cache/
7
+ .pytest_cache/
8
+ *.egg-info/
9
+ dist/
10
+ build/
11
+ _build/
12
+ .DS_Store
@@ -8,7 +8,7 @@ lint:
8
8
  uv run ruff check src/ tests/
9
9
 
10
10
  typecheck:
11
- uv run mypy src/
11
+ uv run mypy src/ tests/
12
12
 
13
13
  test:
14
14
  uv run pytest tests/ -v
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: axio-tools-docker
3
- Version: 0.2.3
3
+ Version: 0.3.2
4
4
  Summary: Docker sandbox tools for Axio
5
5
  Project-URL: Homepage, https://github.com/axio-agent/axio-tools-docker
6
6
  Project-URL: Repository, https://github.com/axio-agent/axio-tools-docker
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "axio-tools-docker"
3
- version = "0.2.3"
3
+ version = "0.3.2"
4
4
  description = "Docker sandbox tools for Axio"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -79,7 +79,7 @@ class DockerPlugin:
79
79
  return self._tools
80
80
 
81
81
  def settings_screen(self) -> Any:
82
- from .settings import DockerSettingsScreen
82
+ from .tui import DockerSettingsScreen
83
83
 
84
84
  return DockerSettingsScreen(self._manager, config=self._config, global_config=self._global_config)
85
85
 
@@ -0,0 +1,5 @@
1
+ """Retained for backwards compatibility — import from tui instead."""
2
+
3
+ from axio_tools_docker.tui.docker import DockerSettingsScreen
4
+
5
+ __all__ = ["DockerSettingsScreen"]
@@ -0,0 +1,15 @@
1
+ """TUI screens for axio-tools-docker (requires textual)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ try:
6
+ from .docker import DockerSettingsScreen
7
+
8
+ __all__ = ["DockerSettingsScreen"]
9
+ except ImportError as _e:
10
+ import warnings
11
+
12
+ warnings.warn(
13
+ f"axio-tools-docker TUI screens are unavailable: {_e}. Install textual: pip install axio[tui]",
14
+ stacklevel=1,
15
+ )
@@ -0,0 +1,152 @@
1
+ """Settings screen for Docker sandbox configuration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from textual.app import ComposeResult
8
+ from textual.binding import Binding
9
+ from textual.containers import Container, Horizontal
10
+ from textual.screen import ModalScreen
11
+ from textual.widgets import Button, Checkbox, Input, Static
12
+
13
+ from axio_tools_docker.config import SandboxConfig
14
+ from axio_tools_docker.manager import SandboxManager
15
+
16
+
17
+ class DockerSettingsScreen(ModalScreen[None]):
18
+ """Settings screen for Docker sandbox configuration."""
19
+
20
+ BINDINGS = [Binding("escape", "cancel", "Cancel")]
21
+ CSS = """
22
+ DockerSettingsScreen { align: center middle; }
23
+ #docker-settings {
24
+ width: 70;
25
+ height: auto;
26
+ max-height: 90%;
27
+ border: heavy $accent;
28
+ background: $panel;
29
+ padding: 1 2;
30
+ }
31
+ #docker-settings Input { margin-bottom: 1; }
32
+ #docker-settings Checkbox { margin-bottom: 1; }
33
+ .docker-buttons { height: auto; margin-top: 1; }
34
+ .docker-buttons Button { margin: 0 1; }
35
+ .docker-container-actions { height: auto; margin-top: 1; margin-bottom: 1; }
36
+ .docker-container-actions Button { margin: 0 1; }
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ manager: SandboxManager,
42
+ config: Any = None,
43
+ global_config: Any = None,
44
+ ) -> None:
45
+ super().__init__()
46
+ self._manager = manager
47
+ self._config = config
48
+ self._global_config = global_config
49
+
50
+ def compose(self) -> ComposeResult:
51
+ cfg = self._manager.config
52
+ docker_ok = self._manager.docker_available()
53
+ status = "[green]Docker available[/]" if docker_ok else "[red]Docker not found[/]"
54
+ running = self._manager.container_running
55
+
56
+ with Container(id="docker-settings"):
57
+ yield Static("[bold]Docker Sandbox Settings[/]")
58
+ yield Static(f"Status: {status}", id="docker-status")
59
+ yield Static(
60
+ "Container: [green]running[/]" if running else "Container: [dim]stopped[/]",
61
+ id="docker-container-status",
62
+ )
63
+ with Horizontal(classes="docker-container-actions"):
64
+ yield Button("Prepare", id="btn-prepare", variant="success", disabled=running)
65
+ yield Button("Stop", id="btn-stop", variant="error", disabled=not running)
66
+ yield Button("Recreate", id="btn-recreate", variant="warning", disabled=not running)
67
+ yield Static("Image:")
68
+ yield Input(value=cfg.image, placeholder="e.g. python:latest", id="docker-image")
69
+ yield Static("Memory limit:")
70
+ yield Input(value=cfg.memory, placeholder="e.g. 256m", id="docker-memory")
71
+ yield Static("CPU limit:")
72
+ yield Input(value=cfg.cpus, placeholder="e.g. 1.0", id="docker-cpus")
73
+ yield Checkbox("Allow network access", cfg.network, id="docker-network")
74
+ with Horizontal(classes="docker-buttons"):
75
+ yield Button("Save", id="btn-save", variant="primary")
76
+ yield Button("Cancel", id="btn-cancel")
77
+
78
+ def on_mount(self) -> None:
79
+ self.query_one("#docker-image", Input).focus()
80
+
81
+ def _update_container_status(self) -> None:
82
+ running = self._manager.container_running
83
+ status_widget = self.query_one("#docker-container-status", Static)
84
+ status_widget.update(
85
+ "Container: [green]running[/]" if running else "Container: [dim]stopped[/]",
86
+ )
87
+ self.query_one("#btn-prepare", Button).disabled = running
88
+ self.query_one("#btn-stop", Button).disabled = not running
89
+ self.query_one("#btn-recreate", Button).disabled = not running
90
+
91
+ def on_button_pressed(self, event: Button.Pressed) -> None:
92
+ if event.button.id == "btn-cancel":
93
+ self.dismiss(None)
94
+ elif event.button.id == "btn-save":
95
+ self._save()
96
+ elif event.button.id == "btn-prepare":
97
+ self.app.run_worker(self._do_prepare())
98
+ elif event.button.id == "btn-stop":
99
+ self.app.run_worker(self._do_stop())
100
+ elif event.button.id == "btn-recreate":
101
+ self.app.run_worker(self._do_recreate())
102
+
103
+ async def _do_prepare(self) -> None:
104
+ self.notify("Preparing container...")
105
+ try:
106
+ await self._manager._ensure_container()
107
+ self._update_container_status()
108
+ self.notify("Container ready")
109
+ except RuntimeError as exc:
110
+ self.notify(str(exc), severity="error")
111
+
112
+ async def _do_stop(self) -> None:
113
+ await self._manager.close()
114
+ self._update_container_status()
115
+ self.notify("Container stopped")
116
+
117
+ async def _do_recreate(self) -> None:
118
+ self.notify("Recreating container...")
119
+ try:
120
+ await self._manager.recreate()
121
+ self._update_container_status()
122
+ self.notify("Container recreated")
123
+ except RuntimeError as exc:
124
+ self.notify(str(exc), severity="error")
125
+ self._update_container_status()
126
+
127
+ def _save(self) -> None:
128
+ image = self.query_one("#docker-image", Input).value.strip()
129
+ memory = self.query_one("#docker-memory", Input).value.strip()
130
+ cpus = self.query_one("#docker-cpus", Input).value.strip()
131
+ network = self.query_one("#docker-network", Checkbox).value
132
+
133
+ cfg = SandboxConfig(
134
+ image=image or "python:latest",
135
+ memory=memory or "256m",
136
+ cpus=cpus or "1.0",
137
+ network=network,
138
+ )
139
+ self._manager.config = cfg
140
+ self.app.run_worker(self._persist(cfg))
141
+ self.dismiss(None)
142
+
143
+ async def _persist(self, cfg: SandboxConfig) -> None:
144
+ db = self._config or self._global_config
145
+ if db is None:
146
+ return
147
+ await db.delete_prefix("docker.")
148
+ for key, value in cfg.to_dict().items():
149
+ await db.set(f"docker.{key}", value)
150
+
151
+ def action_cancel(self) -> None:
152
+ self.dismiss(None)
@@ -1,170 +0,0 @@
1
- """TUI settings screen for Docker sandbox configuration."""
2
-
3
- from __future__ import annotations
4
-
5
- from typing import TYPE_CHECKING, Any
6
-
7
- try:
8
- from textual.app import ComposeResult
9
- from textual.binding import Binding
10
- from textual.containers import Container, Horizontal
11
- from textual.screen import ModalScreen
12
- from textual.widgets import Button, Checkbox, Input, Static
13
-
14
- _HAS_TEXTUAL = True
15
- except ImportError:
16
- _HAS_TEXTUAL = False
17
-
18
- if TYPE_CHECKING:
19
- from .config import SandboxConfig
20
- from .manager import SandboxManager
21
-
22
-
23
- if _HAS_TEXTUAL:
24
-
25
- class DockerSettingsScreen(ModalScreen[None]):
26
- """Settings screen for Docker sandbox configuration."""
27
-
28
- BINDINGS = [Binding("escape", "cancel", "Cancel")]
29
- CSS = """
30
- DockerSettingsScreen { align: center middle; }
31
- #docker-settings {
32
- width: 70;
33
- height: auto;
34
- max-height: 90%;
35
- border: heavy $accent;
36
- background: $panel;
37
- padding: 1 2;
38
- }
39
- #docker-settings Input { margin-bottom: 1; }
40
- #docker-settings Checkbox { margin-bottom: 1; }
41
- .docker-buttons { height: auto; margin-top: 1; }
42
- .docker-buttons Button { margin: 0 1; }
43
- .docker-container-actions { height: auto; margin-top: 1; margin-bottom: 1; }
44
- .docker-container-actions Button { margin: 0 1; }
45
- """
46
-
47
- def __init__(
48
- self,
49
- manager: SandboxManager,
50
- config: Any = None,
51
- global_config: Any = None,
52
- ) -> None:
53
- super().__init__()
54
- self._manager = manager
55
- self._config = config
56
- self._global_config = global_config
57
-
58
- def compose(self) -> ComposeResult:
59
- cfg = self._manager.config
60
- docker_ok = self._manager.docker_available()
61
- status = "[green]Docker available[/]" if docker_ok else "[red]Docker not found[/]"
62
- running = self._manager.container_running
63
-
64
- with Container(id="docker-settings"):
65
- yield Static("[bold]Docker Sandbox Settings[/]")
66
- yield Static(f"Status: {status}", id="docker-status")
67
- yield Static(
68
- "Container: [green]running[/]" if running else "Container: [dim]stopped[/]",
69
- id="docker-container-status",
70
- )
71
- with Horizontal(classes="docker-container-actions"):
72
- yield Button("Prepare", id="btn-prepare", variant="success", disabled=running)
73
- yield Button("Stop", id="btn-stop", variant="error", disabled=not running)
74
- yield Button("Recreate", id="btn-recreate", variant="warning", disabled=not running)
75
- yield Static("Image:")
76
- yield Input(value=cfg.image, placeholder="e.g. python:latest", id="docker-image")
77
- yield Static("Memory limit:")
78
- yield Input(value=cfg.memory, placeholder="e.g. 256m", id="docker-memory")
79
- yield Static("CPU limit:")
80
- yield Input(value=cfg.cpus, placeholder="e.g. 1.0", id="docker-cpus")
81
- yield Checkbox("Allow network access", cfg.network, id="docker-network")
82
- with Horizontal(classes="docker-buttons"):
83
- yield Button("Save", id="btn-save", variant="primary")
84
- yield Button("Cancel", id="btn-cancel")
85
-
86
- def on_mount(self) -> None:
87
- self.query_one("#docker-image", Input).focus()
88
-
89
- def _update_container_status(self) -> None:
90
- running = self._manager.container_running
91
- status_widget = self.query_one("#docker-container-status", Static)
92
- status_widget.update(
93
- "Container: [green]running[/]" if running else "Container: [dim]stopped[/]",
94
- )
95
- self.query_one("#btn-prepare", Button).disabled = running
96
- self.query_one("#btn-stop", Button).disabled = not running
97
- self.query_one("#btn-recreate", Button).disabled = not running
98
-
99
- def on_button_pressed(self, event: Button.Pressed) -> None:
100
- if event.button.id == "btn-cancel":
101
- self.dismiss(None)
102
- elif event.button.id == "btn-save":
103
- self._save()
104
- elif event.button.id == "btn-prepare":
105
- self.app.run_worker(self._do_prepare())
106
- elif event.button.id == "btn-stop":
107
- self.app.run_worker(self._do_stop())
108
- elif event.button.id == "btn-recreate":
109
- self.app.run_worker(self._do_recreate())
110
-
111
- async def _do_prepare(self) -> None:
112
- self.notify("Preparing container...")
113
- try:
114
- await self._manager._ensure_container()
115
- self._update_container_status()
116
- self.notify("Container ready")
117
- except RuntimeError as exc:
118
- self.notify(str(exc), severity="error")
119
-
120
- async def _do_stop(self) -> None:
121
- await self._manager.close()
122
- self._update_container_status()
123
- self.notify("Container stopped")
124
-
125
- async def _do_recreate(self) -> None:
126
- self.notify("Recreating container...")
127
- try:
128
- await self._manager.recreate()
129
- self._update_container_status()
130
- self.notify("Container recreated")
131
- except RuntimeError as exc:
132
- self.notify(str(exc), severity="error")
133
- self._update_container_status()
134
-
135
- def _save(self) -> None:
136
- from .config import SandboxConfig
137
-
138
- image = self.query_one("#docker-image", Input).value.strip()
139
- memory = self.query_one("#docker-memory", Input).value.strip()
140
- cpus = self.query_one("#docker-cpus", Input).value.strip()
141
- network = self.query_one("#docker-network", Checkbox).value
142
-
143
- cfg = SandboxConfig(
144
- image=image or "python:latest",
145
- memory=memory or "256m",
146
- cpus=cpus or "1.0",
147
- network=network,
148
- )
149
- self._manager.config = cfg
150
- self.app.run_worker(self._persist(cfg))
151
- self.dismiss(None)
152
-
153
- async def _persist(self, cfg: SandboxConfig) -> None:
154
- db = self._config or self._global_config
155
- if db is None:
156
- return
157
- await db.delete_prefix("docker.")
158
- for key, value in cfg.to_dict().items():
159
- await db.set(f"docker.{key}", value)
160
-
161
- def action_cancel(self) -> None:
162
- self.dismiss(None)
163
-
164
- else:
165
-
166
- class DockerSettingsScreen: # type: ignore[no-redef]
167
- """Stub when textual is not installed."""
168
-
169
- def __init__(self, *args: Any, **kwargs: Any) -> None:
170
- raise ImportError("textual is required for Docker settings screen")