oh-my-batch 0.1.1__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {oh_my_batch-0.1.1 → oh_my_batch-0.2.1}/PKG-INFO +19 -5
- {oh_my_batch-0.1.1 → oh_my_batch-0.2.1}/README.md +18 -4
- {oh_my_batch-0.1.1 → oh_my_batch-0.2.1}/oh_my_batch/assets/functions.sh +16 -1
- {oh_my_batch-0.1.1 → oh_my_batch-0.2.1}/oh_my_batch/batch.py +46 -16
- {oh_my_batch-0.1.1 → oh_my_batch-0.2.1}/oh_my_batch/cli.py +3 -0
- {oh_my_batch-0.1.1 → oh_my_batch-0.2.1}/oh_my_batch/job.py +4 -3
- oh_my_batch-0.2.1/oh_my_batch/misc.py +14 -0
- {oh_my_batch-0.1.1 → oh_my_batch-0.2.1}/oh_my_batch/util.py +15 -1
- {oh_my_batch-0.1.1 → oh_my_batch-0.2.1}/pyproject.toml +1 -2
- {oh_my_batch-0.1.1 → oh_my_batch-0.2.1}/LICENSE +0 -0
- {oh_my_batch-0.1.1 → oh_my_batch-0.2.1}/oh_my_batch/__init__.py +0 -0
- {oh_my_batch-0.1.1 → oh_my_batch-0.2.1}/oh_my_batch/__main__.py +0 -0
- {oh_my_batch-0.1.1 → oh_my_batch-0.2.1}/oh_my_batch/assets/__init__.py +0 -0
- {oh_my_batch-0.1.1 → oh_my_batch-0.2.1}/oh_my_batch/combo.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: oh-my-batch
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.2.1
|
4
4
|
Summary:
|
5
5
|
License: GPL
|
6
6
|
Author: weihong.xu
|
@@ -22,19 +22,33 @@ Description-Content-Type: text/markdown
|
|
22
22
|
[](https://pypi.org/project/oh-my-batch/)
|
23
23
|
[](https://pypi.org/project/oh-my-batch/)
|
24
24
|
|
25
|
-
A
|
25
|
+
A toolkit to manipulate batch tasks with command line. Designed for scientific computing community.
|
26
26
|
|
27
27
|
## Features
|
28
28
|
* `omb combo`: generate folders/files from different combinations of parameters
|
29
29
|
* `omb batch`: generate batch scripts from multiple working directories
|
30
30
|
* `omb job`: track the state of job in job schedular
|
31
31
|
|
32
|
+
## Shell Tips
|
33
|
+
`oh-my-batch` is intended to help you implement computational workflows with shell scripts.
|
34
|
+
To make the best use of `oh-my-batch`, you need to know some shell tips.
|
35
|
+
|
36
|
+
* [Retry commands until success in shell script](https://stackoverflow.com/a/79191004/3099733)
|
37
|
+
|
32
38
|
## Install
|
33
39
|
```bash
|
34
40
|
pip install oh-my-batch
|
35
41
|
```
|
36
42
|
|
37
43
|
## Use cases
|
44
|
+
### Load useful functions to your shell script
|
45
|
+
You can load useful functions from `oh-my-batch` this way:
|
46
|
+
|
47
|
+
```bash
|
48
|
+
omb misc export-shell-func > omb-func.sh && source omb-func.sh
|
49
|
+
```
|
50
|
+
|
51
|
+
This will load some useful functions to your shell script, for example, `checkpoint` function.
|
38
52
|
|
39
53
|
### Generate files from different combinations of parameters
|
40
54
|
|
@@ -99,9 +113,9 @@ cat > tmp/lammps_header.sh <<EOF
|
|
99
113
|
EOF
|
100
114
|
|
101
115
|
omb batch \
|
102
|
-
|
103
|
-
|
104
|
-
|
116
|
+
add_work_dirs tmp/tasks/* - \
|
117
|
+
add_header_files tmp/lammps_header.sh - \
|
118
|
+
add_cmds "checkpoint lmp.done ./run.sh" - \
|
105
119
|
make tmp/lmp-{i}.slurm --concurrency 2
|
106
120
|
```
|
107
121
|
|
@@ -4,19 +4,33 @@
|
|
4
4
|
[](https://pypi.org/project/oh-my-batch/)
|
5
5
|
[](https://pypi.org/project/oh-my-batch/)
|
6
6
|
|
7
|
-
A
|
7
|
+
A toolkit to manipulate batch tasks with command line. Designed for scientific computing community.
|
8
8
|
|
9
9
|
## Features
|
10
10
|
* `omb combo`: generate folders/files from different combinations of parameters
|
11
11
|
* `omb batch`: generate batch scripts from multiple working directories
|
12
12
|
* `omb job`: track the state of job in job schedular
|
13
13
|
|
14
|
+
## Shell Tips
|
15
|
+
`oh-my-batch` is intended to help you implement computational workflows with shell scripts.
|
16
|
+
To make the best use of `oh-my-batch`, you need to know some shell tips.
|
17
|
+
|
18
|
+
* [Retry commands until success in shell script](https://stackoverflow.com/a/79191004/3099733)
|
19
|
+
|
14
20
|
## Install
|
15
21
|
```bash
|
16
22
|
pip install oh-my-batch
|
17
23
|
```
|
18
24
|
|
19
25
|
## Use cases
|
26
|
+
### Load useful functions to your shell script
|
27
|
+
You can load useful functions from `oh-my-batch` this way:
|
28
|
+
|
29
|
+
```bash
|
30
|
+
omb misc export-shell-func > omb-func.sh && source omb-func.sh
|
31
|
+
```
|
32
|
+
|
33
|
+
This will load some useful functions to your shell script, for example, `checkpoint` function.
|
20
34
|
|
21
35
|
### Generate files from different combinations of parameters
|
22
36
|
|
@@ -81,9 +95,9 @@ cat > tmp/lammps_header.sh <<EOF
|
|
81
95
|
EOF
|
82
96
|
|
83
97
|
omb batch \
|
84
|
-
|
85
|
-
|
86
|
-
|
98
|
+
add_work_dirs tmp/tasks/* - \
|
99
|
+
add_header_files tmp/lammps_header.sh - \
|
100
|
+
add_cmds "checkpoint lmp.done ./run.sh" - \
|
87
101
|
make tmp/lmp-{i}.slurm --concurrency 2
|
88
102
|
```
|
89
103
|
|
@@ -1,6 +1,7 @@
|
|
1
|
+
GREEN='\033[0;32m'
|
2
|
+
NC='\033[0m'
|
1
3
|
|
2
4
|
checkpoint() {
|
3
|
-
# Usage: checkpoint <flag_file> <command> [arg1] [arg2] ...
|
4
5
|
local flag_file="$1"
|
5
6
|
shift # Remove the first argument so $@ contains only the command and its arguments
|
6
7
|
if [ -f "$flag_file" ]; then
|
@@ -10,6 +11,7 @@ checkpoint() {
|
|
10
11
|
local exit_code=$?
|
11
12
|
if [ $exit_code -eq 0 ]; then
|
12
13
|
local current_time=$(date '+%Y-%m-%d %H:%M:%S')
|
14
|
+
mkdir -p "$(dirname "$flag_file")"
|
13
15
|
printf 'Command succeeded at %s\n' "$current_time" > "$flag_file"
|
14
16
|
echo "Created flag file '$flag_file' with timestamp: $current_time"
|
15
17
|
else
|
@@ -18,3 +20,16 @@ checkpoint() {
|
|
18
20
|
fi
|
19
21
|
fi
|
20
22
|
}
|
23
|
+
|
24
|
+
echo -e "${GREEN}Functiion: checkpoint${NC}"
|
25
|
+
cat <<EOF
|
26
|
+
Usage:
|
27
|
+
checkpoint <flag_file> <command> [arg1] [arg2] ...
|
28
|
+
|
29
|
+
Set a flag file to indicate the completion of a command,
|
30
|
+
so that it will not be executed again next time.
|
31
|
+
|
32
|
+
Example:
|
33
|
+
checkpoint lmp.done lmp -in in.lmp
|
34
|
+
|
35
|
+
EOF
|
@@ -13,7 +13,7 @@ class BatchMaker:
|
|
13
13
|
self._script_bottom = []
|
14
14
|
self._command = []
|
15
15
|
|
16
|
-
def
|
16
|
+
def add_work_dirs(self, *dir: str):
|
17
17
|
"""
|
18
18
|
Add working directories
|
19
19
|
|
@@ -22,39 +22,55 @@ class BatchMaker:
|
|
22
22
|
self._work_dirs.extend(expand_globs(dir))
|
23
23
|
return self
|
24
24
|
|
25
|
-
def
|
25
|
+
def add_header_files(self, *file: str, encoding='utf-8'):
|
26
26
|
"""
|
27
27
|
Add script header from files
|
28
28
|
|
29
29
|
:param file: File path
|
30
30
|
:param encoding: File encoding
|
31
31
|
"""
|
32
|
-
|
33
|
-
self._script_header.append(f.read())
|
32
|
+
self._script_header.extend(load_files(*file, encoding=encoding))
|
34
33
|
return self
|
35
34
|
|
36
|
-
def
|
35
|
+
def add_headers(self, *header: str):
|
36
|
+
"""
|
37
|
+
Add script header
|
38
|
+
|
39
|
+
:param header: Header lines
|
40
|
+
"""
|
41
|
+
self._script_header.extend(header)
|
42
|
+
return self
|
43
|
+
|
44
|
+
def add_bottom_files(self, *file: str, encoding='utf-8'):
|
37
45
|
"""
|
38
46
|
Add script bottom from files
|
39
47
|
|
40
48
|
:param file: File path
|
41
49
|
:param encoding: File encoding
|
42
50
|
"""
|
43
|
-
|
44
|
-
|
51
|
+
self._script_bottom.extend(load_files(*file, encoding=encoding))
|
52
|
+
return self
|
53
|
+
|
54
|
+
def add_bottoms(self, *bottom: str):
|
55
|
+
"""
|
56
|
+
Add script bottom
|
45
57
|
|
46
|
-
|
58
|
+
:param bottom: Bottom lines
|
59
|
+
"""
|
60
|
+
self._script_bottom.extend(bottom)
|
61
|
+
return self
|
62
|
+
|
63
|
+
def add_cmd_files(self, *file: str, encoding='utf-8'):
|
47
64
|
"""
|
48
65
|
Add commands from files to run under every working directory
|
49
66
|
|
50
67
|
:param file: File path
|
51
68
|
:param encoding: File encoding
|
52
69
|
"""
|
53
|
-
|
54
|
-
self._command.append(f.read())
|
70
|
+
self._command.extend(load_files(*file, encoding=encoding))
|
55
71
|
return self
|
56
72
|
|
57
|
-
def
|
73
|
+
def add_cmds(self, *cmd: str):
|
58
74
|
"""
|
59
75
|
add commands to run under every working directory
|
60
76
|
|
@@ -68,10 +84,10 @@ class BatchMaker:
|
|
68
84
|
Make batch script files from the previous setup
|
69
85
|
|
70
86
|
:param path: Path to save batch script files, use {i} to represent index
|
71
|
-
:param concurrency: Number of
|
87
|
+
:param concurrency: Number of scripts to to make
|
72
88
|
"""
|
73
89
|
# inject pre-defined functions
|
74
|
-
self.
|
90
|
+
self.add_header_files(get_asset('functions.sh'))
|
75
91
|
|
76
92
|
header = '\n'.join(self._script_header)
|
77
93
|
bottom = '\n'.join(self._script_bottom)
|
@@ -80,10 +96,10 @@ class BatchMaker:
|
|
80
96
|
work_dirs_arr = "\n".join(shlex.quote(w) for w in work_dirs)
|
81
97
|
body.extend([
|
82
98
|
'[ -n "$PBS_O_WORKDIR" ] && cd $PBS_O_WORKDIR # fix PBS',
|
83
|
-
f'
|
99
|
+
f'WORK_DIRS=({work_dirs_arr})',
|
84
100
|
'',
|
85
|
-
'for
|
86
|
-
'pushd $
|
101
|
+
'for WORK_DIR in "${WORK_DIRS[@]}"; do',
|
102
|
+
'pushd $WORK_DIR',
|
87
103
|
*self._command,
|
88
104
|
'popd',
|
89
105
|
'done'
|
@@ -94,3 +110,17 @@ class BatchMaker:
|
|
94
110
|
with open(out_path, 'w', encoding=encoding) as f:
|
95
111
|
f.write(script)
|
96
112
|
os.chmod(out_path, mode_translate(str(mode)))
|
113
|
+
|
114
|
+
|
115
|
+
def load_files(*file, encoding='utf-8', raise_invalid=False):
|
116
|
+
"""
|
117
|
+
Load files from paths
|
118
|
+
|
119
|
+
:param files: List of file paths
|
120
|
+
:return: List of file contents
|
121
|
+
"""
|
122
|
+
result = []
|
123
|
+
for file in expand_globs(file, raise_invalid=raise_invalid):
|
124
|
+
with open(file, 'r', encoding=encoding) as f:
|
125
|
+
result.append(f.read())
|
126
|
+
return result
|
@@ -6,7 +6,7 @@ import time
|
|
6
6
|
import os
|
7
7
|
import re
|
8
8
|
|
9
|
-
from .util import expand_globs, shell_run, parse_csv
|
9
|
+
from .util import expand_globs, shell_run, parse_csv, ensure_dir, log_cp
|
10
10
|
|
11
11
|
|
12
12
|
logger = logging.getLogger(__name__)
|
@@ -70,6 +70,7 @@ class BaseJobManager:
|
|
70
70
|
while True:
|
71
71
|
self._update_jobs(jobs, max_tries, opts)
|
72
72
|
if recovery:
|
73
|
+
ensure_dir(recovery)
|
73
74
|
with open(recovery, 'w', encoding='utf-8') as f:
|
74
75
|
json.dump(jobs, f, indent=2)
|
75
76
|
|
@@ -103,7 +104,7 @@ class Slurm(BaseJobManager):
|
|
103
104
|
query_cmd = f'{self._sacct_bin} -X -P --format=JobID,JobName,State -j {",".join(job_ids)}'
|
104
105
|
cp = shell_run(query_cmd)
|
105
106
|
if cp.returncode != 0:
|
106
|
-
logger.error('Failed to query job status: %s', cp
|
107
|
+
logger.error('Failed to query job status: %s', log_cp(cp))
|
107
108
|
return jobs
|
108
109
|
logger.info('Job status:\n%s', cp.stdout.decode('utf-8'))
|
109
110
|
new_state = parse_csv(cp.stdout.decode('utf-8'))
|
@@ -132,7 +133,7 @@ class Slurm(BaseJobManager):
|
|
132
133
|
cp = shell_run(submit_cmd)
|
133
134
|
if cp.returncode != 0:
|
134
135
|
job['state'] = JobState.FAILED
|
135
|
-
logger.error('Failed to submit job: %s', cp
|
136
|
+
logger.error('Failed to submit job: %s', log_cp(cp))
|
136
137
|
else:
|
137
138
|
job['id'] = self._parse_job_id(cp.stdout.decode('utf-8'))
|
138
139
|
assert job['id'], 'Failed to parse job id'
|
@@ -0,0 +1,14 @@
|
|
1
|
+
from .assets import get_asset
|
2
|
+
|
3
|
+
class Misc:
|
4
|
+
|
5
|
+
def export_shell_func(self):
|
6
|
+
"""
|
7
|
+
Export shell functions for batch scripts
|
8
|
+
|
9
|
+
For example, you can load them to you shell environment with the following command:
|
10
|
+
omb misc export-shell-func > omb-func.sh && source omb-func.sh
|
11
|
+
"""
|
12
|
+
shell_func = get_asset('functions.sh')
|
13
|
+
with open(shell_func, 'r', encoding='utf-8') as f:
|
14
|
+
print(f.read())
|
@@ -83,4 +83,18 @@ def parse_csv(text: str, delimiter="|"):
|
|
83
83
|
Parse CSV text to list of dictionaries
|
84
84
|
"""
|
85
85
|
reader = csv.DictReader(text.splitlines(), delimiter=delimiter)
|
86
|
-
return list(reader)
|
86
|
+
return list(reader)
|
87
|
+
|
88
|
+
|
89
|
+
def log_cp(cp):
|
90
|
+
"""
|
91
|
+
Log child process
|
92
|
+
"""
|
93
|
+
log = f'Command: {cp.args}\nReturn code: {cp.returncode}'
|
94
|
+
|
95
|
+
out = cp.stdout.decode('utf-8').strip()
|
96
|
+
if out:
|
97
|
+
log += f'\nSTDOUT:\n{out}'
|
98
|
+
err = cp.stderr.decode('utf-8').strip()
|
99
|
+
if err:
|
100
|
+
log += f'\nSTDERR:\n{err}'
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "oh-my-batch"
|
3
|
-
version = "0.
|
3
|
+
version = "0.2.1"
|
4
4
|
description = ""
|
5
5
|
authors = ["weihong.xu <xuweihong.cn@gmail.com>"]
|
6
6
|
license = "GPL"
|
@@ -11,7 +11,6 @@ packages = [{include = "oh_my_batch"}]
|
|
11
11
|
python = "^3.8"
|
12
12
|
fire = "^0.7.0"
|
13
13
|
|
14
|
-
|
15
14
|
[build-system]
|
16
15
|
requires = ["poetry-core"]
|
17
16
|
build-backend = "poetry.core.masonry.api"
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|