resubmit 0.0.3__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.
resubmit-0.0.3/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Your Name
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,89 @@
1
+ Metadata-Version: 2.4
2
+ Name: resubmit
3
+ Version: 0.0.3
4
+ Summary: Small wrapper around submitit to simplify cluster submissions
5
+ Author: Amir Mehrpanah
6
+ License: MIT
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: submitit>=0.8
10
+ Provides-Extra: debug
11
+ Requires-Dist: debugpy; extra == "debug"
12
+ Dynamic: license-file
13
+
14
+ # resubmit
15
+
16
+ Small utility library to simplify job submission with Submitit on SLURM clusters.
17
+
18
+ Quick usage:
19
+
20
+ - Install locally for development:
21
+
22
+ ```bash
23
+ pip install -e .[debug]
24
+ ```
25
+
26
+ - Use in your project:
27
+
28
+ ```python
29
+ from resubmit import submit_jobs, maybe_attach_debugger
30
+
31
+ # attach remote debugger if requested
32
+ maybe_attach_debugger(args.get("port", None))
33
+
34
+ # submit jobs (list of dicts)
35
+ submit_jobs(jobs_list, my_entrypoint, timeout_min=60, block=True)
36
+ ```
37
+
38
+ ## API
39
+
40
+ ### submit_jobs(...) 🔧
41
+
42
+ Submit multiple jobs to a Slurm cluster using Submitit.
43
+
44
+ Signature (short):
45
+
46
+ `submit_jobs(jobs_args: Iterable[dict], func: Callable[[List[dict]], Any], *, timeout_min: int, cpus_per_task: int = 16, mem_gb: int = 64, num_gpus: int = 1, account: Optional[str] = None, folder: str = "logs/%j", block: bool = False, prompt: bool = True, local_run: bool = False, slurm_additional_parameters: Optional[Dict] = None, constraint: Optional[str] = None, reservation: Optional[str] = None)`
47
+
48
+ - `jobs_args`: iterable of per-job kwargs (each item is passed to `func`).
49
+ - `func`: entrypoint called for each job (should accept a list or single job dict depending on your usage).
50
+ - `timeout_min`, `cpus_per_task`, `mem_gb`, `num_gpus`: common Slurm resources.
51
+ - `account`: optional Slurm account name.
52
+ - `folder`: logs folder for Submitit files (supports `%j` for job id).
53
+ - `block`: if True, waits for all jobs and returns results.
54
+ - `prompt`: if True, asks for confirmation interactively; set to `False` for CI or tests.
55
+ - `local_run`: run the jobs locally without Submitit (useful for debugging).
56
+ - `slurm_additional_parameters`: pass any extra Slurm key/value pairs to Submitit.
57
+ - `constraint` / `reservation`: cluster-specific options kept out of defaults — provide them explicitly if you need them (they take precedence over values in `slurm_additional_parameters`).
58
+
59
+ Example:
60
+
61
+ ```python
62
+ submit_jobs(
63
+ jobs_list,
64
+ my_entrypoint,
65
+ timeout_min=60,
66
+ num_gpus=2,
67
+ prompt=False,
68
+ constraint="gpu",
69
+ )
70
+ ```
71
+
72
+ ### maybe_attach_debugger(port: Optional[int]) 🐞
73
+
74
+ Attach `debugpy` to the job when `port` is provided (> 0). Safe no-op if `port` is `None` or `<= 0`.
75
+
76
+ - If `debugpy` (and `submitit`) are not available on the node, a `RuntimeError` is raised with an explanatory message.
77
+
78
+ Example:
79
+
80
+ ```python
81
+ # attach remote debugger only when a port is provided (e.g., from CLI args)
82
+ maybe_attach_debugger(args.get("port"))
83
+ ```
84
+
85
+ ---
86
+
87
+ Tips:
88
+ - Use `prompt=False` when calling `submit_jobs` from scripts or CI to avoid interactive prompts.
89
+ - Tests demonstrate non-interactive behavior (`prompt=False`) and optional `constraint`/`reservation` handling.
@@ -0,0 +1,76 @@
1
+ # resubmit
2
+
3
+ Small utility library to simplify job submission with Submitit on SLURM clusters.
4
+
5
+ Quick usage:
6
+
7
+ - Install locally for development:
8
+
9
+ ```bash
10
+ pip install -e .[debug]
11
+ ```
12
+
13
+ - Use in your project:
14
+
15
+ ```python
16
+ from resubmit import submit_jobs, maybe_attach_debugger
17
+
18
+ # attach remote debugger if requested
19
+ maybe_attach_debugger(args.get("port", None))
20
+
21
+ # submit jobs (list of dicts)
22
+ submit_jobs(jobs_list, my_entrypoint, timeout_min=60, block=True)
23
+ ```
24
+
25
+ ## API
26
+
27
+ ### submit_jobs(...) 🔧
28
+
29
+ Submit multiple jobs to a Slurm cluster using Submitit.
30
+
31
+ Signature (short):
32
+
33
+ `submit_jobs(jobs_args: Iterable[dict], func: Callable[[List[dict]], Any], *, timeout_min: int, cpus_per_task: int = 16, mem_gb: int = 64, num_gpus: int = 1, account: Optional[str] = None, folder: str = "logs/%j", block: bool = False, prompt: bool = True, local_run: bool = False, slurm_additional_parameters: Optional[Dict] = None, constraint: Optional[str] = None, reservation: Optional[str] = None)`
34
+
35
+ - `jobs_args`: iterable of per-job kwargs (each item is passed to `func`).
36
+ - `func`: entrypoint called for each job (should accept a list or single job dict depending on your usage).
37
+ - `timeout_min`, `cpus_per_task`, `mem_gb`, `num_gpus`: common Slurm resources.
38
+ - `account`: optional Slurm account name.
39
+ - `folder`: logs folder for Submitit files (supports `%j` for job id).
40
+ - `block`: if True, waits for all jobs and returns results.
41
+ - `prompt`: if True, asks for confirmation interactively; set to `False` for CI or tests.
42
+ - `local_run`: run the jobs locally without Submitit (useful for debugging).
43
+ - `slurm_additional_parameters`: pass any extra Slurm key/value pairs to Submitit.
44
+ - `constraint` / `reservation`: cluster-specific options kept out of defaults — provide them explicitly if you need them (they take precedence over values in `slurm_additional_parameters`).
45
+
46
+ Example:
47
+
48
+ ```python
49
+ submit_jobs(
50
+ jobs_list,
51
+ my_entrypoint,
52
+ timeout_min=60,
53
+ num_gpus=2,
54
+ prompt=False,
55
+ constraint="gpu",
56
+ )
57
+ ```
58
+
59
+ ### maybe_attach_debugger(port: Optional[int]) 🐞
60
+
61
+ Attach `debugpy` to the job when `port` is provided (> 0). Safe no-op if `port` is `None` or `<= 0`.
62
+
63
+ - If `debugpy` (and `submitit`) are not available on the node, a `RuntimeError` is raised with an explanatory message.
64
+
65
+ Example:
66
+
67
+ ```python
68
+ # attach remote debugger only when a port is provided (e.g., from CLI args)
69
+ maybe_attach_debugger(args.get("port"))
70
+ ```
71
+
72
+ ---
73
+
74
+ Tips:
75
+ - Use `prompt=False` when calling `submit_jobs` from scripts or CI to avoid interactive prompts.
76
+ - Tests demonstrate non-interactive behavior (`prompt=False`) and optional `constraint`/`reservation` handling.
@@ -0,0 +1,22 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel", "build"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "resubmit"
7
+ version = "0.0.3"
8
+ description = "Small wrapper around submitit to simplify cluster submissions"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ authors = [ { name = "Amir Mehrpanah" } ]
12
+ dependencies = ["submitit>=0.8"]
13
+
14
+ [project.optional-dependencies]
15
+ debug = ["debugpy"]
16
+
17
+ [tool.setuptools.packages.find]
18
+ where = ["src"]
19
+ include = ["resubmit*"]
20
+
21
+ [tool.setuptools]
22
+ package-dir = { "" = "src" }
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,6 @@
1
+ """resubmit: small helpers around submitit for reproducible cluster submissions."""
2
+
3
+ from .submit import submit_jobs
4
+ from .debug import maybe_attach_debugger
5
+
6
+ __all__ = ["submit_jobs", "maybe_attach_debugger"]
@@ -0,0 +1,26 @@
1
+ """Debug helpers (attach remote debugger when running via Submitit)."""
2
+
3
+
4
+ from typing import Optional
5
+
6
+ def maybe_attach_debugger(port: Optional[int]) -> None:
7
+ """Attach debugpy to the current job when `port` is provided (> 0).
8
+
9
+ Safe no-op if `port` is None or <= 0. Raises informative errors if debugpy is missing.
10
+ """
11
+ if port is None or port <= 0:
12
+ return
13
+
14
+ try:
15
+ import submitit
16
+ import debugpy
17
+ except Exception as exc: # pragma: no cover - environmental dependency
18
+ raise RuntimeError(
19
+ "debugging requires 'submitit' and 'debugpy' packages installed on the compute node"
20
+ ) from exc
21
+
22
+ job_env = submitit.JobEnvironment()
23
+ print(f"Debugger is running on node {job_env.hostname} port {port}")
24
+ debugpy.listen((job_env.hostname, port))
25
+ print("Waiting for debugger attach")
26
+ debugpy.wait_for_client()
@@ -0,0 +1,85 @@
1
+ """Core submission utilities wrapping submitit."""
2
+ from typing import Any, Callable, Iterable, List, Optional, Dict
3
+
4
+
5
+ def submit_jobs(
6
+ jobs_args: Iterable[dict],
7
+ func: Callable[[List[dict]], Any],
8
+ *,
9
+ timeout_min: int,
10
+ cpus_per_task: int = 16,
11
+ mem_gb: int = 64,
12
+ num_gpus: int = 1,
13
+ account: Optional[str] = None,
14
+ folder: str = "logs/%j",
15
+ block: bool = False,
16
+ prompt: bool = True,
17
+ local_run: bool = False,
18
+ slurm_additional_parameters: Optional[Dict] = None,
19
+ constraint: Optional[str] = None,
20
+ reservation: Optional[str] = None,
21
+ ):
22
+ """Submit jobs described by `jobs_args` where each entry is a dict of kwargs for `func`.
23
+
24
+ - If `local_run` is True, the function is called directly: `func(jobs_args)`.
25
+ - Otherwise, submits via submitit.AutoExecutor and returns job objects or, if `block` is True, waits and returns results.
26
+
27
+ Optional Slurm settings `constraint` and `reservation` can be provided via explicit
28
+ parameters (they take precedence) or by passing `slurm_additional_parameters`.
29
+ If not provided, they are omitted so the code is not tied to cluster-specific
30
+ defaults.
31
+ """
32
+ jobs_list = list(jobs_args) if not isinstance(jobs_args, list) else jobs_args
33
+
34
+ if len(jobs_list) == 0:
35
+ print("No jobs to run exiting")
36
+ return
37
+
38
+ if local_run:
39
+ print("Running locally (local_run=True)")
40
+ return func(jobs_list)
41
+
42
+ if prompt:
43
+ print("Do you want to continue? [y/n]", flush=True)
44
+ if input() != "y":
45
+ print("Aborted")
46
+ return
47
+
48
+ import submitit
49
+ print("submitting jobs")
50
+ executor = submitit.AutoExecutor(folder=folder)
51
+
52
+ # default slurm params (keep cluster-specific options out unless explicitly set)
53
+ if slurm_additional_parameters is None:
54
+ slurm_additional_parameters = {"gpus": num_gpus}
55
+ else:
56
+ slurm_additional_parameters = dict(slurm_additional_parameters)
57
+ slurm_additional_parameters.setdefault("gpus", num_gpus)
58
+
59
+ # Allow explicit overrides similar to `account`.
60
+ if account is not None:
61
+ slurm_additional_parameters["account"] = account
62
+ if reservation is not None:
63
+ slurm_additional_parameters["reservation"] = reservation
64
+ if constraint is not None:
65
+ slurm_additional_parameters["constraint"] = constraint
66
+
67
+ print("Slurm additional parameters:", slurm_additional_parameters)
68
+
69
+ executor.update_parameters(
70
+ timeout_min=timeout_min,
71
+ cpus_per_task=cpus_per_task,
72
+ mem_gb=mem_gb,
73
+ slurm_additional_parameters=slurm_additional_parameters,
74
+ )
75
+
76
+ jobs = executor.map_array(func, jobs_list)
77
+ print("Job submitted")
78
+
79
+ if block:
80
+ print("Waiting for job to finish")
81
+ results = [job.result() for job in jobs]
82
+ print("All jobs finished")
83
+ return results
84
+
85
+ return jobs
@@ -0,0 +1,89 @@
1
+ Metadata-Version: 2.4
2
+ Name: resubmit
3
+ Version: 0.0.3
4
+ Summary: Small wrapper around submitit to simplify cluster submissions
5
+ Author: Amir Mehrpanah
6
+ License: MIT
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: submitit>=0.8
10
+ Provides-Extra: debug
11
+ Requires-Dist: debugpy; extra == "debug"
12
+ Dynamic: license-file
13
+
14
+ # resubmit
15
+
16
+ Small utility library to simplify job submission with Submitit on SLURM clusters.
17
+
18
+ Quick usage:
19
+
20
+ - Install locally for development:
21
+
22
+ ```bash
23
+ pip install -e .[debug]
24
+ ```
25
+
26
+ - Use in your project:
27
+
28
+ ```python
29
+ from resubmit import submit_jobs, maybe_attach_debugger
30
+
31
+ # attach remote debugger if requested
32
+ maybe_attach_debugger(args.get("port", None))
33
+
34
+ # submit jobs (list of dicts)
35
+ submit_jobs(jobs_list, my_entrypoint, timeout_min=60, block=True)
36
+ ```
37
+
38
+ ## API
39
+
40
+ ### submit_jobs(...) 🔧
41
+
42
+ Submit multiple jobs to a Slurm cluster using Submitit.
43
+
44
+ Signature (short):
45
+
46
+ `submit_jobs(jobs_args: Iterable[dict], func: Callable[[List[dict]], Any], *, timeout_min: int, cpus_per_task: int = 16, mem_gb: int = 64, num_gpus: int = 1, account: Optional[str] = None, folder: str = "logs/%j", block: bool = False, prompt: bool = True, local_run: bool = False, slurm_additional_parameters: Optional[Dict] = None, constraint: Optional[str] = None, reservation: Optional[str] = None)`
47
+
48
+ - `jobs_args`: iterable of per-job kwargs (each item is passed to `func`).
49
+ - `func`: entrypoint called for each job (should accept a list or single job dict depending on your usage).
50
+ - `timeout_min`, `cpus_per_task`, `mem_gb`, `num_gpus`: common Slurm resources.
51
+ - `account`: optional Slurm account name.
52
+ - `folder`: logs folder for Submitit files (supports `%j` for job id).
53
+ - `block`: if True, waits for all jobs and returns results.
54
+ - `prompt`: if True, asks for confirmation interactively; set to `False` for CI or tests.
55
+ - `local_run`: run the jobs locally without Submitit (useful for debugging).
56
+ - `slurm_additional_parameters`: pass any extra Slurm key/value pairs to Submitit.
57
+ - `constraint` / `reservation`: cluster-specific options kept out of defaults — provide them explicitly if you need them (they take precedence over values in `slurm_additional_parameters`).
58
+
59
+ Example:
60
+
61
+ ```python
62
+ submit_jobs(
63
+ jobs_list,
64
+ my_entrypoint,
65
+ timeout_min=60,
66
+ num_gpus=2,
67
+ prompt=False,
68
+ constraint="gpu",
69
+ )
70
+ ```
71
+
72
+ ### maybe_attach_debugger(port: Optional[int]) 🐞
73
+
74
+ Attach `debugpy` to the job when `port` is provided (> 0). Safe no-op if `port` is `None` or `<= 0`.
75
+
76
+ - If `debugpy` (and `submitit`) are not available on the node, a `RuntimeError` is raised with an explanatory message.
77
+
78
+ Example:
79
+
80
+ ```python
81
+ # attach remote debugger only when a port is provided (e.g., from CLI args)
82
+ maybe_attach_debugger(args.get("port"))
83
+ ```
84
+
85
+ ---
86
+
87
+ Tips:
88
+ - Use `prompt=False` when calling `submit_jobs` from scripts or CI to avoid interactive prompts.
89
+ - Tests demonstrate non-interactive behavior (`prompt=False`) and optional `constraint`/`reservation` handling.
@@ -0,0 +1,12 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/resubmit/__init__.py
5
+ src/resubmit/debug.py
6
+ src/resubmit/submit.py
7
+ src/resubmit.egg-info/PKG-INFO
8
+ src/resubmit.egg-info/SOURCES.txt
9
+ src/resubmit.egg-info/dependency_links.txt
10
+ src/resubmit.egg-info/requires.txt
11
+ src/resubmit.egg-info/top_level.txt
12
+ tests/test_resubmit.py
@@ -0,0 +1,4 @@
1
+ submitit>=0.8
2
+
3
+ [debug]
4
+ debugpy
@@ -0,0 +1 @@
1
+ resubmit
@@ -0,0 +1,116 @@
1
+ import pytest
2
+ from resubmit import submit_jobs, maybe_attach_debugger
3
+
4
+
5
+ def dummy_func(jobs):
6
+ # return a list of strings to show behavior
7
+ return [f"ok-{j['id']}" for j in jobs]
8
+
9
+
10
+ def test_submit_local_run():
11
+ jobs = [{"id": 1}, {"id": 2}]
12
+ res = submit_jobs(jobs, dummy_func, timeout_min=1, local_run=True)
13
+ assert res == ["ok-1", "ok-2"]
14
+
15
+
16
+ def test_maybe_attach_debugger_noop():
17
+ # should not raise when port is None or 0
18
+ maybe_attach_debugger(None)
19
+ maybe_attach_debugger(0)
20
+
21
+
22
+ def test_slurm_parameters_optional(monkeypatch):
23
+ events = {}
24
+
25
+ class DummyExecutor:
26
+ def __init__(self, folder):
27
+ events['folder'] = folder
28
+
29
+ def update_parameters(self, **kwargs):
30
+ # capture the parameters passed to the executor
31
+ events['update'] = kwargs
32
+
33
+ def map_array(self, func, jobs_list):
34
+ return []
35
+
36
+ class DummyModule:
37
+ AutoExecutor = DummyExecutor
38
+
39
+ import sys
40
+ monkeypatch.setitem(sys.modules, 'submitit', DummyModule)
41
+
42
+ jobs = [{"id": 1}]
43
+ # default: no constraint/reservation keys
44
+ submit_jobs(jobs, dummy_func, timeout_min=1, local_run=False, num_gpus=2, prompt=False)
45
+ slurm = events['update']['slurm_additional_parameters']
46
+ assert slurm['gpus'] == 2
47
+ assert 'constraint' not in slurm
48
+ assert 'reservation' not in slurm
49
+
50
+
51
+ def test_slurm_parameters_settable(monkeypatch):
52
+ events = {}
53
+
54
+ class DummyExecutor:
55
+ def __init__(self, folder):
56
+ events['folder'] = folder
57
+
58
+ def update_parameters(self, **kwargs):
59
+ events['update'] = kwargs
60
+
61
+ def map_array(self, func, jobs_list):
62
+ return []
63
+
64
+ class DummyModule:
65
+ AutoExecutor = DummyExecutor
66
+
67
+ import sys
68
+ monkeypatch.setitem(sys.modules, 'submitit', DummyModule)
69
+
70
+ jobs = [{"id": 1}]
71
+ submit_jobs(
72
+ jobs,
73
+ dummy_func,
74
+ timeout_min=1,
75
+ local_run=False,
76
+ constraint='thin',
77
+ reservation='safe',
78
+ prompt=False,
79
+ )
80
+ slurm = events['update']['slurm_additional_parameters']
81
+ assert slurm['constraint'] == 'thin'
82
+ assert slurm['reservation'] == 'safe'
83
+
84
+
85
+ def test_slurm_parameters_arg_precedence(monkeypatch):
86
+ events = {}
87
+
88
+ class DummyExecutor:
89
+ def __init__(self, folder):
90
+ events['folder'] = folder
91
+
92
+ def update_parameters(self, **kwargs):
93
+ events['update'] = kwargs
94
+
95
+ def map_array(self, func, jobs_list):
96
+ return []
97
+
98
+ class DummyModule:
99
+ AutoExecutor = DummyExecutor
100
+
101
+ import sys
102
+ monkeypatch.setitem(sys.modules, 'submitit', DummyModule)
103
+
104
+ jobs = [{"id": 1}]
105
+ # slurm_additional_parameters has constraint='foo' but explicit arg should override
106
+ submit_jobs(
107
+ jobs,
108
+ dummy_func,
109
+ timeout_min=1,
110
+ local_run=False,
111
+ slurm_additional_parameters={'constraint': 'foo'},
112
+ constraint='bar',
113
+ prompt=False,
114
+ )
115
+ slurm = events['update']['slurm_additional_parameters']
116
+ assert slurm['constraint'] == 'bar'