ray-dispatcher 0.1.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.
- ray_dispatcher-0.1.2/.github/dependabot.yml +20 -0
- ray_dispatcher-0.1.2/.github/scripts/release_version.py +48 -0
- ray_dispatcher-0.1.2/.github/workflows/ci.yml +80 -0
- ray_dispatcher-0.1.2/.github/workflows/release.yml +52 -0
- ray_dispatcher-0.1.2/.gitignore +13 -0
- ray_dispatcher-0.1.2/LICENSE +21 -0
- ray_dispatcher-0.1.2/PKG-INFO +158 -0
- ray_dispatcher-0.1.2/README.md +141 -0
- ray_dispatcher-0.1.2/docs/superpowers/plans/2026-06-28-ray-dispatcher-phase-1-foundations.md +1301 -0
- ray_dispatcher-0.1.2/docs/superpowers/plans/2026-06-28-ray-dispatcher-phase-2-ssh-transport.md +1108 -0
- ray_dispatcher-0.1.2/docs/superpowers/plans/2026-06-28-ray-dispatcher-phase-3a-digests-locking.md +826 -0
- ray_dispatcher-0.1.2/docs/superpowers/plans/2026-06-28-ray-dispatcher-phase-3b-provisioning.md +1646 -0
- ray_dispatcher-0.1.2/docs/superpowers/plans/2026-06-29-ray-dispatcher-phase-4a-lease-pool.md +882 -0
- ray_dispatcher-0.1.2/docs/superpowers/plans/2026-06-29-ray-dispatcher-phase-4b-lease-service.md +766 -0
- ray_dispatcher-0.1.2/docs/superpowers/plans/2026-06-29-ray-dispatcher-phase-5a-results.md +572 -0
- ray_dispatcher-0.1.2/docs/superpowers/plans/2026-06-29-ray-dispatcher-phase-5b-attempt-driver.md +741 -0
- ray_dispatcher-0.1.2/docs/superpowers/plans/2026-06-29-ray-dispatcher-phase-6a-job-orchestration.md +474 -0
- ray_dispatcher-0.1.2/docs/superpowers/plans/2026-06-29-ray-dispatcher-phase-6b-ray-backend-lifecycle.md +706 -0
- ray_dispatcher-0.1.2/docs/superpowers/plans/2026-06-29-ray-dispatcher-phase-6c-submit-resolve-heartbeat-timeout.md +815 -0
- ray_dispatcher-0.1.2/docs/superpowers/plans/2026-06-29-ray-dispatcher-phase-6d-dispatcher-lifecycle.md +761 -0
- ray_dispatcher-0.1.2/docs/superpowers/plans/2026-06-30-ray-dispatcher-phase-9-live-host-visibility.md +429 -0
- ray_dispatcher-0.1.2/docs/superpowers/plans/2026-10-11-audit-fixes.md +251 -0
- ray_dispatcher-0.1.2/docs/superpowers/plans/2026-10-11-python-namespace.md +66 -0
- ray_dispatcher-0.1.2/docs/superpowers/specs/2026-06-27-ray-dispatcher-design.md +742 -0
- ray_dispatcher-0.1.2/docs/superpowers/specs/2026-06-30-live-host-visibility-design.md +100 -0
- ray_dispatcher-0.1.2/docs/superpowers/specs/2026-07-06-ray-dispatcher-bug-fixes-design.md +24 -0
- ray_dispatcher-0.1.2/docs/superpowers/specs/2026-10-11-python-namespace-design.md +48 -0
- ray_dispatcher-0.1.2/examples/toy_batch/README.md +75 -0
- ray_dispatcher-0.1.2/examples/toy_batch/configs/run_01.json +1 -0
- ray_dispatcher-0.1.2/examples/toy_batch/configs/run_02.json +1 -0
- ray_dispatcher-0.1.2/examples/toy_batch/configs/run_03.json +1 -0
- ray_dispatcher-0.1.2/examples/toy_batch/configs/run_04.json +1 -0
- ray_dispatcher-0.1.2/examples/toy_batch/experiment/experiment.py +20 -0
- ray_dispatcher-0.1.2/examples/toy_batch/experiment/pyproject.toml +7 -0
- ray_dispatcher-0.1.2/examples/toy_batch/experiment/uv.lock +8 -0
- ray_dispatcher-0.1.2/examples/toy_batch/pyproject.toml +15 -0
- ray_dispatcher-0.1.2/examples/toy_batch/run_batch.py +168 -0
- ray_dispatcher-0.1.2/examples/toy_batch/tests/test_experiment.py +50 -0
- ray_dispatcher-0.1.2/examples/toy_batch/uv.lock +1024 -0
- ray_dispatcher-0.1.2/pyproject.toml +89 -0
- ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/__init__.py +71 -0
- ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/backends/__init__.py +1 -0
- ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/backends/base.py +44 -0
- ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/backends/ssh_ray.py +603 -0
- ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/digests.py +79 -0
- ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/dispatcher.py +187 -0
- ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/errors.py +60 -0
- ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/locking.py +162 -0
- ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/models.py +296 -0
- ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/paths.py +39 -0
- ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/provisioning.py +519 -0
- ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/remote_runner.py +117 -0
- ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/results.py +158 -0
- ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/scheduling.py +765 -0
- ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/ssh.py +344 -0
- ray_dispatcher-0.1.2/tests/conftest.py +9 -0
- ray_dispatcher-0.1.2/tests/e2e/__init__.py +0 -0
- ray_dispatcher-0.1.2/tests/e2e/conftest.py +140 -0
- ray_dispatcher-0.1.2/tests/e2e/test_concurrency.py +113 -0
- ray_dispatcher-0.1.2/tests/e2e/test_provisioning.py +173 -0
- ray_dispatcher-0.1.2/tests/e2e/test_smoke.py +96 -0
- ray_dispatcher-0.1.2/tests/e2e/test_teardown.py +48 -0
- ray_dispatcher-0.1.2/tests/e2e/test_timeout.py +80 -0
- ray_dispatcher-0.1.2/tests/integration/__init__.py +0 -0
- ray_dispatcher-0.1.2/tests/integration/test_attempt_task.py +108 -0
- ray_dispatcher-0.1.2/tests/integration/test_backend_exclusive_runtime.py +24 -0
- ray_dispatcher-0.1.2/tests/integration/test_backend_running_hosts.py +73 -0
- ray_dispatcher-0.1.2/tests/integration/test_backend_setup.py +169 -0
- ray_dispatcher-0.1.2/tests/integration/test_backend_submit.py +260 -0
- ray_dispatcher-0.1.2/tests/integration/test_backend_sweeper.py +77 -0
- ray_dispatcher-0.1.2/tests/integration/test_backend_teardown.py +136 -0
- ray_dispatcher-0.1.2/tests/integration/test_dispatcher_e2e.py +116 -0
- ray_dispatcher-0.1.2/tests/integration/test_host_lease_actor.py +40 -0
- ray_dispatcher-0.1.2/tests/unit/test_actor_lease_handle.py +29 -0
- ray_dispatcher-0.1.2/tests/unit/test_assemble_job_result.py +42 -0
- ray_dispatcher-0.1.2/tests/unit/test_attempt_task.py +54 -0
- ray_dispatcher-0.1.2/tests/unit/test_backend_base.py +20 -0
- ray_dispatcher-0.1.2/tests/unit/test_backend_config.py +24 -0
- ray_dispatcher-0.1.2/tests/unit/test_digests_env.py +48 -0
- ray_dispatcher-0.1.2/tests/unit/test_digests_runner.py +16 -0
- ray_dispatcher-0.1.2/tests/unit/test_digests_source.py +50 -0
- ray_dispatcher-0.1.2/tests/unit/test_dispatcher.py +352 -0
- ray_dispatcher-0.1.2/tests/unit/test_errors.py +38 -0
- ray_dispatcher-0.1.2/tests/unit/test_execute_attempt.py +116 -0
- ray_dispatcher-0.1.2/tests/unit/test_execute_attempt_cancel.py +87 -0
- ray_dispatcher-0.1.2/tests/unit/test_execute_attempt_timeout.py +193 -0
- ray_dispatcher-0.1.2/tests/unit/test_failed_attempt_json.py +14 -0
- ray_dispatcher-0.1.2/tests/unit/test_heartbeat_thread.py +48 -0
- ray_dispatcher-0.1.2/tests/unit/test_lease.py +42 -0
- ray_dispatcher-0.1.2/tests/unit/test_lease_pool_acquire.py +71 -0
- ray_dispatcher-0.1.2/tests/unit/test_lease_pool_current_hosts.py +47 -0
- ray_dispatcher-0.1.2/tests/unit/test_lease_pool_exclusions.py +47 -0
- ray_dispatcher-0.1.2/tests/unit/test_lease_pool_heartbeat.py +41 -0
- ray_dispatcher-0.1.2/tests/unit/test_lease_pool_heartbeat_late.py +43 -0
- ray_dispatcher-0.1.2/tests/unit/test_lease_pool_orphans.py +38 -0
- ray_dispatcher-0.1.2/tests/unit/test_lease_pool_quarantine.py +48 -0
- ray_dispatcher-0.1.2/tests/unit/test_lease_pool_quarantined_hosts.py +37 -0
- ray_dispatcher-0.1.2/tests/unit/test_lease_pool_release.py +44 -0
- ray_dispatcher-0.1.2/tests/unit/test_lease_pool_sweep.py +89 -0
- ray_dispatcher-0.1.2/tests/unit/test_lease_service_acquire.py +46 -0
- ray_dispatcher-0.1.2/tests/unit/test_lease_service_current_hosts.py +33 -0
- ray_dispatcher-0.1.2/tests/unit/test_lease_service_methods.py +65 -0
- ray_dispatcher-0.1.2/tests/unit/test_lease_service_no_capacity.py +53 -0
- ray_dispatcher-0.1.2/tests/unit/test_models_hosts.py +112 -0
- ray_dispatcher-0.1.2/tests/unit/test_models_jobs.py +114 -0
- ray_dispatcher-0.1.2/tests/unit/test_models_project.py +82 -0
- ray_dispatcher-0.1.2/tests/unit/test_models_results.py +85 -0
- ray_dispatcher-0.1.2/tests/unit/test_namespace.py +12 -0
- ray_dispatcher-0.1.2/tests/unit/test_paths.py +49 -0
- ray_dispatcher-0.1.2/tests/unit/test_provisioning_env.py +88 -0
- ray_dispatcher-0.1.2/tests/unit/test_provisioning_host.py +104 -0
- ray_dispatcher-0.1.2/tests/unit/test_provisioning_inventory.py +173 -0
- ray_dispatcher-0.1.2/tests/unit/test_provisioning_layout.py +78 -0
- ray_dispatcher-0.1.2/tests/unit/test_provisioning_preflight.py +75 -0
- ray_dispatcher-0.1.2/tests/unit/test_provisioning_python.py +63 -0
- ray_dispatcher-0.1.2/tests/unit/test_provisioning_runner.py +54 -0
- ray_dispatcher-0.1.2/tests/unit/test_provisioning_secrets.py +94 -0
- ray_dispatcher-0.1.2/tests/unit/test_provisioning_source.py +45 -0
- ray_dispatcher-0.1.2/tests/unit/test_provisioning_uv.py +106 -0
- ray_dispatcher-0.1.2/tests/unit/test_public_api.py +44 -0
- ray_dispatcher-0.1.2/tests/unit/test_publish_failure.py +64 -0
- ray_dispatcher-0.1.2/tests/unit/test_reconcile_host.py +56 -0
- ray_dispatcher-0.1.2/tests/unit/test_release_version.py +84 -0
- ray_dispatcher-0.1.2/tests/unit/test_remote_runner.py +83 -0
- ray_dispatcher-0.1.2/tests/unit/test_remote_runner_forward.py +113 -0
- ray_dispatcher-0.1.2/tests/unit/test_results_collect.py +134 -0
- ray_dispatcher-0.1.2/tests/unit/test_results_layout.py +34 -0
- ray_dispatcher-0.1.2/tests/unit/test_results_manifest.py +69 -0
- ray_dispatcher-0.1.2/tests/unit/test_results_publish.py +26 -0
- ray_dispatcher-0.1.2/tests/unit/test_rsync_argv.py +50 -0
- ray_dispatcher-0.1.2/tests/unit/test_run_job.py +329 -0
- ray_dispatcher-0.1.2/tests/unit/test_run_job_registers_pid.py +92 -0
- ray_dispatcher-0.1.2/tests/unit/test_run_paths.py +31 -0
- ray_dispatcher-0.1.2/tests/unit/test_runner_manifest.py +58 -0
- ray_dispatcher-0.1.2/tests/unit/test_running_hosts_batches.py +20 -0
- ray_dispatcher-0.1.2/tests/unit/test_secret_env_map.py +23 -0
- ray_dispatcher-0.1.2/tests/unit/test_session_lock_acquire.py +25 -0
- ray_dispatcher-0.1.2/tests/unit/test_session_lock_contention.py +170 -0
- ray_dispatcher-0.1.2/tests/unit/test_session_lock_heartbeat.py +24 -0
- ray_dispatcher-0.1.2/tests/unit/test_should_retry.py +38 -0
- ray_dispatcher-0.1.2/tests/unit/test_smoke.py +6 -0
- ray_dispatcher-0.1.2/tests/unit/test_ssh_config.py +40 -0
- ray_dispatcher-0.1.2/tests/unit/test_ssh_connection.py +50 -0
- ray_dispatcher-0.1.2/tests/unit/test_ssh_transport.py +118 -0
- ray_dispatcher-0.1.2/tests/unit/test_ssh_write_remote_file.py +39 -0
- ray_dispatcher-0.1.2/tests/unit/test_teardown_drain.py +103 -0
- ray_dispatcher-0.1.2/tests/unit/test_terminate_pgroup.py +70 -0
- ray_dispatcher-0.1.2/tests/unit/test_transport_fake.py +26 -0
- ray_dispatcher-0.1.2/tests/unit/test_vm_cleanup.py +53 -0
- ray_dispatcher-0.1.2/uv.lock +1970 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: uv
|
|
4
|
+
directories:
|
|
5
|
+
- /
|
|
6
|
+
- /examples/toy_batch
|
|
7
|
+
- /examples/toy_batch/experiment
|
|
8
|
+
schedule:
|
|
9
|
+
interval: weekly
|
|
10
|
+
open-pull-requests-limit: 5
|
|
11
|
+
groups:
|
|
12
|
+
python-dependencies:
|
|
13
|
+
patterns: ['*']
|
|
14
|
+
- package-ecosystem: github-actions
|
|
15
|
+
directory: /
|
|
16
|
+
schedule:
|
|
17
|
+
interval: weekly
|
|
18
|
+
groups:
|
|
19
|
+
github-actions:
|
|
20
|
+
patterns: ['*']
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Select releases from the version change across a push to main."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import subprocess
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
import tomllib
|
|
10
|
+
except ModuleNotFoundError:
|
|
11
|
+
import tomli as tomllib
|
|
12
|
+
|
|
13
|
+
from packaging.version import Version
|
|
14
|
+
|
|
15
|
+
from it.unimib.data.nanofaas.ray_dispatcher import __version__
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def should_publish(previous: str, current: str, package_version: str) -> bool:
|
|
19
|
+
if current != package_version:
|
|
20
|
+
raise ValueError("pyproject.toml version must match package __version__")
|
|
21
|
+
if Version(current) < Version(previous):
|
|
22
|
+
raise ValueError("Package version must not decrease")
|
|
23
|
+
return Version(current) > Version(previous)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def main() -> None:
|
|
27
|
+
current = tomllib.loads(Path("pyproject.toml").read_text())["project"]["version"]
|
|
28
|
+
previous = current
|
|
29
|
+
if os.environ["GITHUB_EVENT_NAME"] == "push":
|
|
30
|
+
before = os.environ["BEFORE_SHA"]
|
|
31
|
+
if not re.fullmatch(r"[0-9a-f]{40}", before) or before == "0" * 40:
|
|
32
|
+
raise ValueError("Expected the existing main branch's previous commit SHA")
|
|
33
|
+
# The ref is validated; git receives an argument list, not a shell command.
|
|
34
|
+
result = subprocess.run( # noqa: S603
|
|
35
|
+
["git", "show", f"{before}:pyproject.toml"], # noqa: S607
|
|
36
|
+
check=True,
|
|
37
|
+
capture_output=True,
|
|
38
|
+
text=True,
|
|
39
|
+
)
|
|
40
|
+
previous = tomllib.loads(result.stdout)["project"]["version"]
|
|
41
|
+
publish = should_publish(previous, current, __version__)
|
|
42
|
+
with Path(os.environ["GITHUB_OUTPUT"]).open("a") as output:
|
|
43
|
+
output.write(f"publish={str(publish).lower()}\nversion={Version(current)}\n")
|
|
44
|
+
print(f"Version {previous} -> {current}; publish={publish}")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
if __name__ == "__main__":
|
|
48
|
+
main()
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
name: checks
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
pull_request:
|
|
5
|
+
workflow_dispatch:
|
|
6
|
+
workflow_call:
|
|
7
|
+
schedule:
|
|
8
|
+
- cron: '23 6 * * 1'
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
jobs:
|
|
12
|
+
checks:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
timeout-minutes: 20
|
|
15
|
+
strategy:
|
|
16
|
+
fail-fast: false
|
|
17
|
+
matrix:
|
|
18
|
+
python: ['3.10', '3.13']
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v7.0.1
|
|
21
|
+
- uses: actions/setup-python@v7.0.0
|
|
22
|
+
with:
|
|
23
|
+
python-version: ${{ matrix.python }}
|
|
24
|
+
- uses: astral-sh/setup-uv@v10.3.0
|
|
25
|
+
with:
|
|
26
|
+
version: '0.12.24'
|
|
27
|
+
- run: uv sync --locked
|
|
28
|
+
- run: uv run --locked ruff check .
|
|
29
|
+
- run: uv run --locked ruff format --check .
|
|
30
|
+
- run: uv run --locked mypy src
|
|
31
|
+
- run: uv run --locked deptry src
|
|
32
|
+
- run: uv run --locked pytest tests/unit -q --cov --cov-report=term-missing --cov-report=xml
|
|
33
|
+
# Use the synced interpreter: uv run triggers Ray runtime-env repackaging.
|
|
34
|
+
- run: .venv/bin/python -m pytest tests/integration -q
|
|
35
|
+
- run: uv audit --locked --python-version '${{ matrix.python }}' --python-platform linux
|
|
36
|
+
- run: uv audit --locked --project examples/toy_batch --python-version 3.13 --python-platform linux
|
|
37
|
+
- if: matrix.python == '3.13'
|
|
38
|
+
run: |
|
|
39
|
+
uv sync --locked --group e2e
|
|
40
|
+
uv run --locked --group e2e python -c 'from multipass_vm_sdk import MultipassClient, MultipassVM, find_ssh_public_key'
|
|
41
|
+
uv run --locked --group e2e pytest tests/unit/test_vm_cleanup.py -q
|
|
42
|
+
uv run --locked --group e2e pytest -o addopts= --collect-only tests/e2e -q
|
|
43
|
+
uv sync --locked --project examples/toy_batch
|
|
44
|
+
uv run --locked --project examples/toy_batch pytest examples/toy_batch/tests -q
|
|
45
|
+
distribution:
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
timeout-minutes: 10
|
|
48
|
+
steps:
|
|
49
|
+
- uses: actions/checkout@v7.0.1
|
|
50
|
+
- uses: actions/setup-python@v7.0.0
|
|
51
|
+
with:
|
|
52
|
+
python-version: '3.13'
|
|
53
|
+
- uses: astral-sh/setup-uv@v10.3.0
|
|
54
|
+
with:
|
|
55
|
+
version: '0.12.24'
|
|
56
|
+
- run: uv sync --locked
|
|
57
|
+
- run: uv build
|
|
58
|
+
- run: uv run --locked twine check --strict dist/*
|
|
59
|
+
- name: Install and import the wheel outside the checkout
|
|
60
|
+
run: |
|
|
61
|
+
uv venv /tmp/rd-wheel
|
|
62
|
+
uv pip install --python /tmp/rd-wheel/bin/python dist/*.whl
|
|
63
|
+
cd /tmp
|
|
64
|
+
/tmp/rd-wheel/bin/python - <<'PY'
|
|
65
|
+
import importlib
|
|
66
|
+
from importlib.metadata import version
|
|
67
|
+
from pathlib import Path
|
|
68
|
+
from it.unimib.data.nanofaas import ray_dispatcher as rd
|
|
69
|
+
assert rd.Dispatcher and rd.Job
|
|
70
|
+
assert version("ray-dispatcher") == rd.__version__
|
|
71
|
+
assert Path(rd.__file__).with_name("remote_runner.py").is_file()
|
|
72
|
+
for name in ("it", "it.unimib", "it.unimib.data", "it.unimib.data.nanofaas"):
|
|
73
|
+
assert importlib.import_module(name).__file__ is None
|
|
74
|
+
assert importlib.util.find_spec("ray_dispatcher") is None
|
|
75
|
+
PY
|
|
76
|
+
- uses: actions/upload-artifact@v7.0.2
|
|
77
|
+
with:
|
|
78
|
+
name: python-distributions
|
|
79
|
+
path: dist/*
|
|
80
|
+
if-no-files-found: error
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
name: PyPI release
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
workflow_dispatch:
|
|
6
|
+
permissions:
|
|
7
|
+
contents: read
|
|
8
|
+
jobs:
|
|
9
|
+
version:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
timeout-minutes: 5
|
|
12
|
+
outputs:
|
|
13
|
+
publish: ${{ steps.version.outputs.publish }}
|
|
14
|
+
version: ${{ steps.version.outputs.version }}
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v7.0.1
|
|
17
|
+
with:
|
|
18
|
+
fetch-depth: 0
|
|
19
|
+
- uses: actions/setup-python@v7.0.0
|
|
20
|
+
with:
|
|
21
|
+
python-version: '3.13'
|
|
22
|
+
- uses: astral-sh/setup-uv@v10.3.0
|
|
23
|
+
with:
|
|
24
|
+
version: '0.12.24'
|
|
25
|
+
- run: uv sync --locked
|
|
26
|
+
- id: version
|
|
27
|
+
run: .venv/bin/python .github/scripts/release_version.py
|
|
28
|
+
env:
|
|
29
|
+
BEFORE_SHA: ${{ github.event.before }}
|
|
30
|
+
checks:
|
|
31
|
+
needs: version
|
|
32
|
+
if: needs.version.outputs.publish == 'true' || github.event_name == 'workflow_dispatch'
|
|
33
|
+
uses: ./.github/workflows/ci.yml
|
|
34
|
+
publish:
|
|
35
|
+
needs: [version, checks]
|
|
36
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'Nanofaas/ray-dispatcher' && needs.version.outputs.publish == 'true'
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
timeout-minutes: 10
|
|
39
|
+
concurrency:
|
|
40
|
+
group: pypi-${{ needs.version.outputs.version }}
|
|
41
|
+
cancel-in-progress: false
|
|
42
|
+
environment:
|
|
43
|
+
name: pypi
|
|
44
|
+
url: https://pypi.org/project/ray-dispatcher/
|
|
45
|
+
permissions:
|
|
46
|
+
id-token: write
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/download-artifact@v8.0.2
|
|
49
|
+
with:
|
|
50
|
+
name: python-distributions
|
|
51
|
+
path: dist
|
|
52
|
+
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ray-dispatcher contributors
|
|
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,158 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: ray-dispatcher
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Generic Ray-scheduled dispatcher for subprocess jobs on SSH-accessible VMs.
|
|
5
|
+
Project-URL: Repository, https://github.com/Nanofaas/ray-dispatcher
|
|
6
|
+
Project-URL: Issues, https://github.com/Nanofaas/ray-dispatcher/issues
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Requires-Dist: fabric<4,>=3.2
|
|
11
|
+
Requires-Dist: paramiko<6,>=5
|
|
12
|
+
Requires-Dist: pyyaml<7,>=6.0
|
|
13
|
+
Requires-Dist: ray<3,>=2.56
|
|
14
|
+
Provides-Extra: progress
|
|
15
|
+
Requires-Dist: rich<15,>=13; extra == 'progress'
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# ray-dispatcher
|
|
19
|
+
|
|
20
|
+
Generic, Ray-scheduled dispatcher for subprocess jobs on SSH-accessible VMs.
|
|
21
|
+
See `docs/superpowers/specs/` for the design and `docs/superpowers/plans/` for
|
|
22
|
+
the implementation plan.
|
|
23
|
+
|
|
24
|
+
## Installation and imports
|
|
25
|
+
|
|
26
|
+
Install with `pip install ray-dispatcher` or `uv add ray-dispatcher`.
|
|
27
|
+
Requires Python >=3.10. For development, install from a checkout with
|
|
28
|
+
`uv sync --locked`.
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from it.unimib.data.nanofaas.ray_dispatcher import Dispatcher, Inventory, Job, Project
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The installable distribution remains `ray-dispatcher`. Imports have migrated
|
|
35
|
+
from `ray_dispatcher` to `it.unimib.data.nanofaas.ray_dispatcher`; update existing
|
|
36
|
+
consumers. There is no legacy import alias. Parent packages are implicit
|
|
37
|
+
PEP 420 namespaces, so other distributions can share the Nanofaas namespace.
|
|
38
|
+
|
|
39
|
+
Licensed under the [MIT License](LICENSE).
|
|
40
|
+
|
|
41
|
+
## Publishing
|
|
42
|
+
|
|
43
|
+
The [release workflow](.github/workflows/release.yml) publishes to PyPI when a
|
|
44
|
+
push to `main` increases the version. Update both `version` in `pyproject.toml`
|
|
45
|
+
and `__version__` in `src/it/unimib/data/nanofaas/ray_dispatcher/__init__.py`,
|
|
46
|
+
then update both locks and commit them together with the version bump:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
uv lock
|
|
50
|
+
uv lock --project examples/toy_batch
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Version decreases and mismatched versions fail the workflow.
|
|
54
|
+
|
|
55
|
+
Before the first release, configure a GitHub **pending trusted publisher** at
|
|
56
|
+
[PyPI account Publishing](https://pypi.org/manage/account/publishing/):
|
|
57
|
+
|
|
58
|
+
| Setting | Value |
|
|
59
|
+
| --- | --- |
|
|
60
|
+
| PyPI project name | `ray-dispatcher` |
|
|
61
|
+
| GitHub owner | `Nanofaas` |
|
|
62
|
+
| Repository | `ray-dispatcher` |
|
|
63
|
+
| Workflow filename | `release.yml` |
|
|
64
|
+
| Environment | `pypi` |
|
|
65
|
+
|
|
66
|
+
See the [official setup instructions](https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc/).
|
|
67
|
+
The pending publisher creates the PyPI project on the first successful upload;
|
|
68
|
+
it does not reserve the name. No API token or repository secret is needed.
|
|
69
|
+
|
|
70
|
+
Run **PyPI release → Run workflow** in GitHub Actions to verify the complete
|
|
71
|
+
release pipeline without uploading anything. Download the `python-distributions`
|
|
72
|
+
artifact to inspect wheel and sdist. A version bump on `main` runs the same
|
|
73
|
+
checks on Python 3.10 and 3.13, including integration tests, audits, metadata
|
|
74
|
+
validation and an isolated wheel install, before uploading those exact artifacts.
|
|
75
|
+
Only the upload job receives OIDC permission. Unchanged versions do not publish;
|
|
76
|
+
re-running an already successful release will fail because PyPI files cannot
|
|
77
|
+
be overwritten. Fix failures and re-run the failed jobs before making another
|
|
78
|
+
version bump.
|
|
79
|
+
|
|
80
|
+
## Development checks
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
uv sync --locked
|
|
84
|
+
uv run ruff check .
|
|
85
|
+
uv run ruff format --check .
|
|
86
|
+
uv run mypy src
|
|
87
|
+
uv run deptry src
|
|
88
|
+
uv audit --locked
|
|
89
|
+
uv run pytest tests/unit -q --cov --cov-report=term-missing
|
|
90
|
+
uv build
|
|
91
|
+
uv run twine check --strict dist/*
|
|
92
|
+
uv run pytest tests/unit -q
|
|
93
|
+
.venv/bin/python -m pytest tests/integration -q
|
|
94
|
+
.venv/bin/python -m pytest -q
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
CI runs lint, formatting, strict type checking and the complete local test suite
|
|
98
|
+
on Python 3.10 and 3.13. Integration tests start a local Ray runtime; unit tests
|
|
99
|
+
do not start Ray. Run integration tests with the interpreter created by
|
|
100
|
+
`uv sync`: wrapping them in `uv run` activates Ray’s automatic runtime-environment
|
|
101
|
+
repackaging, which is unnecessary for this exclusively local runtime.
|
|
102
|
+
|
|
103
|
+
Real-VM tests require Python >=3.11, a running Multipass daemon and an SSH public
|
|
104
|
+
key. They create disposable VMs and delete them after the session:
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
uv sync --locked --group e2e
|
|
108
|
+
uv run --group e2e pytest -o addopts= tests/e2e -m e2e -q
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The example and E2E fixtures use the PyPI distribution `multipass-vm-sdk==0.6.0`
|
|
112
|
+
and import `multipass_vm_sdk`.
|
|
113
|
+
|
|
114
|
+
## Lifecycle and API changes
|
|
115
|
+
|
|
116
|
+
`running_hosts()` returns `{(batch_id, job_id): host}`. Update consumers from
|
|
117
|
+
`hosts.get(job_id)` to `hosts.get((handle.batch_id, handle.job_id))` to distinguish
|
|
118
|
+
jobs with the same name in separate batches.
|
|
119
|
+
|
|
120
|
+
`teardown()` confirms worker completion and orphan cleanup before releasing
|
|
121
|
+
session ownership. If cleanup cannot be confirmed within 30 seconds, it raises
|
|
122
|
+
`DispatcherError`, keeps the session heartbeats and job metadata, and rejects new
|
|
123
|
+
submissions. Restore connectivity and retry `teardown()`; do not discard the
|
|
124
|
+
Dispatcher while it still protects unfinished work. A process crash ends those
|
|
125
|
+
heartbeats, so ownership protection then lasts only until the session lock TTL.
|
|
126
|
+
|
|
127
|
+
Child logs stream to remote files and are collected into local attempt logs.
|
|
128
|
+
The dispatcher disables the runner's duplicate forwarding over SSH to avoid
|
|
129
|
+
capturing unbounded child output in worker memory. The standalone runner still
|
|
130
|
+
forwards output by default.
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
## Quality gates
|
|
134
|
+
|
|
135
|
+
Use uv 0.12.24, pinned in CI, and `uv sync --locked`. Ruff checks security rules
|
|
136
|
+
(`S`) and cyclomatic complexity (`C901`) in addition to formatting and existing
|
|
137
|
+
lint rules. Complexity is capped at the current maximum of 16; lower the cap
|
|
138
|
+
as lifecycle functions are simplified. Security exceptions are scoped to
|
|
139
|
+
intentional subprocess execution, test assertions and fixture credentials.
|
|
140
|
+
|
|
141
|
+
Unit tests measure statement and branch coverage with a minimum of 80%
|
|
142
|
+
(current baseline: 81%). Ray integration and real-VM tests run separately;
|
|
143
|
+
the coverage number does not include remote processes. `deptry` checks direct,
|
|
144
|
+
transitive, unused and development-only imports. The unused legacy `progress`
|
|
145
|
+
extra is retained for installation compatibility and explicitly excluded from
|
|
146
|
+
the unused-dependency check.
|
|
147
|
+
|
|
148
|
+
`uv audit --locked` checks known vulnerabilities in the resolved dependencies.
|
|
149
|
+
CI audits the root and example locks and runs weekly as well as on pushes and
|
|
150
|
+
pull requests, so newly disclosed vulnerabilities are detected without a code
|
|
151
|
+
change. Dependabot proposes weekly Python and GitHub Actions updates; updates
|
|
152
|
+
require review and passing checks. Secret scanning and push protection are
|
|
153
|
+
enabled on GitHub.
|
|
154
|
+
|
|
155
|
+
CI builds wheel and sdist, validates their metadata with Twine and installs the
|
|
156
|
+
wheel in a clean environment outside the checkout. The release workflow reuses
|
|
157
|
+
these checks before publishing a version bump. See the
|
|
158
|
+
[repository](https://github.com/Nanofaas/ray-dispatcher).
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# ray-dispatcher
|
|
2
|
+
|
|
3
|
+
Generic, Ray-scheduled dispatcher for subprocess jobs on SSH-accessible VMs.
|
|
4
|
+
See `docs/superpowers/specs/` for the design and `docs/superpowers/plans/` for
|
|
5
|
+
the implementation plan.
|
|
6
|
+
|
|
7
|
+
## Installation and imports
|
|
8
|
+
|
|
9
|
+
Install with `pip install ray-dispatcher` or `uv add ray-dispatcher`.
|
|
10
|
+
Requires Python >=3.10. For development, install from a checkout with
|
|
11
|
+
`uv sync --locked`.
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from it.unimib.data.nanofaas.ray_dispatcher import Dispatcher, Inventory, Job, Project
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The installable distribution remains `ray-dispatcher`. Imports have migrated
|
|
18
|
+
from `ray_dispatcher` to `it.unimib.data.nanofaas.ray_dispatcher`; update existing
|
|
19
|
+
consumers. There is no legacy import alias. Parent packages are implicit
|
|
20
|
+
PEP 420 namespaces, so other distributions can share the Nanofaas namespace.
|
|
21
|
+
|
|
22
|
+
Licensed under the [MIT License](LICENSE).
|
|
23
|
+
|
|
24
|
+
## Publishing
|
|
25
|
+
|
|
26
|
+
The [release workflow](.github/workflows/release.yml) publishes to PyPI when a
|
|
27
|
+
push to `main` increases the version. Update both `version` in `pyproject.toml`
|
|
28
|
+
and `__version__` in `src/it/unimib/data/nanofaas/ray_dispatcher/__init__.py`,
|
|
29
|
+
then update both locks and commit them together with the version bump:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
uv lock
|
|
33
|
+
uv lock --project examples/toy_batch
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Version decreases and mismatched versions fail the workflow.
|
|
37
|
+
|
|
38
|
+
Before the first release, configure a GitHub **pending trusted publisher** at
|
|
39
|
+
[PyPI account Publishing](https://pypi.org/manage/account/publishing/):
|
|
40
|
+
|
|
41
|
+
| Setting | Value |
|
|
42
|
+
| --- | --- |
|
|
43
|
+
| PyPI project name | `ray-dispatcher` |
|
|
44
|
+
| GitHub owner | `Nanofaas` |
|
|
45
|
+
| Repository | `ray-dispatcher` |
|
|
46
|
+
| Workflow filename | `release.yml` |
|
|
47
|
+
| Environment | `pypi` |
|
|
48
|
+
|
|
49
|
+
See the [official setup instructions](https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc/).
|
|
50
|
+
The pending publisher creates the PyPI project on the first successful upload;
|
|
51
|
+
it does not reserve the name. No API token or repository secret is needed.
|
|
52
|
+
|
|
53
|
+
Run **PyPI release → Run workflow** in GitHub Actions to verify the complete
|
|
54
|
+
release pipeline without uploading anything. Download the `python-distributions`
|
|
55
|
+
artifact to inspect wheel and sdist. A version bump on `main` runs the same
|
|
56
|
+
checks on Python 3.10 and 3.13, including integration tests, audits, metadata
|
|
57
|
+
validation and an isolated wheel install, before uploading those exact artifacts.
|
|
58
|
+
Only the upload job receives OIDC permission. Unchanged versions do not publish;
|
|
59
|
+
re-running an already successful release will fail because PyPI files cannot
|
|
60
|
+
be overwritten. Fix failures and re-run the failed jobs before making another
|
|
61
|
+
version bump.
|
|
62
|
+
|
|
63
|
+
## Development checks
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
uv sync --locked
|
|
67
|
+
uv run ruff check .
|
|
68
|
+
uv run ruff format --check .
|
|
69
|
+
uv run mypy src
|
|
70
|
+
uv run deptry src
|
|
71
|
+
uv audit --locked
|
|
72
|
+
uv run pytest tests/unit -q --cov --cov-report=term-missing
|
|
73
|
+
uv build
|
|
74
|
+
uv run twine check --strict dist/*
|
|
75
|
+
uv run pytest tests/unit -q
|
|
76
|
+
.venv/bin/python -m pytest tests/integration -q
|
|
77
|
+
.venv/bin/python -m pytest -q
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
CI runs lint, formatting, strict type checking and the complete local test suite
|
|
81
|
+
on Python 3.10 and 3.13. Integration tests start a local Ray runtime; unit tests
|
|
82
|
+
do not start Ray. Run integration tests with the interpreter created by
|
|
83
|
+
`uv sync`: wrapping them in `uv run` activates Ray’s automatic runtime-environment
|
|
84
|
+
repackaging, which is unnecessary for this exclusively local runtime.
|
|
85
|
+
|
|
86
|
+
Real-VM tests require Python >=3.11, a running Multipass daemon and an SSH public
|
|
87
|
+
key. They create disposable VMs and delete them after the session:
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
uv sync --locked --group e2e
|
|
91
|
+
uv run --group e2e pytest -o addopts= tests/e2e -m e2e -q
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The example and E2E fixtures use the PyPI distribution `multipass-vm-sdk==0.6.0`
|
|
95
|
+
and import `multipass_vm_sdk`.
|
|
96
|
+
|
|
97
|
+
## Lifecycle and API changes
|
|
98
|
+
|
|
99
|
+
`running_hosts()` returns `{(batch_id, job_id): host}`. Update consumers from
|
|
100
|
+
`hosts.get(job_id)` to `hosts.get((handle.batch_id, handle.job_id))` to distinguish
|
|
101
|
+
jobs with the same name in separate batches.
|
|
102
|
+
|
|
103
|
+
`teardown()` confirms worker completion and orphan cleanup before releasing
|
|
104
|
+
session ownership. If cleanup cannot be confirmed within 30 seconds, it raises
|
|
105
|
+
`DispatcherError`, keeps the session heartbeats and job metadata, and rejects new
|
|
106
|
+
submissions. Restore connectivity and retry `teardown()`; do not discard the
|
|
107
|
+
Dispatcher while it still protects unfinished work. A process crash ends those
|
|
108
|
+
heartbeats, so ownership protection then lasts only until the session lock TTL.
|
|
109
|
+
|
|
110
|
+
Child logs stream to remote files and are collected into local attempt logs.
|
|
111
|
+
The dispatcher disables the runner's duplicate forwarding over SSH to avoid
|
|
112
|
+
capturing unbounded child output in worker memory. The standalone runner still
|
|
113
|
+
forwards output by default.
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
## Quality gates
|
|
117
|
+
|
|
118
|
+
Use uv 0.12.24, pinned in CI, and `uv sync --locked`. Ruff checks security rules
|
|
119
|
+
(`S`) and cyclomatic complexity (`C901`) in addition to formatting and existing
|
|
120
|
+
lint rules. Complexity is capped at the current maximum of 16; lower the cap
|
|
121
|
+
as lifecycle functions are simplified. Security exceptions are scoped to
|
|
122
|
+
intentional subprocess execution, test assertions and fixture credentials.
|
|
123
|
+
|
|
124
|
+
Unit tests measure statement and branch coverage with a minimum of 80%
|
|
125
|
+
(current baseline: 81%). Ray integration and real-VM tests run separately;
|
|
126
|
+
the coverage number does not include remote processes. `deptry` checks direct,
|
|
127
|
+
transitive, unused and development-only imports. The unused legacy `progress`
|
|
128
|
+
extra is retained for installation compatibility and explicitly excluded from
|
|
129
|
+
the unused-dependency check.
|
|
130
|
+
|
|
131
|
+
`uv audit --locked` checks known vulnerabilities in the resolved dependencies.
|
|
132
|
+
CI audits the root and example locks and runs weekly as well as on pushes and
|
|
133
|
+
pull requests, so newly disclosed vulnerabilities are detected without a code
|
|
134
|
+
change. Dependabot proposes weekly Python and GitHub Actions updates; updates
|
|
135
|
+
require review and passing checks. Secret scanning and push protection are
|
|
136
|
+
enabled on GitHub.
|
|
137
|
+
|
|
138
|
+
CI builds wheel and sdist, validates their metadata with Twine and installs the
|
|
139
|
+
wheel in a clean environment outside the checkout. The release workflow reuses
|
|
140
|
+
these checks before publishing a version bump. See the
|
|
141
|
+
[repository](https://github.com/Nanofaas/ray-dispatcher).
|