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.
- horus_singularity-0.0.1/.github/workflows/python-ci.yaml +62 -0
- horus_singularity-0.0.1/.github/workflows/release.yaml +35 -0
- horus_singularity-0.0.1/.gitignore +54 -0
- horus_singularity-0.0.1/.mit.tmpl +4 -0
- horus_singularity-0.0.1/.pre-commit-config.yaml +43 -0
- horus_singularity-0.0.1/LICENSE +21 -0
- horus_singularity-0.0.1/Makefile +101 -0
- horus_singularity-0.0.1/PKG-INFO +9 -0
- horus_singularity-0.0.1/README.md +123 -0
- horus_singularity-0.0.1/babel.cfg +2 -0
- horus_singularity-0.0.1/pyproject.toml +114 -0
- horus_singularity-0.0.1/src/horus_singularity/__init__.py +6 -0
- horus_singularity-0.0.1/src/horus_singularity/executor/__init__.py +6 -0
- horus_singularity-0.0.1/src/horus_singularity/executor/singularity.py +190 -0
- horus_singularity-0.0.1/src/horus_singularity/i18n.py +22 -0
- horus_singularity-0.0.1/src/horus_singularity/locale/en/LC_MESSAGES/horus_singularity.mo +0 -0
- horus_singularity-0.0.1/src/horus_singularity/locale/en/LC_MESSAGES/horus_singularity.po +47 -0
- horus_singularity-0.0.1/src/horus_singularity/locale/messages.pot +45 -0
- horus_singularity-0.0.1/tests/__init__.py +6 -0
- horus_singularity-0.0.1/tests/conftest.py +55 -0
- horus_singularity-0.0.1/tests/unit/__init__.py +6 -0
- horus_singularity-0.0.1/tests/unit/test_singularity_executor.py +180 -0
- horus_singularity-0.0.1/uv.lock +758 -0
|
@@ -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,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
|
+
[](https://www.python.org/downloads/)
|
|
4
|
+
[](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,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"
|