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.
Files changed (150) hide show
  1. ray_dispatcher-0.1.2/.github/dependabot.yml +20 -0
  2. ray_dispatcher-0.1.2/.github/scripts/release_version.py +48 -0
  3. ray_dispatcher-0.1.2/.github/workflows/ci.yml +80 -0
  4. ray_dispatcher-0.1.2/.github/workflows/release.yml +52 -0
  5. ray_dispatcher-0.1.2/.gitignore +13 -0
  6. ray_dispatcher-0.1.2/LICENSE +21 -0
  7. ray_dispatcher-0.1.2/PKG-INFO +158 -0
  8. ray_dispatcher-0.1.2/README.md +141 -0
  9. ray_dispatcher-0.1.2/docs/superpowers/plans/2026-06-28-ray-dispatcher-phase-1-foundations.md +1301 -0
  10. ray_dispatcher-0.1.2/docs/superpowers/plans/2026-06-28-ray-dispatcher-phase-2-ssh-transport.md +1108 -0
  11. ray_dispatcher-0.1.2/docs/superpowers/plans/2026-06-28-ray-dispatcher-phase-3a-digests-locking.md +826 -0
  12. ray_dispatcher-0.1.2/docs/superpowers/plans/2026-06-28-ray-dispatcher-phase-3b-provisioning.md +1646 -0
  13. ray_dispatcher-0.1.2/docs/superpowers/plans/2026-06-29-ray-dispatcher-phase-4a-lease-pool.md +882 -0
  14. ray_dispatcher-0.1.2/docs/superpowers/plans/2026-06-29-ray-dispatcher-phase-4b-lease-service.md +766 -0
  15. ray_dispatcher-0.1.2/docs/superpowers/plans/2026-06-29-ray-dispatcher-phase-5a-results.md +572 -0
  16. ray_dispatcher-0.1.2/docs/superpowers/plans/2026-06-29-ray-dispatcher-phase-5b-attempt-driver.md +741 -0
  17. ray_dispatcher-0.1.2/docs/superpowers/plans/2026-06-29-ray-dispatcher-phase-6a-job-orchestration.md +474 -0
  18. ray_dispatcher-0.1.2/docs/superpowers/plans/2026-06-29-ray-dispatcher-phase-6b-ray-backend-lifecycle.md +706 -0
  19. ray_dispatcher-0.1.2/docs/superpowers/plans/2026-06-29-ray-dispatcher-phase-6c-submit-resolve-heartbeat-timeout.md +815 -0
  20. ray_dispatcher-0.1.2/docs/superpowers/plans/2026-06-29-ray-dispatcher-phase-6d-dispatcher-lifecycle.md +761 -0
  21. ray_dispatcher-0.1.2/docs/superpowers/plans/2026-06-30-ray-dispatcher-phase-9-live-host-visibility.md +429 -0
  22. ray_dispatcher-0.1.2/docs/superpowers/plans/2026-10-11-audit-fixes.md +251 -0
  23. ray_dispatcher-0.1.2/docs/superpowers/plans/2026-10-11-python-namespace.md +66 -0
  24. ray_dispatcher-0.1.2/docs/superpowers/specs/2026-06-27-ray-dispatcher-design.md +742 -0
  25. ray_dispatcher-0.1.2/docs/superpowers/specs/2026-06-30-live-host-visibility-design.md +100 -0
  26. ray_dispatcher-0.1.2/docs/superpowers/specs/2026-07-06-ray-dispatcher-bug-fixes-design.md +24 -0
  27. ray_dispatcher-0.1.2/docs/superpowers/specs/2026-10-11-python-namespace-design.md +48 -0
  28. ray_dispatcher-0.1.2/examples/toy_batch/README.md +75 -0
  29. ray_dispatcher-0.1.2/examples/toy_batch/configs/run_01.json +1 -0
  30. ray_dispatcher-0.1.2/examples/toy_batch/configs/run_02.json +1 -0
  31. ray_dispatcher-0.1.2/examples/toy_batch/configs/run_03.json +1 -0
  32. ray_dispatcher-0.1.2/examples/toy_batch/configs/run_04.json +1 -0
  33. ray_dispatcher-0.1.2/examples/toy_batch/experiment/experiment.py +20 -0
  34. ray_dispatcher-0.1.2/examples/toy_batch/experiment/pyproject.toml +7 -0
  35. ray_dispatcher-0.1.2/examples/toy_batch/experiment/uv.lock +8 -0
  36. ray_dispatcher-0.1.2/examples/toy_batch/pyproject.toml +15 -0
  37. ray_dispatcher-0.1.2/examples/toy_batch/run_batch.py +168 -0
  38. ray_dispatcher-0.1.2/examples/toy_batch/tests/test_experiment.py +50 -0
  39. ray_dispatcher-0.1.2/examples/toy_batch/uv.lock +1024 -0
  40. ray_dispatcher-0.1.2/pyproject.toml +89 -0
  41. ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/__init__.py +71 -0
  42. ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/backends/__init__.py +1 -0
  43. ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/backends/base.py +44 -0
  44. ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/backends/ssh_ray.py +603 -0
  45. ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/digests.py +79 -0
  46. ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/dispatcher.py +187 -0
  47. ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/errors.py +60 -0
  48. ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/locking.py +162 -0
  49. ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/models.py +296 -0
  50. ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/paths.py +39 -0
  51. ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/provisioning.py +519 -0
  52. ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/remote_runner.py +117 -0
  53. ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/results.py +158 -0
  54. ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/scheduling.py +765 -0
  55. ray_dispatcher-0.1.2/src/it/unimib/data/nanofaas/ray_dispatcher/ssh.py +344 -0
  56. ray_dispatcher-0.1.2/tests/conftest.py +9 -0
  57. ray_dispatcher-0.1.2/tests/e2e/__init__.py +0 -0
  58. ray_dispatcher-0.1.2/tests/e2e/conftest.py +140 -0
  59. ray_dispatcher-0.1.2/tests/e2e/test_concurrency.py +113 -0
  60. ray_dispatcher-0.1.2/tests/e2e/test_provisioning.py +173 -0
  61. ray_dispatcher-0.1.2/tests/e2e/test_smoke.py +96 -0
  62. ray_dispatcher-0.1.2/tests/e2e/test_teardown.py +48 -0
  63. ray_dispatcher-0.1.2/tests/e2e/test_timeout.py +80 -0
  64. ray_dispatcher-0.1.2/tests/integration/__init__.py +0 -0
  65. ray_dispatcher-0.1.2/tests/integration/test_attempt_task.py +108 -0
  66. ray_dispatcher-0.1.2/tests/integration/test_backend_exclusive_runtime.py +24 -0
  67. ray_dispatcher-0.1.2/tests/integration/test_backend_running_hosts.py +73 -0
  68. ray_dispatcher-0.1.2/tests/integration/test_backend_setup.py +169 -0
  69. ray_dispatcher-0.1.2/tests/integration/test_backend_submit.py +260 -0
  70. ray_dispatcher-0.1.2/tests/integration/test_backend_sweeper.py +77 -0
  71. ray_dispatcher-0.1.2/tests/integration/test_backend_teardown.py +136 -0
  72. ray_dispatcher-0.1.2/tests/integration/test_dispatcher_e2e.py +116 -0
  73. ray_dispatcher-0.1.2/tests/integration/test_host_lease_actor.py +40 -0
  74. ray_dispatcher-0.1.2/tests/unit/test_actor_lease_handle.py +29 -0
  75. ray_dispatcher-0.1.2/tests/unit/test_assemble_job_result.py +42 -0
  76. ray_dispatcher-0.1.2/tests/unit/test_attempt_task.py +54 -0
  77. ray_dispatcher-0.1.2/tests/unit/test_backend_base.py +20 -0
  78. ray_dispatcher-0.1.2/tests/unit/test_backend_config.py +24 -0
  79. ray_dispatcher-0.1.2/tests/unit/test_digests_env.py +48 -0
  80. ray_dispatcher-0.1.2/tests/unit/test_digests_runner.py +16 -0
  81. ray_dispatcher-0.1.2/tests/unit/test_digests_source.py +50 -0
  82. ray_dispatcher-0.1.2/tests/unit/test_dispatcher.py +352 -0
  83. ray_dispatcher-0.1.2/tests/unit/test_errors.py +38 -0
  84. ray_dispatcher-0.1.2/tests/unit/test_execute_attempt.py +116 -0
  85. ray_dispatcher-0.1.2/tests/unit/test_execute_attempt_cancel.py +87 -0
  86. ray_dispatcher-0.1.2/tests/unit/test_execute_attempt_timeout.py +193 -0
  87. ray_dispatcher-0.1.2/tests/unit/test_failed_attempt_json.py +14 -0
  88. ray_dispatcher-0.1.2/tests/unit/test_heartbeat_thread.py +48 -0
  89. ray_dispatcher-0.1.2/tests/unit/test_lease.py +42 -0
  90. ray_dispatcher-0.1.2/tests/unit/test_lease_pool_acquire.py +71 -0
  91. ray_dispatcher-0.1.2/tests/unit/test_lease_pool_current_hosts.py +47 -0
  92. ray_dispatcher-0.1.2/tests/unit/test_lease_pool_exclusions.py +47 -0
  93. ray_dispatcher-0.1.2/tests/unit/test_lease_pool_heartbeat.py +41 -0
  94. ray_dispatcher-0.1.2/tests/unit/test_lease_pool_heartbeat_late.py +43 -0
  95. ray_dispatcher-0.1.2/tests/unit/test_lease_pool_orphans.py +38 -0
  96. ray_dispatcher-0.1.2/tests/unit/test_lease_pool_quarantine.py +48 -0
  97. ray_dispatcher-0.1.2/tests/unit/test_lease_pool_quarantined_hosts.py +37 -0
  98. ray_dispatcher-0.1.2/tests/unit/test_lease_pool_release.py +44 -0
  99. ray_dispatcher-0.1.2/tests/unit/test_lease_pool_sweep.py +89 -0
  100. ray_dispatcher-0.1.2/tests/unit/test_lease_service_acquire.py +46 -0
  101. ray_dispatcher-0.1.2/tests/unit/test_lease_service_current_hosts.py +33 -0
  102. ray_dispatcher-0.1.2/tests/unit/test_lease_service_methods.py +65 -0
  103. ray_dispatcher-0.1.2/tests/unit/test_lease_service_no_capacity.py +53 -0
  104. ray_dispatcher-0.1.2/tests/unit/test_models_hosts.py +112 -0
  105. ray_dispatcher-0.1.2/tests/unit/test_models_jobs.py +114 -0
  106. ray_dispatcher-0.1.2/tests/unit/test_models_project.py +82 -0
  107. ray_dispatcher-0.1.2/tests/unit/test_models_results.py +85 -0
  108. ray_dispatcher-0.1.2/tests/unit/test_namespace.py +12 -0
  109. ray_dispatcher-0.1.2/tests/unit/test_paths.py +49 -0
  110. ray_dispatcher-0.1.2/tests/unit/test_provisioning_env.py +88 -0
  111. ray_dispatcher-0.1.2/tests/unit/test_provisioning_host.py +104 -0
  112. ray_dispatcher-0.1.2/tests/unit/test_provisioning_inventory.py +173 -0
  113. ray_dispatcher-0.1.2/tests/unit/test_provisioning_layout.py +78 -0
  114. ray_dispatcher-0.1.2/tests/unit/test_provisioning_preflight.py +75 -0
  115. ray_dispatcher-0.1.2/tests/unit/test_provisioning_python.py +63 -0
  116. ray_dispatcher-0.1.2/tests/unit/test_provisioning_runner.py +54 -0
  117. ray_dispatcher-0.1.2/tests/unit/test_provisioning_secrets.py +94 -0
  118. ray_dispatcher-0.1.2/tests/unit/test_provisioning_source.py +45 -0
  119. ray_dispatcher-0.1.2/tests/unit/test_provisioning_uv.py +106 -0
  120. ray_dispatcher-0.1.2/tests/unit/test_public_api.py +44 -0
  121. ray_dispatcher-0.1.2/tests/unit/test_publish_failure.py +64 -0
  122. ray_dispatcher-0.1.2/tests/unit/test_reconcile_host.py +56 -0
  123. ray_dispatcher-0.1.2/tests/unit/test_release_version.py +84 -0
  124. ray_dispatcher-0.1.2/tests/unit/test_remote_runner.py +83 -0
  125. ray_dispatcher-0.1.2/tests/unit/test_remote_runner_forward.py +113 -0
  126. ray_dispatcher-0.1.2/tests/unit/test_results_collect.py +134 -0
  127. ray_dispatcher-0.1.2/tests/unit/test_results_layout.py +34 -0
  128. ray_dispatcher-0.1.2/tests/unit/test_results_manifest.py +69 -0
  129. ray_dispatcher-0.1.2/tests/unit/test_results_publish.py +26 -0
  130. ray_dispatcher-0.1.2/tests/unit/test_rsync_argv.py +50 -0
  131. ray_dispatcher-0.1.2/tests/unit/test_run_job.py +329 -0
  132. ray_dispatcher-0.1.2/tests/unit/test_run_job_registers_pid.py +92 -0
  133. ray_dispatcher-0.1.2/tests/unit/test_run_paths.py +31 -0
  134. ray_dispatcher-0.1.2/tests/unit/test_runner_manifest.py +58 -0
  135. ray_dispatcher-0.1.2/tests/unit/test_running_hosts_batches.py +20 -0
  136. ray_dispatcher-0.1.2/tests/unit/test_secret_env_map.py +23 -0
  137. ray_dispatcher-0.1.2/tests/unit/test_session_lock_acquire.py +25 -0
  138. ray_dispatcher-0.1.2/tests/unit/test_session_lock_contention.py +170 -0
  139. ray_dispatcher-0.1.2/tests/unit/test_session_lock_heartbeat.py +24 -0
  140. ray_dispatcher-0.1.2/tests/unit/test_should_retry.py +38 -0
  141. ray_dispatcher-0.1.2/tests/unit/test_smoke.py +6 -0
  142. ray_dispatcher-0.1.2/tests/unit/test_ssh_config.py +40 -0
  143. ray_dispatcher-0.1.2/tests/unit/test_ssh_connection.py +50 -0
  144. ray_dispatcher-0.1.2/tests/unit/test_ssh_transport.py +118 -0
  145. ray_dispatcher-0.1.2/tests/unit/test_ssh_write_remote_file.py +39 -0
  146. ray_dispatcher-0.1.2/tests/unit/test_teardown_drain.py +103 -0
  147. ray_dispatcher-0.1.2/tests/unit/test_terminate_pgroup.py +70 -0
  148. ray_dispatcher-0.1.2/tests/unit/test_transport_fake.py +26 -0
  149. ray_dispatcher-0.1.2/tests/unit/test_vm_cleanup.py +53 -0
  150. 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,13 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ .pytest_cache/
5
+ .mypy_cache/
6
+ .ruff_cache/
7
+ dist/
8
+ .coverage
9
+ results/
10
+ .worktrees/
11
+
12
+ coverage.xml
13
+ htmlcov/
@@ -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).