axio-tools-docker 0.9.0__tar.gz → 0.9.1__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.
- {axio_tools_docker-0.9.0 → axio_tools_docker-0.9.1}/PKG-INFO +5 -4
- {axio_tools_docker-0.9.0 → axio_tools_docker-0.9.1}/README.md +4 -3
- {axio_tools_docker-0.9.0 → axio_tools_docker-0.9.1}/pyproject.toml +6 -2
- {axio_tools_docker-0.9.0 → axio_tools_docker-0.9.1}/src/axio_tools_docker/sandbox.py +8 -5
- {axio_tools_docker-0.9.0 → axio_tools_docker-0.9.1}/tests/test_integration.py +2 -2
- {axio_tools_docker-0.9.0 → axio_tools_docker-0.9.1}/tests/test_sandbox.py +11 -0
- {axio_tools_docker-0.9.0 → axio_tools_docker-0.9.1}/.github/workflows/publish.yml +0 -0
- {axio_tools_docker-0.9.0 → axio_tools_docker-0.9.1}/.github/workflows/tests.yml +0 -0
- {axio_tools_docker-0.9.0 → axio_tools_docker-0.9.1}/.gitignore +0 -0
- {axio_tools_docker-0.9.0 → axio_tools_docker-0.9.1}/LICENSE +0 -0
- {axio_tools_docker-0.9.0 → axio_tools_docker-0.9.1}/Makefile +0 -0
- {axio_tools_docker-0.9.0 → axio_tools_docker-0.9.1}/conftest.py +0 -0
- {axio_tools_docker-0.9.0 → axio_tools_docker-0.9.1}/src/axio_tools_docker/__init__.py +0 -0
- {axio_tools_docker-0.9.0 → axio_tools_docker-0.9.1}/src/axio_tools_docker/py.typed +0 -0
- {axio_tools_docker-0.9.0 → axio_tools_docker-0.9.1}/tests/__init__.py +0 -0
- {axio_tools_docker-0.9.0 → axio_tools_docker-0.9.1}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: axio-tools-docker
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.1
|
|
4
4
|
Summary: Docker sandbox tools for Axio
|
|
5
5
|
Project-URL: Homepage, https://github.com/mosquito/axio-agent
|
|
6
6
|
Project-URL: Repository, https://github.com/mosquito/axio-agent
|
|
@@ -115,9 +115,10 @@ The running container's ID is available as `sandbox.container_id` inside the
|
|
|
115
115
|
|
|
116
116
|
## Named containers and reuse
|
|
117
117
|
|
|
118
|
-
Pass `name=` to give the container a fixed name. If a
|
|
119
|
-
|
|
120
|
-
one
|
|
118
|
+
Pass `name=` to give the container a fixed name. If a container with that name
|
|
119
|
+
already exists, the sandbox starts it if needed and attaches instead of creating
|
|
120
|
+
a new one. Attached containers are never removed on exit - regardless of
|
|
121
|
+
`remove`:
|
|
121
122
|
|
|
122
123
|
```python
|
|
123
124
|
import asyncio
|
|
@@ -101,9 +101,10 @@ The running container's ID is available as `sandbox.container_id` inside the
|
|
|
101
101
|
|
|
102
102
|
## Named containers and reuse
|
|
103
103
|
|
|
104
|
-
Pass `name=` to give the container a fixed name. If a
|
|
105
|
-
|
|
106
|
-
one
|
|
104
|
+
Pass `name=` to give the container a fixed name. If a container with that name
|
|
105
|
+
already exists, the sandbox starts it if needed and attaches instead of creating
|
|
106
|
+
a new one. Attached containers are never removed on exit - regardless of
|
|
107
|
+
`remove`:
|
|
107
108
|
|
|
108
109
|
```python
|
|
109
110
|
import asyncio
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "axio-tools-docker"
|
|
3
|
-
version = "0.9.
|
|
3
|
+
version = "0.9.1"
|
|
4
4
|
description = "Docker sandbox tools for Axio"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -24,6 +24,7 @@ testpaths = ["tests", "README.md"]
|
|
|
24
24
|
|
|
25
25
|
[tool.ruff]
|
|
26
26
|
line-length = 119
|
|
27
|
+
output-format = "concise"
|
|
27
28
|
target-version = "py312"
|
|
28
29
|
|
|
29
30
|
[tool.ruff.lint]
|
|
@@ -33,6 +34,10 @@ select = ["E", "F", "I", "UP"]
|
|
|
33
34
|
strict = true
|
|
34
35
|
python_version = "3.12"
|
|
35
36
|
|
|
37
|
+
[[tool.mypy.overrides]]
|
|
38
|
+
module = "aiodocker.*"
|
|
39
|
+
follow_imports = "skip"
|
|
40
|
+
|
|
36
41
|
[dependency-groups]
|
|
37
42
|
dev = [
|
|
38
43
|
"pytest>=8",
|
|
@@ -43,4 +48,3 @@ dev = [
|
|
|
43
48
|
"markdown-pytest>=0.6.0",
|
|
44
49
|
"aiodocker>=0.26",
|
|
45
50
|
]
|
|
46
|
-
|
|
@@ -12,7 +12,7 @@ import stat as stat_module
|
|
|
12
12
|
import tarfile
|
|
13
13
|
import uuid
|
|
14
14
|
from datetime import datetime
|
|
15
|
-
from typing import Any
|
|
15
|
+
from typing import Any, cast
|
|
16
16
|
|
|
17
17
|
import aiodocker
|
|
18
18
|
from axio.tool import CONTEXT, Tool
|
|
@@ -324,6 +324,9 @@ class DockerSandbox:
|
|
|
324
324
|
if self.name:
|
|
325
325
|
try:
|
|
326
326
|
self._container = await self._client.containers.get(self.name)
|
|
327
|
+
info = await self._container.show()
|
|
328
|
+
if not info.get("State", {}).get("Running", False):
|
|
329
|
+
await self._container.start()
|
|
327
330
|
self._attached = True
|
|
328
331
|
logger.info("Attached to existing container (name=%s)", self.name)
|
|
329
332
|
except aiodocker.exceptions.DockerError:
|
|
@@ -420,7 +423,7 @@ class DockerSandbox:
|
|
|
420
423
|
if self.named_volumes and self.volumes_remove and not was_attached:
|
|
421
424
|
for vol_name in self.named_volumes.values():
|
|
422
425
|
with contextlib.suppress(Exception):
|
|
423
|
-
vol = await self._client.volumes.get(vol_name)
|
|
426
|
+
vol = await self._client.volumes.get(vol_name)
|
|
424
427
|
await vol.delete()
|
|
425
428
|
logger.info("Removed %d named volume(s)", len(self.named_volumes))
|
|
426
429
|
await self._client.close()
|
|
@@ -439,7 +442,7 @@ class DockerSandbox:
|
|
|
439
442
|
"""Return the ID of the running container. Only valid inside `async with`."""
|
|
440
443
|
if self._container is None:
|
|
441
444
|
raise RuntimeError("DockerSandbox must be used as an async context manager")
|
|
442
|
-
return self._container.id
|
|
445
|
+
return str(self._container.id)
|
|
443
446
|
|
|
444
447
|
async def _ensure_image(self) -> None:
|
|
445
448
|
"""Pull the image if it is not present locally."""
|
|
@@ -514,7 +517,7 @@ class DockerSandbox:
|
|
|
514
517
|
tar.addfile(info, io.BytesIO(data))
|
|
515
518
|
parent = os.path.dirname(path) or "/"
|
|
516
519
|
await self.exec(f"mkdir -p {shlex.quote(parent)}")
|
|
517
|
-
await self._container.put_archive(
|
|
520
|
+
await self._container.put_archive(
|
|
518
521
|
path=parent,
|
|
519
522
|
data=buf.getvalue(),
|
|
520
523
|
)
|
|
@@ -523,7 +526,7 @@ class DockerSandbox:
|
|
|
523
526
|
async def get_archive(self, path: str) -> tarfile.TarFile:
|
|
524
527
|
"""Fetch a path from the container as a TarFile object."""
|
|
525
528
|
assert self._container is not None
|
|
526
|
-
return await self._container.get_archive(path=path)
|
|
529
|
+
return cast(tarfile.TarFile, await self._container.get_archive(path=path))
|
|
527
530
|
|
|
528
531
|
async def read_file_bytes(self, path: str) -> bytes:
|
|
529
532
|
"""Read a file from inside the container and return raw bytes."""
|
|
@@ -360,7 +360,7 @@ async def test_named_volume_persists_across_containers(docker: str, image: str)
|
|
|
360
360
|
finally:
|
|
361
361
|
async with aiodocker.Docker(url=docker) as client:
|
|
362
362
|
with contextlib.suppress(Exception):
|
|
363
|
-
vol = await client.volumes.get(vol_name)
|
|
363
|
+
vol = await client.volumes.get(vol_name)
|
|
364
364
|
await vol.delete()
|
|
365
365
|
|
|
366
366
|
|
|
@@ -378,4 +378,4 @@ async def test_volumes_remove_cleans_up(docker: str, image: str) -> None:
|
|
|
378
378
|
|
|
379
379
|
async with aiodocker.Docker(url=docker) as client:
|
|
380
380
|
with pytest.raises(aiodocker.exceptions.DockerError):
|
|
381
|
-
await client.volumes.get(vol_name)
|
|
381
|
+
await client.volumes.get(vol_name)
|
|
@@ -63,6 +63,7 @@ def mock_docker_factory(
|
|
|
63
63
|
mock_container.start = AsyncMock()
|
|
64
64
|
mock_container.delete = AsyncMock()
|
|
65
65
|
mock_container.exec = AsyncMock(return_value=mock_exec)
|
|
66
|
+
mock_container.show = AsyncMock(return_value={"State": {"Running": True}})
|
|
66
67
|
mock_container.put_archive = AsyncMock()
|
|
67
68
|
mock_container.get_archive = AsyncMock(
|
|
68
69
|
return_value=archive_content if archive_content is not None else make_tar_file("file.txt", b"content")
|
|
@@ -166,6 +167,16 @@ async def test_named_existing_container_attaches() -> None:
|
|
|
166
167
|
container.start.assert_not_awaited()
|
|
167
168
|
|
|
168
169
|
|
|
170
|
+
async def test_named_existing_stopped_container_starts_before_attach() -> None:
|
|
171
|
+
cls, client, container = mock_docker_factory()
|
|
172
|
+
client.containers.get = AsyncMock(return_value=container)
|
|
173
|
+
container.show = AsyncMock(return_value={"State": {"Running": False}})
|
|
174
|
+
with patch("axio_tools_docker.sandbox.aiodocker.Docker", cls):
|
|
175
|
+
async with DockerSandbox(name="my-sandbox"):
|
|
176
|
+
pass
|
|
177
|
+
container.start.assert_awaited_once()
|
|
178
|
+
|
|
179
|
+
|
|
169
180
|
async def test_named_existing_container_not_deleted() -> None:
|
|
170
181
|
"""Attached container is never removed even with remove=True."""
|
|
171
182
|
cls, client, container = mock_docker_factory()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|