cluster-uv 0.0.1.post1__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 (66) hide show
  1. cluster_uv-0.0.1.post1/.claude-plugin/plugin.json +8 -0
  2. cluster_uv-0.0.1.post1/.copier-answers.yml +8 -0
  3. cluster_uv-0.0.1.post1/.github/ISSUE_TEMPLATE/bug_report.md +24 -0
  4. cluster_uv-0.0.1.post1/.github/ISSUE_TEMPLATE/feature_proposal.md +18 -0
  5. cluster_uv-0.0.1.post1/.github/PULL_REQUEST_TEMPLATE.md +5 -0
  6. cluster_uv-0.0.1.post1/.github/workflows/build.yaml +136 -0
  7. cluster_uv-0.0.1.post1/.github/workflows/docs.yaml +35 -0
  8. cluster_uv-0.0.1.post1/.github/workflows/publish.yaml +47 -0
  9. cluster_uv-0.0.1.post1/.gitignore +18 -0
  10. cluster_uv-0.0.1.post1/.pre-commit-config.yaml +51 -0
  11. cluster_uv-0.0.1.post1/.python-version +1 -0
  12. cluster_uv-0.0.1.post1/CLAUDE.md +52 -0
  13. cluster_uv-0.0.1.post1/CONTRIBUTING.md +39 -0
  14. cluster_uv-0.0.1.post1/LICENSE +21 -0
  15. cluster_uv-0.0.1.post1/PKG-INFO +183 -0
  16. cluster_uv-0.0.1.post1/README.md +167 -0
  17. cluster_uv-0.0.1.post1/cluv/__init__.py +0 -0
  18. cluster_uv-0.0.1.post1/cluv/__main__.py +317 -0
  19. cluster_uv-0.0.1.post1/cluv/cache.py +99 -0
  20. cluster_uv-0.0.1.post1/cluv/cli/__init__.py +13 -0
  21. cluster_uv-0.0.1.post1/cluv/cli/dashboard.py +23 -0
  22. cluster_uv-0.0.1.post1/cluv/cli/init.py +313 -0
  23. cluster_uv-0.0.1.post1/cluv/cli/login.py +60 -0
  24. cluster_uv-0.0.1.post1/cluv/cli/run.py +32 -0
  25. cluster_uv-0.0.1.post1/cluv/cli/status.py +562 -0
  26. cluster_uv-0.0.1.post1/cluv/cli/submit.py +645 -0
  27. cluster_uv-0.0.1.post1/cluv/cli/sync.py +627 -0
  28. cluster_uv-0.0.1.post1/cluv/config.py +210 -0
  29. cluster_uv-0.0.1.post1/cluv/job.py +143 -0
  30. cluster_uv-0.0.1.post1/cluv/py.typed +0 -0
  31. cluster_uv-0.0.1.post1/cluv/remote.py +252 -0
  32. cluster_uv-0.0.1.post1/cluv/slurm.py +391 -0
  33. cluster_uv-0.0.1.post1/cluv/ssh.py +12 -0
  34. cluster_uv-0.0.1.post1/cluv/utils.py +35 -0
  35. cluster_uv-0.0.1.post1/docs/guides/introduction.md +73 -0
  36. cluster_uv-0.0.1.post1/docs/guides/syncing-datasets.md +53 -0
  37. cluster_uv-0.0.1.post1/docs/index.md +61 -0
  38. cluster_uv-0.0.1.post1/docs/reference/cli/init.md +4 -0
  39. cluster_uv-0.0.1.post1/docs/reference/cli/login.md +1 -0
  40. cluster_uv-0.0.1.post1/docs/reference/cli/run.md +3 -0
  41. cluster_uv-0.0.1.post1/docs/reference/cli/submit.md +3 -0
  42. cluster_uv-0.0.1.post1/docs/reference/cli/sync.md +1 -0
  43. cluster_uv-0.0.1.post1/docs/reference/config.md +1 -0
  44. cluster_uv-0.0.1.post1/docs/reference/remote.md +1 -0
  45. cluster_uv-0.0.1.post1/examples/pytorch-example/.gitignore +2 -0
  46. cluster_uv-0.0.1.post1/examples/pytorch-example/README.md +23 -0
  47. cluster_uv-0.0.1.post1/examples/pytorch-example/main.py +101 -0
  48. cluster_uv-0.0.1.post1/examples/pytorch-example/pyproject.toml +90 -0
  49. cluster_uv-0.0.1.post1/examples/pytorch-example/scripts/job.sh +13 -0
  50. cluster_uv-0.0.1.post1/mkdocs.yaml +51 -0
  51. cluster_uv-0.0.1.post1/pyproject.toml +149 -0
  52. cluster_uv-0.0.1.post1/scripts/job.sh +9 -0
  53. cluster_uv-0.0.1.post1/scripts/safe_job.sh +45 -0
  54. cluster_uv-0.0.1.post1/tests/__init__.py +0 -0
  55. cluster_uv-0.0.1.post1/tests/conftest.py +20 -0
  56. cluster_uv-0.0.1.post1/tests/data/.gitkeep +1 -0
  57. cluster_uv-0.0.1.post1/tests/data/dataset.txt +1 -0
  58. cluster_uv-0.0.1.post1/tests/test_config.py +334 -0
  59. cluster_uv-0.0.1.post1/tests/test_init.py +210 -0
  60. cluster_uv-0.0.1.post1/tests/test_integration.py +399 -0
  61. cluster_uv-0.0.1.post1/tests/test_slurm.py +424 -0
  62. cluster_uv-0.0.1.post1/tests/test_ssh.py +32 -0
  63. cluster_uv-0.0.1.post1/tests/test_submit.py +667 -0
  64. cluster_uv-0.0.1.post1/tests/test_sync.py +93 -0
  65. cluster_uv-0.0.1.post1/tests/test_sync_refs.py +28 -0
  66. cluster_uv-0.0.1.post1/uv.lock +1934 -0
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "cluv",
3
+ "description": "Assist with cluv — a UV-based multi-cluster HPC project manager",
4
+ "version": "0.1.0",
5
+ "keywords": ["hpc", "slurm", "uv", "ssh", "mila", "drac"],
6
+ "author": { "name": "fabrice" },
7
+ "license": "MIT"
8
+ }
@@ -0,0 +1,8 @@
1
+ # Changes here will be overwritten by Copier
2
+ _commit: v0.0.5
3
+ _src_path: gh:mila-iqia/tool_template
4
+ project_description: A new project/tool called cluv.
5
+ python_version: '3.13'
6
+ tool_name: cluv
7
+ your_email: normandf@mila.quebec
8
+ your_name: Fabrice Normandin
@@ -0,0 +1,24 @@
1
+ ---
2
+
3
+ name: Bug report
4
+ about: Create a report to help us improve
5
+ title: '[BUG]: '
6
+ labels: bug
7
+ assignees: ''
8
+
9
+ ---
10
+
11
+ ### What command did you run?
12
+ <!-- Please paste the exact command you ran that caused the bug. -->
13
+
14
+ ### Describe the bug
15
+ <!-- A clear and concise description of what the bug is. If there is an error traceback, please paste it here. -->
16
+
17
+ ### Screenshots
18
+ <!-- If applicable, add screenshots to help explain your problem. -->
19
+
20
+ ### Setup
21
+ <!-- Please provide details about your setup : OS, Cluv config, Clusters... -->
22
+
23
+ ### Additional context
24
+ <!-- Add any other context about the problem here. -->
@@ -0,0 +1,18 @@
1
+ ---
2
+
3
+ name: Feature proposal
4
+ about: Propose a new feature or improvement
5
+ title: '[FEAT]: '
6
+ labels: enhancement
7
+ assignees: ''
8
+
9
+ ---
10
+
11
+ ### Context
12
+ <!-- Why is this feature needed? What problem does it solve or what use case does it enable? -->
13
+
14
+ ### Proposed solution
15
+ <!-- Describe how you'd like this to work. Include example commands or expected output if applicable. -->
16
+
17
+ ### Additional context
18
+ <!-- Any other context, references, or screenshots that might be helpful. -->
@@ -0,0 +1,5 @@
1
+ ## Summary
2
+ <!-- A clear and concise description of the feature you're proposing. -->
3
+
4
+ ## Issues
5
+ <!-- List any related issues here. If this is a new feature, you can create an issue first and link it here. -->
@@ -0,0 +1,136 @@
1
+ # This workflow will install Python dependencies, run tests and lint with a single version of Python
2
+ # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
3
+
4
+ name: Python application
5
+
6
+ on:
7
+ push:
8
+ branches:
9
+ - master
10
+ pull_request:
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ # https://stackoverflow.com/a/72408109/6388696
16
+ # https://docs.github.com/en/actions/using-jobs/using-concurrency#example-using-concurrency-to-cancel-any-in-progress-job-or-run
17
+ concurrency:
18
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
19
+ cancel-in-progress: true
20
+
21
+ jobs:
22
+ linting:
23
+ name: Run linting/pre-commit checks
24
+ runs-on: ubuntu-latest
25
+ timeout-minutes: 5
26
+ steps:
27
+ - uses: actions/checkout@v4
28
+ - uses: actions/setup-python@v4
29
+ with:
30
+ python-version: "3.13"
31
+ - run: pip install 'pre-commit<4.0.0'
32
+ - run: pre-commit --version
33
+ - run: pre-commit install
34
+ - run: pre-commit run --all-files --show-diff-on-failure
35
+
36
+ check_docs:
37
+ needs: [ linting ]
38
+ runs-on: ubuntu-latest
39
+ steps:
40
+ - uses: actions/checkout@v4
41
+ - name: Install the latest version of uv
42
+ uses: astral-sh/setup-uv@v3
43
+ with:
44
+ version: "latest"
45
+ enable-cache: true
46
+ # https://github.com/astral-sh/setup-uv?tab=readme-ov-file#github-authentication-token
47
+ github-token: ${{ secrets.GITHUB_TOKEN }}
48
+ cache-suffix: "3.13"
49
+ - name: Install dependencies
50
+ run: uv sync --frozen
51
+ - name: Build the documentation (strict mode)
52
+ run: uv run mkdocs build --strict
53
+
54
+ unit_tests:
55
+ needs: [ linting ]
56
+ runs-on: ${{ matrix.platform }}
57
+ strategy:
58
+ max-parallel: 4
59
+ matrix:
60
+ platform: [ "ubuntu-latest", "macos-latest" ]
61
+ python-version: [ "3.13" ]
62
+ steps:
63
+ - uses: actions/checkout@v4
64
+ - name: Install the latest version of uv
65
+ uses: astral-sh/setup-uv@v3
66
+ with:
67
+ version: "latest"
68
+ enable-cache: true
69
+ # https://github.com/astral-sh/setup-uv?tab=readme-ov-file#github-authentication-token
70
+ github-token: ${{ secrets.GITHUB_TOKEN }}
71
+ cache-suffix: ${{ matrix.python-version }}
72
+ - name: Pin python-version ${{ matrix.python-version }}
73
+ run: uv python pin ${{ matrix.python-version }}
74
+ - name: Install dependencies
75
+ run: uv sync --frozen
76
+ - name: Test with pytest
77
+ run: uv run pytest -v --cov=cluv --cov-report=xml --cov-append
78
+ - name: Store coverage report as an artifact
79
+ uses: actions/upload-artifact@v4
80
+ with:
81
+ name: coverage-reports-unit-tests-${{ matrix.platform }}-${{
82
+ matrix.python-version }}
83
+ path: ./coverage.xml
84
+ integration_tests:
85
+ needs: [ unit_tests ]
86
+ runs-on: self-hosted
87
+ # TODO: remove this once we have a decently solid setup, not too reliant on the dev machine
88
+ # having active SSH connections to the Slurm clusters.
89
+ continue-on-error: true
90
+ steps:
91
+ - uses: actions/checkout@v6
92
+ with:
93
+ # Trying to checkout the branch (not just a commit) so that the cluv command
94
+ # that do a git push work in the self-hosted CI.
95
+ fetch-tags: true
96
+ fetch-depth: 0
97
+ - name: Install the latest version of uv
98
+ uses: astral-sh/setup-uv@v7
99
+ with:
100
+ version: "latest"
101
+ enable-cache: true
102
+ # https://github.com/astral-sh/setup-uv?tab=readme-ov-file#github-authentication-token
103
+ github-token: ${{ secrets.GITHUB_TOKEN }}
104
+ - name: Install dependencies
105
+ run: uv sync --frozen
106
+ - name: Run all tests
107
+ run: uv run pytest -v --slow --cov=cluv --cov-report=xml --cov-append
108
+ - name: Store coverage report as an artifact
109
+ uses: actions/upload-artifact@v4
110
+ with:
111
+ name: coverage-reports-integration-tests
112
+ path: ./coverage.xml
113
+
114
+ # https://about.codecov.io/blog/uploading-code-coverage-in-a-separate-job-on-github-actions/
115
+ upload-coverage-codecov:
116
+ needs: [ unit_tests, integration_tests ]
117
+ runs-on: ubuntu-latest
118
+ name: Upload coverage reports to Codecov
119
+ timeout-minutes: 5
120
+ steps:
121
+ - name: Checkout
122
+ uses: actions/checkout@v4
123
+ - name: Download artifacts
124
+ uses: actions/download-artifact@v4
125
+ with:
126
+ pattern: coverage-reports-*
127
+ merge-multiple: false
128
+ # download all the artifacts in this directory (each .coverage.xml will be in a subdirectory)
129
+ # Next step if this doesn't work would be to give the coverage files a unique name and use merge-multiple: true
130
+ path: coverage_reports
131
+ - name: Upload coverage reports to Codecov
132
+ uses: codecov/codecov-action@v4
133
+ with:
134
+ token: ${{ secrets.CODECOV_TOKEN }}
135
+ directory: coverage_reports
136
+ fail_ci_if_error: true
@@ -0,0 +1,35 @@
1
+ name: Publish docs via GitHub Pages
2
+ on:
3
+ push:
4
+ branches:
5
+ - master
6
+
7
+ jobs:
8
+ build:
9
+ name: Deploy docs
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - name: Checkout
13
+ uses: actions/checkout@v4
14
+ - name: Install the latest version of uv
15
+ uses: astral-sh/setup-uv@v3
16
+ with:
17
+ version: "latest"
18
+ enable-cache: true # no need, uses the local uv cache.
19
+ # https://github.com/astral-sh/setup-uv?tab=readme-ov-file#github-authentication-token
20
+ github-token: ${{ secrets.GITHUB_TOKEN }}
21
+ cache-suffix: "3.13"
22
+
23
+ - name: Pin python-version
24
+ run: uv python pin 3.13
25
+
26
+ - name: Install dependencies
27
+ run: uv sync --frozen
28
+
29
+ - name: Deploy docs
30
+ run: uv run mkdocs gh-deploy --force
31
+ # note: Checking if we really need the one below:
32
+ # uses: mhausenblas/mkdocs-deploy-gh-pages@1.9
33
+ # # Or use mhausenblas/mkdocs-deploy-gh-pages@nomaterial to build without the mkdocs-material theme
34
+ # env:
35
+ # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,47 @@
1
+ # This workflow will upload a Python Package using Poetry when a release is created
2
+ # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
3
+
4
+ # This workflow uses actions that are not certified by GitHub.
5
+ # They are provided by a third-party and are governed by
6
+ # separate terms of service, privacy policy, and support
7
+ # documentation.
8
+ on:
9
+ release:
10
+ types: [published]
11
+ workflow_dispatch:
12
+ jobs:
13
+ # https://docs.pypi.org/trusted-publishers/using-a-publisher/
14
+ publish:
15
+ strategy:
16
+ matrix:
17
+ python-version: [3.13]
18
+ os: [ubuntu-latest]
19
+ runs-on: ${{ matrix.os }}
20
+ environment:
21
+ name: pypi
22
+ url: https://pypi.org/p/cluster-uv
23
+ permissions:
24
+ # IMPORTANT: this permission is mandatory for trusted publishing
25
+ id-token: write
26
+ steps:
27
+ - uses: actions/checkout@v4
28
+
29
+ - name: Install the latest version of uv
30
+ uses: astral-sh/setup-uv@v3
31
+ with:
32
+ version: "latest"
33
+ enable-cache: true
34
+ # https://github.com/astral-sh/setup-uv?tab=readme-ov-file#github-authentication-token
35
+ github-token: ${{ secrets.GITHUB_TOKEN }}
36
+ cache-suffix: ${{ matrix.python-version }}
37
+ - name: Pin python-version ${{ matrix.python-version }}
38
+ run: uv python pin ${{ matrix.python-version }}
39
+ - name: Install dependencies
40
+ run: uv sync --frozen
41
+
42
+ - name: Build package
43
+ run: |
44
+ uv build
45
+
46
+ - name: Publish package distributions to PyPI
47
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,18 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ # uv lockfile
13
+ # uv.lock
14
+ .claude
15
+ .vscode
16
+ logs/
17
+ # created by `uv run mkdocs serve` and such.
18
+ site/
@@ -0,0 +1,51 @@
1
+ default_language_version:
2
+ python: python3
3
+
4
+ repos:
5
+ - repo: https://github.com/pre-commit/pre-commit-hooks
6
+ rev: v6.0.0
7
+ hooks:
8
+ # list of supported hooks: https://pre-commit.com/hooks.html
9
+ - id: trailing-whitespace
10
+ require_serial: true
11
+ - id: end-of-file-fixer
12
+ require_serial: true
13
+ # - id: check-docstring-first
14
+ - id: check-added-large-files
15
+ require_serial: true
16
+ exclude: uv.lock
17
+ - id: check-ast
18
+ require_serial: true
19
+ - id: check-yaml
20
+ require_serial: true
21
+ exclude: "mkdocs.yml"
22
+ - id: debug-statements
23
+ require_serial: true
24
+ - id: detect-private-key
25
+ require_serial: true
26
+ - id: check-executables-have-shebangs
27
+ require_serial: true
28
+ - id: check-toml
29
+ require_serial: true
30
+ - id: check-case-conflict
31
+ require_serial: true
32
+
33
+ - repo: https://github.com/astral-sh/ruff-pre-commit
34
+ # Ruff version.
35
+ rev: v0.15.16
36
+ hooks:
37
+ # Run the linter.
38
+ - id: ruff-check
39
+ args: [ --fix ]
40
+ # Run the formatter.
41
+ - id: ruff-format
42
+
43
+ # word spelling linter
44
+ - repo: https://github.com/codespell-project/codespell
45
+ rev: v2.4.2
46
+ hooks:
47
+ - id: codespell
48
+ args:
49
+ - --skip=logs/**,data/**
50
+ # - --ignore-words-list=abc,def
51
+ require_serial: true
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,52 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Commands
6
+
7
+ ```bash
8
+ # Install dependencies and activate the venv
9
+ uv sync
10
+
11
+ # Run cluv directly (no install needed)
12
+ uv run cluv <command>
13
+
14
+ # Run with verbose logging (-v, -vv, -vvv)
15
+ uv run cluv -v status
16
+ ```
17
+
18
+ Run tests with `uv run pytest`.
19
+
20
+ ## Architecture
21
+
22
+ `cluv` is a CLI tool for managing UV-based Python projects across multiple HPC clusters (Mila, DRAC/Narval, etc.).
23
+
24
+ **Entry point**: `cluv/__main__.py:main()` — builds an `argparse`/`simple_parsing` parser with subcommands, then dispatches to the appropriate async or sync function. Each subcommand registers its args in an `add_<cmd>_args()` function in `__main__.py`, and its implementation lives in `cluv/cli/<cmd>.py`.
25
+
26
+ **Config**: `cluv/config.py` — reads the `[tool.cluv]` section from the nearest `pyproject.toml`. Key fields: `clusters` (list of hostnames or table with per-cluster settings), `slurm` (global `SBATCH_*` env var defaults), `cluster_configs` (per-cluster `SBATCH_*` overrides). Config is cached with `@functools.cache`.
27
+
28
+ **SSH connections**: All remote operations go through `milatools.utils.remote_v2.Remote`. Connections reuse existing SSH ControlMaster sockets (checked via `control_socket_is_running_async`) to avoid triggering 2FA prompts. `cluv login` establishes fresh connections sequentially (to avoid concurrent 2FA prompts). The `login.get_remote_without_2fa_prompt()` helper is used by `sync` to only operate on already-connected clusters.
29
+
30
+ **Async pattern**: Multi-cluster operations use `asyncio.gather` for parallelism. The `sync` command uses `milatools.utils.parallel_progress.run_tasks_with_progress_bar` to display per-cluster progress. The top-level `main()` uses `asyncio.run()` when the subcommand function is a coroutine.
31
+
32
+ **Rich console**: A single `rich.Console` instance is created in `cluv/utils.py` and patched into `milatools` internals (`milatools.cli.console`, etc.) so all output goes through one stream.
33
+
34
+ **`cluv status`** (`cluv/cli/status.py`): Currently entirely mock data. The `ClusterStatus` / `JobStats` / `StorageStats` dataclasses define the data model. The `get_mock_cluster_status()` function is the stub to replace with real implementations. The display logic (`_build_cluster_table`, `_build_my_jobs_table`) is separate and should not need changes when real data is wired in.
35
+
36
+ **`cluv sync`** (`cluv/cli/sync.py`): The most complete command. Runs: `git push` locally → `git clone`/`fetch`/`checkout`/`pull` on each remote → `uv sync` on each remote → optional `rsync` of results back. Each cluster's work is encapsulated in `sync_task_function`.
37
+
38
+ **`cluv submit`** (`cluv/cli/submit.py`): `cluv submit <cluster> <job.sh> [--no-sync] [sbatch-flags...] [-- program-args...]`. Enforces a clean git tree, injects `GIT_COMMIT`, merges global + per-cluster `SBATCH_*` env vars from config, then runs `sbatch` on the remote. Arguments before `--` are forwarded as sbatch flags; arguments after `--` are passed to the job script.
39
+
40
+ **`cluv init`** and **`cluv run`**: Not yet implemented (stubs/`NotImplementedError`).
41
+
42
+ ## Cluster notes
43
+
44
+ - **Mila cluster** is typically the "home base". Detected via `Path("/home/mila").exists()` in `utils.current_cluster()`.
45
+ - **DRAC clusters** (narval, tamia, rorqual, fir, etc.) are detected via `$CC_CLUSTER` env var. They expose a `partition-stats` command that prints a text table of queued/running/idle node counts by partition type (Regular vs GPU) and walltime bucket. See `partiton-stats_output.txt` for a sample.
46
+ - DRAC clusters require a special `uv.toml` pointing to the DRAC wheelhouse for offline installs.
47
+ - Cluster hostnames in `[tool.cluv]` must match the SSH hostnames configured in `~/.ssh/config`.
48
+
49
+ ## Work etiquette
50
+
51
+ - Make small, clean commits regularly. Each commit should only contain changes related to a single "action" or "theme".
52
+ - Write minimalist, clean, pythonic code. Avoid being overly general or abstract.
@@ -0,0 +1,39 @@
1
+ # Contributing to cluv
2
+
3
+ Thank you for your interest in contributing to cluv!
4
+
5
+ ## Issues
6
+
7
+ Use [GitHub Issues](https://github.com/mila-iqia/cluv/issues) to report bugs or suggest features.
8
+
9
+ ## Development setup
10
+
11
+ Clone and install project:
12
+ ```bash
13
+ git clone https://github.com/mila-iqia/cluv
14
+ cd cluv
15
+ uv sync
16
+ ```
17
+
18
+ Use your local version as the cluv tool:
19
+ ```bash
20
+ uv tool install --editable <path_to_cluv_repo>
21
+ ```
22
+
23
+ ### Testing
24
+
25
+ ```bash
26
+ uv run pytest
27
+ ```
28
+
29
+ Tests marked `integration` require live SSH connections to real clusters. Skip them locally:
30
+
31
+ ```bash
32
+ uv run pytest -m "not integration"
33
+ ```
34
+
35
+ ### Linting
36
+
37
+ ```bash
38
+ uv run pre-commit run --all-files
39
+ ```
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mila
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,183 @@
1
+ Metadata-Version: 2.4
2
+ Name: cluster-uv
3
+ Version: 0.0.1.post1
4
+ Summary: cluv (from 'cluster' + 'uv') - A versatile tool to work with uv python projects across HPC clusters.
5
+ Author-email: Fabrice Normandin <normandf@mila.quebec>
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.13
8
+ Requires-Dist: milatools>=0.1.9
9
+ Requires-Dist: platformdirs>=4.9.6
10
+ Requires-Dist: pydantic>=2.0
11
+ Requires-Dist: pyyaml>=6.0.3
12
+ Requires-Dist: rich-argparse>=1.7.2
13
+ Requires-Dist: rich>=13.0
14
+ Requires-Dist: simple-parsing>=0.1.8
15
+ Description-Content-Type: text/markdown
16
+
17
+ # cluv
18
+
19
+ cluv — sync UV-based Python projects across HPC clusters.
20
+
21
+ ## Status
22
+
23
+ In early development. Commands are functional, but expect bugs or missing features.
24
+
25
+ ## Requirements
26
+
27
+ - Python >= 3.13
28
+ - [UV](https://docs.astral.sh/uv/)
29
+ - SSH access configured for each cluster in `~/.ssh/config` (run `cluv login` to open ControlMaster sessions)
30
+ - A GitHub repository with your project
31
+
32
+ ## Installation
33
+
34
+ Install as a UV tool:
35
+
36
+ ```bash
37
+ uv tool install git+https://github.com/mila-iqia/cluv
38
+ ```
39
+
40
+ Then you can run `cluv` directly as a command:
41
+
42
+ ```bash
43
+ cluv init
44
+ cluv login mila
45
+ cluv sync mila
46
+ cluv submit mila job.sh
47
+ ```
48
+
49
+ ## Quick Start
50
+
51
+ 1. Initialize your project with:
52
+ ```bash
53
+ cluv init
54
+ ```
55
+ 2. Establish SSH connections to all configured clusters:
56
+ ```bash
57
+ cluv login
58
+ ```
59
+ 3. Sync your project to all clusters and run `uv sync` on each:
60
+ ```bash
61
+ cluv sync
62
+ ```
63
+
64
+ ## Examples
65
+
66
+ See the [examples](examples) folder for sample projects using cluv. Each example includes a README with instructions specific to that project.
67
+
68
+ ## Configuration
69
+
70
+ Add a `[tool.cluv]` section to the `pyproject.toml` of your project. `cluv init` generates a default config, or you can write it by hand.
71
+ See the config at the project root for an example, or refer to the schema below.
72
+
73
+ ### Top-level fields
74
+
75
+ | Field | Type | Description |
76
+ |-------|------|-------------|
77
+ | `clusters` | table | Per-cluster settings, keyed by SSH hostname from `~/.ssh/config`. |
78
+ | `env` | table | Global environment variables applied to all clusters. |
79
+ | `results_path` | string | Path relative to the project root for storing results. `cluv sync` rsyncs that directory back from each remote cluster. |
80
+
81
+ ### `[tool.cluv.clusters.<name>.env]`
82
+ Environment variables for a specific cluster. Values here are merged on top of `[tool.cluv.env]` when submitting.
83
+
84
+ ### Variables priority
85
+ Environment variables can be set at multiple levels when submitting jobs, with the following precedence (highest to lowest):
86
+ 1. Command-line arguments to `cluv submit`.
87
+ 2. Cluster-specific variables in `[tool.cluv.clusters.<name>.env]`
88
+ 3. Global variables in `[tool.cluv.env]`
89
+ 4. SBATCH directives inside the job script (e.g. `#SBATCH --export=VAR=value`)
90
+ 5. Default values from the cluster (e.g. `SBATCH_PARTITION`)
91
+
92
+ ### Example
93
+
94
+ Here's an example `pyproject.toml` with cluv configuration for three clusters, and some global and cluster-specific environment variables:
95
+
96
+ ```toml
97
+ [tool.cluv]
98
+ results_path = "logs"
99
+
100
+ [tool.cluv.env]
101
+ SBATCH_TIMELIMIT = "3:00:00"
102
+ WANDB_MODE = "offline"
103
+
104
+ [tool.cluv.clusters.mila]
105
+ env = { WANDB_MODE="online", SBATCH_PARTITION="long" }
106
+
107
+ [tool.cluv.clusters.narval]
108
+
109
+ [tool.cluv.clusters.tamia]
110
+ ```
111
+
112
+ ## Commands
113
+
114
+ ### `cluv init`
115
+
116
+ Initialize the current directory as a cluv project. Must be run from inside your `$HOME` directory.
117
+
118
+ ```
119
+ cluv init
120
+ ```
121
+
122
+ Default project structure after `cluv init`:
123
+ ```
124
+ my_project/
125
+ ├── README.md
126
+ ├── logs -> $SCRATCH/logs/my_project # symlink to $SCRATCH
127
+ ├── pyproject.toml # includes [tool.cluv] config
128
+ ├── scripts/
129
+ │ ├── job.sh # Slurm job script template
130
+ │ └── safe_job.sh # Slurm job script template (copies .venv and prior results)
131
+ └── src/
132
+ └── my_project/
133
+ └── __init__.py
134
+ ```
135
+
136
+ ### `cluv login`
137
+
138
+ Open SSH ControlMaster connections to all configured clusters. Run this before any command that requires a live connection.
139
+
140
+ ```
141
+ cluv login [<cluster> ...]
142
+ ```
143
+
144
+ ### `cluv sync`
145
+
146
+ Push local git changes, then on each cluster: clone or fetch the repo, check out the current branch, and run `uv sync`. Optionally rsyncs results back if `results_path` is set in the config.
147
+
148
+ ```
149
+ cluv sync [<cluster> ...]
150
+ ```
151
+
152
+
153
+ ### `cluv status`
154
+
155
+ Display an overview of :
156
+ * Cluster: GPU availability, running/queued jobs and disk usage.
157
+ * Jobs: cached jobs from `cluv submit` with their status.
158
+
159
+ ```
160
+ cluv status [<table>]
161
+ ```
162
+
163
+ ### `cluv submit`
164
+
165
+ Submit a SLURM job on a remote cluster.
166
+
167
+ ```
168
+ cluv submit <cluster> <job.sh> [<sbatch-flags> ...] [-- <program-args> ...]
169
+ ```
170
+
171
+ For example:
172
+
173
+ ```bash
174
+ cluv submit rorqual script/job.sh --time=00:10:00 -- python main.py
175
+ ```
176
+
177
+ ### `cluv run`
178
+
179
+ Sync the project to a cluster, then run a command there with `uv run`.
180
+
181
+ ```
182
+ cluv run <cluster> <command> [<args> ...]
183
+ ```