horus-docker 0.0.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.
@@ -0,0 +1,62 @@
1
+ name: Python CI
2
+
3
+ on:
4
+ pull_request:
5
+ branches: [main]
6
+
7
+ jobs:
8
+ lint:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v6
12
+
13
+ - name: Set up Python
14
+ uses: actions/setup-python@v6
15
+ with:
16
+ python-version: "3.13"
17
+ cache: "pip"
18
+
19
+ - name: Install uv
20
+ uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
21
+
22
+ - name: Sync project dependencies
23
+ run: uv sync --group dev
24
+
25
+ - name: Lint with Ruff and mypy
26
+ run: uv run make lint
27
+
28
+ - name: Check translations
29
+ run: uv run make babel-check
30
+
31
+ test:
32
+ runs-on: ubuntu-latest
33
+ strategy:
34
+ fail-fast: false
35
+ matrix:
36
+ python-version: ["3.13", "3.14"]
37
+
38
+ steps:
39
+ - uses: actions/checkout@v6
40
+
41
+ - name: Set up Python ${{ matrix.python-version }}
42
+ uses: actions/setup-python@v6
43
+ with:
44
+ python-version: ${{ matrix.python-version }}
45
+ cache: "pip"
46
+
47
+ - name: Install uv
48
+ uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
49
+
50
+ - name: Sync project dependencies
51
+ run: uv sync --group dev
52
+
53
+ - name: Run tests with coverage
54
+ run: uv run make test
55
+
56
+ - name: Upload coverage report
57
+ uses: actions/upload-artifact@v6
58
+ if: matrix.python-version == '3.13'
59
+ with:
60
+ name: coverage-html-report
61
+ path: htmlcov/
62
+ retention-days: 5
@@ -0,0 +1,35 @@
1
+ name: Release
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ release:
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ id-token: write
12
+
13
+ steps:
14
+ - uses: actions/checkout@v6
15
+
16
+ - name: Set up Python
17
+ uses: actions/setup-python@v6
18
+ with:
19
+ python-version: "3.13"
20
+ cache: "pip"
21
+
22
+ - name: Install uv
23
+ uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
24
+
25
+ - name: Sync project dependencies
26
+ run: uv sync --group dev
27
+
28
+ - name: Verify and compile translations
29
+ run: uv run make babel-check
30
+
31
+ - name: Build package
32
+ run: uv run uv build
33
+
34
+ - name: Upload to PyPI
35
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,54 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+ *.ipynb
23
+
24
+ # Virtual environments
25
+ venv/
26
+ .venv/
27
+ ENV/
28
+ env/
29
+
30
+ # IDEs
31
+ .vscode/
32
+ .idea/
33
+ *.swp
34
+ *.swo
35
+
36
+ # Testing
37
+ .pytest_cache/
38
+ .coverage
39
+ htmlcov/
40
+ .tox/
41
+
42
+ # OS
43
+ .DS_Store
44
+ Thumbs.db
45
+
46
+ *.xml
47
+
48
+ # i18n
49
+ *.mo
50
+
51
+ # logs
52
+ logs/
53
+
54
+ no-commit/
@@ -0,0 +1,4 @@
1
+ ${projectname}
2
+ Copyright (c) ${years} ${owner}
3
+
4
+ MIT License
@@ -0,0 +1,43 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v6.0.0
4
+ hooks:
5
+ - id: trailing-whitespace
6
+ - id: end-of-file-fixer
7
+ - id: check-yaml
8
+ - id: check-added-large-files
9
+
10
+ - repo: local
11
+ hooks:
12
+ - id: ruff-lint
13
+ name: ruff lint
14
+ entry: ruff check --fix
15
+ language: system
16
+ types: [ python ]
17
+ require_serial: true
18
+
19
+ - id: ruff-format
20
+ name: ruff format
21
+ entry: ruff format
22
+ language: system
23
+ types: [ python ]
24
+
25
+ - id: mypy
26
+ name: mypy
27
+ entry: mypy
28
+ language: system
29
+ types: [ python ]
30
+ require_serial: true
31
+
32
+ # If the commit includes changes in translation files,
33
+ # check that all translations are complete.
34
+ - id: babel-check
35
+ name: babel-check
36
+ entry: make babel-check
37
+ language: system
38
+
39
+ - id: add-license-headers
40
+ name: add-license-headers
41
+ entry: make add-license-headers
42
+ language: system
43
+ pass_filenames: false
@@ -0,0 +1,101 @@
1
+ # Makefile for Horus Runtime plugin development
2
+
3
+ # Command definitions (used in CI and locally)
4
+ PYTEST_CMD = pytest --cov=src/horus_docker --cov-report=xml --cov-report=html --cov-report=term --junitxml=test-results.xml
5
+ RUFF_LINT_CMD = ruff check src/ tests/
6
+ RUFF_FORMAT_CHECK_CMD = ruff format --check --diff .
7
+ RUFF_FIX_CMD = ruff check --fix src/ tests/
8
+ RUFF_FORMAT_CMD = ruff format .
9
+ MYPY_CMD = mypy src/ tests/
10
+
11
+ # i18n settings (babel)
12
+ BABEL_CFG = babel.cfg
13
+ LOCALE_DIR = src/horus_docker/locale
14
+ MESSAGES_POT = $(LOCALE_DIR)/messages.pot
15
+ DOMAIN = horus_docker
16
+ SOURCE_DIR = src/horus_docker
17
+
18
+ # Variables used for babel metadata
19
+ PROJECT_NAME = horus_docker
20
+ ORGANIZATION = Temple Compute
21
+ LICENSE_TMPL = .mit.tmpl
22
+
23
+ .PHONY: test lint format type-check clean help ruff-check ruff-format-check mypy-check babel-update babel-check babel-add babel-extract add-license-headers
24
+
25
+ help:
26
+ @echo "Available commands:"
27
+ @echo " test Run all tests with coverage (same as CI)"
28
+ @echo " lint Run all linting tools (same as CI)"
29
+ @echo " ruff-check Check code with ruff linter (used by CI)"
30
+ @echo " ruff-format-check Check code formatting with ruff (used by CI)"
31
+ @echo " mypy-check Check types with mypy (used by CI)"
32
+ @echo " format Format code with ruff"
33
+ @echo " type-check Run type checking"
34
+ @echo " babel-update Update Babel translations"
35
+ @echo " babel-check Check Babel translations (used by CI)"
36
+ @echo " babel-add Add a new language (usage: make babel-add LANG=es)"
37
+ @echo " babel-extract Extract translatable strings to messages.pot"
38
+ @echo " clean Remove cache files"
39
+
40
+ test:
41
+ # Set PYTHONPATH to current directory to ensure tests can find other test modules
42
+ PYTHONPATH=. $(PYTEST_CMD)
43
+
44
+ # Individual check commands
45
+ ruff-check:
46
+ $(RUFF_LINT_CMD)
47
+
48
+ ruff-format-check:
49
+ $(RUFF_FORMAT_CHECK_CMD)
50
+
51
+ mypy-check:
52
+ $(MYPY_CMD)
53
+
54
+ lint:
55
+ $(RUFF_LINT_CMD)
56
+ $(RUFF_FORMAT_CHECK_CMD)
57
+ $(MYPY_CMD)
58
+
59
+ format:
60
+ $(RUFF_FIX_CMD)
61
+ $(RUFF_FORMAT_CMD)
62
+
63
+ type-check:
64
+ $(MYPY_CMD)
65
+
66
+ add-license-headers:
67
+ licenseheaders -t $(LICENSE_TMPL) -cy -o '$(ORGANIZATION)' -n $(PROJECT_NAME) -d src
68
+ licenseheaders -t $(LICENSE_TMPL) -cy -o '$(ORGANIZATION)' -n $(PROJECT_NAME) -d tests
69
+
70
+ clean:
71
+ find . -type d -name "__pycache__" -delete
72
+ find . -type f -name "*.pyc" -delete
73
+ rm -rf .coverage htmlcov/ .pytest_cache/
74
+ rm -rf *.egg-info/ build/ dist/
75
+
76
+ babel-extract:
77
+ pybabel extract -F $(BABEL_CFG) \
78
+ --project=$(PROJECT_NAME) \
79
+ --copyright-holder="$(ORGANIZATION)" \
80
+ -o $(MESSAGES_POT) $(SOURCE_DIR)
81
+
82
+ babel-update:
83
+ pybabel update -i $(MESSAGES_POT) -d $(LOCALE_DIR) -D $(DOMAIN) --no-fuzzy-matching
84
+
85
+ babel-refresh: babel-extract babel-update
86
+
87
+ babel-check:
88
+ @echo "Checking for missing or fuzzy translations..."
89
+ @for file in $(shell find $(LOCALE_DIR) -name "*.po"); do \
90
+ RESULT=$$(msgfmt --statistics -c -o /dev/null $$file 2>&1); \
91
+ echo "$$RESULT"; \
92
+ if echo "$$RESULT" | grep -E "untranslated|fuzzy" > /dev/null; then \
93
+ echo "ERROR: $$file has missing or fuzzy strings!"; \
94
+ exit 1; \
95
+ fi; \
96
+ done
97
+ @echo "Success: All strings are translated."
98
+ pybabel compile -d $(LOCALE_DIR) -D $(DOMAIN) --statistics
99
+
100
+ babel-add:
101
+ pybabel init -i $(MESSAGES_POT) -d $(LOCALE_DIR) -l $(LANG) -D $(DOMAIN)
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: horus_docker
3
+ Version: 0.0.1
4
+ Summary: A custom plugin for the Horus runtime, providing additional functionality and integrations.
5
+ Classifier: License :: OSI Approved :: MIT License
6
+ Classifier: Programming Language :: Python :: 3
7
+ Requires-Python: >=3.13
8
+ Requires-Dist: horus-runtime>=0.1.3
@@ -0,0 +1,118 @@
1
+ # horus-docker
2
+
3
+ [![Python 3.13+](https://img.shields.io/badge/python-3.13%2B-blue.svg)](https://www.python.org/downloads/)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
5
+
6
+ A [horus-runtime](https://github.com/temple-compute/horus-runtime) plugin that runs tasks inside Docker containers.
7
+
8
+ ---
9
+
10
+ ## Overview
11
+
12
+ **horus-docker** contributes a `DockerExecutor` to the `horus.executor` entry point group. Once installed, any `HorusContext` that calls `HorusContext.boot()` will automatically discover and register the executor — no manual wiring required.
13
+
14
+ The executor accepts a `CommandRuntime`-produced shell command and runs it via `/bin/sh -c` inside a fresh Docker container, giving you the same shell semantics (pipes, globbing, `&&`, …) as the local shell executor.
15
+
16
+ ---
17
+
18
+ ## Repository structure
19
+
20
+ ```
21
+ src/
22
+ └── horus_docker/
23
+ ├── __init__.py
24
+ ├── i18n.py # plugin-scoped gettext wrapper
25
+ ├── locale/
26
+ │ └── messages.pot # translatable strings template
27
+ └── executor/
28
+ ├── __init__.py
29
+ └── docker.py # DockerExecutor implementation
30
+ tests/
31
+ ├── __init__.py
32
+ ├── conftest.py # shared fixtures (registry, HorusContext)
33
+ └── unit/
34
+ ├── __init__.py
35
+ └── test_docker_executor.py
36
+ babel.cfg
37
+ Makefile
38
+ pyproject.toml
39
+ ```
40
+
41
+ ---
42
+
43
+ ## DockerExecutor
44
+
45
+ Registered under `kind = "docker_executor"`. Only accepted when the task's runtime is a `CommandRuntime`.
46
+
47
+ ### Fields
48
+
49
+ | Field | Type | Default | Description |
50
+ |---|---|---|---|
51
+ | `image` | `str` | required | Docker image to run (e.g. `python:3.13-slim`) |
52
+ | `env` | `dict[str, str]` | `{}` | Environment variables passed into the container |
53
+ | `volumes` | `dict[str, str]` | `{}` | Bind mounts as `host_path → container_path` (read-write) |
54
+ | `ports` | `dict[str, str]` | `{}` | Port mappings as `host_port → container_port` |
55
+ | `working_dir` | `str \| None` | `None` | Working directory inside the container |
56
+ | `network` | `str \| None` | `None` | Docker network to attach the container to |
57
+ | `entrypoint` | `str \| list[str] \| None` | `None` | Override the image's `ENTRYPOINT` |
58
+ | `user` | `str \| None` | `None` | User (`name`/`uid`, optionally `uid:gid`) to run as |
59
+ | `auto_remove` | `bool` | `True` | Remove the container after execution finishes |
60
+
61
+ ### Behaviour
62
+
63
+ 1. The task's `CommandRuntime` prepares the shell command via `setup_runtime()`.
64
+ 2. The command is wrapped as `["/bin/sh", "-c", <command>]` and passed to `docker-py`'s `containers.run()`.
65
+ 3. The container runs to completion (blocking, dispatched to a thread with `asyncio.to_thread`).
66
+ 4. Container logs are captured and emitted at `DEBUG` level.
67
+ 5. If the Docker daemon raises a `DockerException`, or the container exits with a non-zero status code, a `TaskExecutionError` is raised.
68
+
69
+ ---
70
+
71
+ ## Development
72
+
73
+ ### Requirements
74
+
75
+ - Python ≥ 3.13
76
+ - Docker daemon accessible from the host
77
+ - `horus-runtime` ≥ 0.1.1 (install from source or PyPI)
78
+
79
+ ### Setup
80
+
81
+ ```bash
82
+ # Install dependencies (creates .venv automatically)
83
+ uv sync
84
+
85
+ # Install pre-commit hooks
86
+ uv run pre-commit install
87
+ ```
88
+
89
+ ### Common commands
90
+
91
+ | Command | Description |
92
+ |---|---|
93
+ | `make test` | Run the full test suite with coverage |
94
+ | `make lint` | ruff + mypy |
95
+ | `make format` | Auto-fix with ruff |
96
+ | `make type-check` | mypy only |
97
+ | `make babel-extract` | Update `messages.pot` |
98
+ | `make babel-add LANG=es` | Add a new language |
99
+ | `make babel-check` | Verify all strings are translated |
100
+ | `make clean` | Remove build artefacts and caches |
101
+
102
+ ---
103
+
104
+ ## Internationalization (i18n)
105
+
106
+ Each plugin maintains its **own** gettext domain and locale directory, independent of the runtime's translations.
107
+
108
+ `src/horus_docker/i18n.py` wraps Python's `gettext` module, looking for compiled `.mo` files in `src/horus_docker/locale/<lang>/LC_MESSAGES/horus_docker.mo`. If no catalog exists for the detected locale, it falls back to the original string.
109
+
110
+ Import the wrapper as `_` (required by Babel's extractor) in any module with user-visible strings. Use `make babel-extract` → edit `.po` → `make babel-check` to update translations. The pre-commit hook prevents committing incomplete catalogs.
111
+
112
+ > Full i18n workflow and plural-form reference: [docs.templecompute.com](https://docs.templecompute.com/docs/sdk/i18n).
113
+
114
+ ---
115
+
116
+ ## License
117
+
118
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,2 @@
1
+ [python: **.py]
2
+ encoding = utf-8
@@ -0,0 +1,114 @@
1
+ [project]
2
+ name = "horus_docker"
3
+ version = "0.0.1"
4
+ description = "A custom plugin for the Horus runtime, providing additional functionality and integrations."
5
+ requires-python = ">=3.13"
6
+ classifiers = [
7
+ "License :: OSI Approved :: MIT License",
8
+ "Programming Language :: Python :: 3",
9
+ ]
10
+ dependencies = ["horus-runtime>=0.1.3"]
11
+
12
+ # ================
13
+ # Plugin entry-points
14
+ # ================
15
+ [project.entry-points."horus.executor"]
16
+ docker = "horus_docker.executor.docker"
17
+
18
+ # ================
19
+
20
+ [dependency-groups]
21
+ dev = [
22
+ "babel~=2.0",
23
+ "licenseheaders~=0.8",
24
+ "mypy~=1.19",
25
+ "pre_commit~=4.0",
26
+ "pytest~=9.0",
27
+ "pytest-asyncio~=1.3.0",
28
+ "pytest-cov~=7.0",
29
+ "ruff~=0.15",
30
+ "types-PyYAML~=6.0",
31
+ ]
32
+
33
+ [build-system]
34
+ requires = ["hatchling"]
35
+ build-backend = "hatchling.build"
36
+
37
+ [tool.coverage.run]
38
+ source = ["horus_docker"]
39
+
40
+ [tool.coverage.report]
41
+ omit = ["tests/*"]
42
+
43
+ [tool.hatch.build]
44
+ artifacts = ["src/horus_docker/locale/**/LC_MESSAGES/*.mo"]
45
+
46
+ [tool.hatch.build.targets.wheel]
47
+ packages = ["src/horus_docker"]
48
+
49
+ [tool.mypy]
50
+ python_version = "3.13"
51
+ strict = true
52
+ exclude = ["build", "dist"]
53
+ plugins = ["pydantic.mypy"]
54
+
55
+ [tool.pydantic-mypy]
56
+ init_typed = true
57
+ init_forbid_extra = true
58
+ warn_required_dynamic_aliases = true
59
+
60
+ [tool.pytest.ini_options]
61
+ addopts = """
62
+ --verbose
63
+ --cov=src/horus_docker
64
+ --cov-report=html
65
+ --cov-report=term-missing
66
+ --cov-fail-under=90
67
+ """
68
+ minversion = "7.0"
69
+ python_classes = ["Test*"]
70
+ python_files = ["test_*.py", "*_test.py"]
71
+ python_functions = ["test_*"]
72
+ testpaths = ["tests"]
73
+ asyncio_mode = "auto"
74
+
75
+ [tool.ruff]
76
+ line-length = 79
77
+ target-version = "py313"
78
+ exclude = [".git", ".venv", "build", "dist", "__pycache__"]
79
+
80
+ [tool.ruff.lint]
81
+ select = [
82
+ "D", # pydocstyle
83
+ "E", # pycodestyle errors
84
+ "W", # pycodestyle warnings
85
+ "F", # pyflakes
86
+ "I", # isort
87
+ "N", # pep8-naming
88
+ "UP", # pyupgrade
89
+ "B", # flake8-bugbear
90
+ "PL", # pylint
91
+ "RUF", # ruff-specific
92
+ "ARG", # ruff-arglint
93
+ "SLF", # ruff-slf
94
+ ]
95
+ extend-select = ["T201"] # Prevent use of print()
96
+
97
+ ignore = [
98
+ "E203", # Whitespace before ':', conflicts with formatter
99
+ "D104", # Missing docstring in __init__ (often redundant)
100
+ "D205", # 1 blank line required between summary line and description
101
+ "D212", # Multi-line docstring summary should start at the first line
102
+ "D200", # One-line docstring should fit on one line
103
+ "PLR2004" # Magic numbers are ok
104
+ ]
105
+
106
+ [tool.ruff.lint.pydocstyle]
107
+ convention = "google"
108
+
109
+ [tool.ruff.lint.per-file-ignores]
110
+ "tests/**/*.py" = ["SLF001"]
111
+
112
+ [tool.ruff.format]
113
+ quote-style = "double"
114
+ indent-style = "space"
@@ -0,0 +1,6 @@
1
+ #
2
+ # horus_docker
3
+ # Copyright (c) 2026 Temple Compute
4
+ #
5
+ # MIT License
6
+ #
@@ -0,0 +1,6 @@
1
+ #
2
+ # horus_docker
3
+ # Copyright (c) 2026 Temple Compute
4
+ #
5
+ # MIT License
6
+ #