horus-singularity 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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Temple Compute
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -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_singularity --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_singularity/locale
14
+ MESSAGES_POT = $(LOCALE_DIR)/messages.pot
15
+ DOMAIN = horus_singularity
16
+ SOURCE_DIR = src/horus_singularity
17
+
18
+ # Variables used for babel metadata
19
+ PROJECT_NAME = horus_singularity
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,9 @@
1
+ Metadata-Version: 2.4
2
+ Name: horus_singularity
3
+ Version: 0.0.1
4
+ Summary: A Horus runtime plugin that runs task commands inside Singularity/Apptainer containers.
5
+ License-File: LICENSE
6
+ Classifier: License :: OSI Approved :: MIT License
7
+ Classifier: Programming Language :: Python :: 3
8
+ Requires-Python: >=3.13
9
+ Requires-Dist: horus-runtime>=0.2.0
@@ -0,0 +1,123 @@
1
+ # horus-singularity
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 Singularity/Apptainer containers.
7
+
8
+ ---
9
+
10
+ ## Overview
11
+
12
+ **horus-singularity** contributes a `SingularityExecutor` 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 Singularity (or Apptainer) container, giving you the same shell semantics (pipes, globbing, `&&`, …) as the local shell executor.
15
+
16
+ It is the HPC counterpart of [horus-docker](https://github.com/temple-compute/horus-docker): most clusters forbid the Docker daemon but ship Singularity/Apptainer, so this plugin is a drop-in swap for the same workflows.
17
+
18
+ ---
19
+
20
+ ## Repository structure
21
+
22
+ ```
23
+ src/
24
+ └── horus_singularity/
25
+ ├── __init__.py
26
+ ├── i18n.py # plugin-scoped gettext wrapper
27
+ ├── locale/
28
+ │ └── messages.pot # translatable strings template
29
+ └── executor/
30
+ ├── __init__.py
31
+ └── singularity.py # SingularityExecutor implementation
32
+ tests/
33
+ ├── __init__.py
34
+ ├── conftest.py # shared fixtures (registry, HorusContext)
35
+ └── unit/
36
+ ├── __init__.py
37
+ └── test_singularity_executor.py
38
+ babel.cfg
39
+ Makefile
40
+ pyproject.toml
41
+ ```
42
+
43
+ ---
44
+
45
+ ## SingularityExecutor
46
+
47
+ Registered under `kind = "singularity"`. Only accepted when the task's runtime is a `CommandRuntime`.
48
+
49
+ ### Fields
50
+
51
+ | Field | Type | Default | Description |
52
+ |---|---|---|---|
53
+ | `image` | `str` | required | Path to the `.sif` image on the target (e.g. `/opt/images/boltz.sif`) |
54
+ | `exe` | `str` | `"singularity"` | Container CLI to invoke — `singularity` or `apptainer` |
55
+ | `binds` | `dict[str, str]` | `{}` | Bind mounts as `host_path → container_path` |
56
+ | `nv` | `bool` | `False` | Add `--nv` to expose the NVIDIA driver stack inside the container |
57
+ | `env` | `dict[str, str]` | `{}` | Environment variables set with `--env NAME=value` |
58
+ | `working_dir` | `str \| None` | `None` | Working directory inside the container (`--pwd`) |
59
+ | `extra_args` | `list[str]` | `[]` | Extra flags passed verbatim before the image path |
60
+
61
+ ### Behaviour
62
+
63
+ 1. The task's `CommandRuntime` prepares the shell command via `setup_runtime()`.
64
+ 2. The executor builds:
65
+ ```
66
+ <exe> exec [--nv] [--env K=V]… [--bind host:container]… [--pwd dir] [extra_args…] <image> /bin/sh -c <command>
67
+ ```
68
+ Every interpolated value is passed through `shlex.quote`.
69
+ 3. The parent directory of every task input/output artifact is auto-bound at the same path inside the container (`host_dir → host_dir`). Explicit `binds` entries win on conflict.
70
+ 4. The command runs on the task target via `target.run_command(...)`; stdout is logged at `INFO`, stderr at `WARNING`.
71
+ 5. A non-zero exit code raises `TaskExecutionError`.
72
+ 6. `cancel_execution()` kills the still-running process (Singularity has no daemon-side container name to `stop`), and is a safe no-op otherwise.
73
+
74
+ ---
75
+
76
+ ## Development
77
+
78
+ ### Requirements
79
+
80
+ - Python ≥ 3.13
81
+ - `singularity` or `apptainer` available on the target
82
+ - `horus-runtime` ≥ 0.2.0
83
+
84
+ ### Setup
85
+
86
+ ```bash
87
+ # Install dependencies (creates .venv automatically)
88
+ uv sync
89
+
90
+ # Install pre-commit hooks
91
+ uv run pre-commit install
92
+ ```
93
+
94
+ ### Common commands
95
+
96
+ | Command | Description |
97
+ |---|---|
98
+ | `make test` | Run the full test suite with coverage |
99
+ | `make lint` | ruff + mypy |
100
+ | `make format` | Auto-fix with ruff |
101
+ | `make type-check` | mypy only |
102
+ | `make babel-extract` | Update `messages.pot` |
103
+ | `make babel-add LANG=es` | Add a new language |
104
+ | `make babel-check` | Verify all strings are translated |
105
+ | `make clean` | Remove build artefacts and caches |
106
+
107
+ ---
108
+
109
+ ## Internationalization (i18n)
110
+
111
+ Each plugin maintains its **own** gettext domain and locale directory, independent of the runtime's translations.
112
+
113
+ `src/horus_singularity/i18n.py` wraps Python's `gettext` module, looking for compiled `.mo` files in `src/horus_singularity/locale/<lang>/LC_MESSAGES/horus_singularity.mo`. If no catalog exists for the detected locale, it falls back to the original string.
114
+
115
+ 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.
116
+
117
+ > Full i18n workflow and plural-form reference: [docs.templecompute.com](https://docs.templecompute.com/docs/sdk/i18n).
118
+
119
+ ---
120
+
121
+ ## License
122
+
123
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,2 @@
1
+ [python: **.py]
2
+ encoding = utf-8
@@ -0,0 +1,114 @@
1
+ [project]
2
+ name = "horus_singularity"
3
+ version = "0.0.1"
4
+ description = "A Horus runtime plugin that runs task commands inside Singularity/Apptainer containers."
5
+ requires-python = ">=3.13"
6
+ classifiers = [
7
+ "License :: OSI Approved :: MIT License",
8
+ "Programming Language :: Python :: 3",
9
+ ]
10
+ dependencies = ["horus-runtime>=0.2.0"]
11
+
12
+ # ================
13
+ # Plugin entry-points
14
+ # ================
15
+ [project.entry-points."horus.executor"]
16
+ singularity = "horus_singularity.executor.singularity"
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_singularity"]
39
+
40
+ [tool.coverage.report]
41
+ omit = ["tests/*"]
42
+
43
+ [tool.hatch.build]
44
+ artifacts = ["src/horus_singularity/locale/**/LC_MESSAGES/*.mo"]
45
+
46
+ [tool.hatch.build.targets.wheel]
47
+ packages = ["src/horus_singularity"]
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_singularity
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_singularity
3
+ # Copyright (c) 2026 Temple Compute
4
+ #
5
+ # MIT License
6
+ #
@@ -0,0 +1,6 @@
1
+ #
2
+ # horus_singularity
3
+ # Copyright (c) 2026 Temple Compute
4
+ #
5
+ # MIT License
6
+ #