cmd-queue 0.1.20__py3-none-any.whl → 0.2.1__py3-none-any.whl

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.

Potentially problematic release.


This version of cmd-queue might be problematic. Click here for more details.

@@ -0,0 +1,746 @@
1
+ Metadata-Version: 2.1
2
+ Name: cmd_queue
3
+ Version: 0.2.1
4
+ Summary: The cmd_queue module for a DAG of bash commands
5
+ Home-page: https://gitlab.kitware.com/computer-vision/cmd_queue
6
+ Author: Kitware Inc., Jon Crall
7
+ Author-email: kitware@kitware.com, jon.crall@kitware.com
8
+ License: Apache 2
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
12
+ Classifier: Topic :: Utilities
13
+ Classifier: License :: OSI Approved :: Apache Software License
14
+ Classifier: Programming Language :: Python :: 3.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Requires-Python: >=3.8
21
+ Description-Content-Type: text/x-rst
22
+ License-File: LICENSE
23
+ Requires-Dist: ubelt>=1.3.0
24
+ Requires-Dist: rich>=12.5.1
25
+ Requires-Dist: scriptconfig>=0.7.9
26
+ Requires-Dist: psutil>=5.9.1
27
+ Requires-Dist: ruamel.yaml>=0.17.22
28
+ Requires-Dist: numpy>=1.19.3; python_version < "3.10" and python_version >= "3.6.0"
29
+ Requires-Dist: pandas>=1.4.0; python_version < "3.10" and python_version >= "3.9"
30
+ Requires-Dist: numpy>=1.21.6; python_version < "3.11" and python_version >= "3.10"
31
+ Requires-Dist: pandas>=1.4.0; python_version < "3.11" and python_version >= "3.10"
32
+ Requires-Dist: networkx>=2.7; python_version < "3.11" and python_version >= "3.8"
33
+ Requires-Dist: numpy>=1.23.2; python_version < "3.12" and python_version >= "3.11"
34
+ Requires-Dist: pandas>=1.5.0; python_version < "3.12" and python_version >= "3.11"
35
+ Requires-Dist: numpy>=1.26.0; python_version < "3.13" and python_version >= "3.12"
36
+ Requires-Dist: pandas>=2.1.1; python_version < "3.13" and python_version >= "3.12"
37
+ Requires-Dist: pandas>=1.1.5; python_version < "3.7" and python_version >= "3.6"
38
+ Requires-Dist: networkx<=2.5.1,>=2.5.1; python_version < "3.7.0" and python_version >= "3.6.0"
39
+ Requires-Dist: networkx>=2.6.2; python_version < "3.8" and python_version >= "3.7"
40
+ Requires-Dist: pandas>=1.3.5; python_version < "3.8" and python_version >= "3.7"
41
+ Requires-Dist: pandas>=1.4.0; python_version < "3.9" and python_version >= "3.8"
42
+ Requires-Dist: networkx>=2.8; python_version < "4.0" and python_version >= "3.11"
43
+ Requires-Dist: numpy>=2.1.0; python_version < "4.0" and python_version >= "3.13"
44
+ Requires-Dist: pandas>=2.2.3; python_version < "4.0" and python_version >= "3.13"
45
+ Provides-Extra: all
46
+ Requires-Dist: ubelt>=1.3.0; extra == "all"
47
+ Requires-Dist: rich>=12.5.1; extra == "all"
48
+ Requires-Dist: scriptconfig>=0.7.9; extra == "all"
49
+ Requires-Dist: psutil>=5.9.1; extra == "all"
50
+ Requires-Dist: ruamel.yaml>=0.17.22; extra == "all"
51
+ Requires-Dist: xdoctest>=1.0.1; extra == "all"
52
+ Provides-Extra: all-strict
53
+ Requires-Dist: ubelt==1.3.0; extra == "all-strict"
54
+ Requires-Dist: rich==12.5.1; extra == "all-strict"
55
+ Requires-Dist: scriptconfig==0.7.9; extra == "all-strict"
56
+ Requires-Dist: psutil==5.9.1; extra == "all-strict"
57
+ Requires-Dist: ruamel.yaml==0.17.22; extra == "all-strict"
58
+ Requires-Dist: xdoctest==1.0.1; extra == "all-strict"
59
+ Requires-Dist: numpy==1.19.3; (python_version < "3.10" and python_version >= "3.6.0") and extra == "all-strict"
60
+ Requires-Dist: pandas==1.4.0; (python_version < "3.10" and python_version >= "3.9") and extra == "all-strict"
61
+ Requires-Dist: coverage==5.3.1; (python_version < "3.10" and python_version >= "3.9") and extra == "all-strict"
62
+ Requires-Dist: pint==0.18; (python_version < "3.10" and python_version >= "3.9") and extra == "all-strict"
63
+ Requires-Dist: numpy==1.21.6; (python_version < "3.11" and python_version >= "3.10") and extra == "all-strict"
64
+ Requires-Dist: pandas==1.4.0; (python_version < "3.11" and python_version >= "3.10") and extra == "all-strict"
65
+ Requires-Dist: pint==0.18; (python_version < "3.11" and python_version >= "3.10") and extra == "all-strict"
66
+ Requires-Dist: networkx==2.7; (python_version < "3.11" and python_version >= "3.8") and extra == "all-strict"
67
+ Requires-Dist: numpy==1.23.2; (python_version < "3.12" and python_version >= "3.11") and extra == "all-strict"
68
+ Requires-Dist: pandas==1.5.0; (python_version < "3.12" and python_version >= "3.11") and extra == "all-strict"
69
+ Requires-Dist: pint==0.18; (python_version < "3.12" and python_version >= "3.11") and extra == "all-strict"
70
+ Requires-Dist: numpy==1.26.0; (python_version < "3.13" and python_version >= "3.12") and extra == "all-strict"
71
+ Requires-Dist: pandas==2.1.1; (python_version < "3.13" and python_version >= "3.12") and extra == "all-strict"
72
+ Requires-Dist: pint==0.23; (python_version < "3.13" and python_version >= "3.12") and extra == "all-strict"
73
+ Requires-Dist: pandas==1.1.5; (python_version < "3.7" and python_version >= "3.6") and extra == "all-strict"
74
+ Requires-Dist: pytest==6.2.0; (python_version < "3.7" and python_version >= "3.6") and extra == "all-strict"
75
+ Requires-Dist: coverage==6.1.1; (python_version < "3.7" and python_version >= "3.6") and extra == "all-strict"
76
+ Requires-Dist: networkx<=2.5.1,==2.5.1; (python_version < "3.7.0" and python_version >= "3.6.0") and extra == "all-strict"
77
+ Requires-Dist: networkx==2.6.2; (python_version < "3.8" and python_version >= "3.7") and extra == "all-strict"
78
+ Requires-Dist: pandas==1.3.5; (python_version < "3.8" and python_version >= "3.7") and extra == "all-strict"
79
+ Requires-Dist: coverage==6.1.1; (python_version < "3.8" and python_version >= "3.7") and extra == "all-strict"
80
+ Requires-Dist: pandas==1.4.0; (python_version < "3.9" and python_version >= "3.8") and extra == "all-strict"
81
+ Requires-Dist: coverage==6.1.1; (python_version < "3.9" and python_version >= "3.8") and extra == "all-strict"
82
+ Requires-Dist: pint==0.18; (python_version < "3.9" and python_version >= "3.8") and extra == "all-strict"
83
+ Requires-Dist: networkx==2.8; (python_version < "4.0" and python_version >= "3.11") and extra == "all-strict"
84
+ Requires-Dist: numpy==2.1.0; (python_version < "4.0" and python_version >= "3.13") and extra == "all-strict"
85
+ Requires-Dist: pandas==2.2.3; (python_version < "4.0" and python_version >= "3.13") and extra == "all-strict"
86
+ Requires-Dist: pint==0.24.4; (python_version < "4.0" and python_version >= "3.13") and extra == "all-strict"
87
+ Requires-Dist: coverage==6.1.1; python_version >= "3.10" and extra == "all-strict"
88
+ Requires-Dist: pytest-cov==3.0.0; python_version >= "3.6.0" and extra == "all-strict"
89
+ Requires-Dist: pytest==7.1.0; python_version >= "3.7" and extra == "all-strict"
90
+ Requires-Dist: textual==0.1.18; python_version >= "3.7" and extra == "all-strict"
91
+ Requires-Dist: numpy>=1.19.3; (python_version < "3.10" and python_version >= "3.6.0") and extra == "all"
92
+ Requires-Dist: pandas>=1.4.0; (python_version < "3.10" and python_version >= "3.9") and extra == "all"
93
+ Requires-Dist: coverage>=5.3.1; (python_version < "3.10" and python_version >= "3.9") and extra == "all"
94
+ Requires-Dist: pint>=0.18; (python_version < "3.10" and python_version >= "3.9") and extra == "all"
95
+ Requires-Dist: numpy>=1.21.6; (python_version < "3.11" and python_version >= "3.10") and extra == "all"
96
+ Requires-Dist: pandas>=1.4.0; (python_version < "3.11" and python_version >= "3.10") and extra == "all"
97
+ Requires-Dist: pint>=0.18; (python_version < "3.11" and python_version >= "3.10") and extra == "all"
98
+ Requires-Dist: networkx>=2.7; (python_version < "3.11" and python_version >= "3.8") and extra == "all"
99
+ Requires-Dist: numpy>=1.23.2; (python_version < "3.12" and python_version >= "3.11") and extra == "all"
100
+ Requires-Dist: pandas>=1.5.0; (python_version < "3.12" and python_version >= "3.11") and extra == "all"
101
+ Requires-Dist: pint>=0.18; (python_version < "3.12" and python_version >= "3.11") and extra == "all"
102
+ Requires-Dist: numpy>=1.26.0; (python_version < "3.13" and python_version >= "3.12") and extra == "all"
103
+ Requires-Dist: pandas>=2.1.1; (python_version < "3.13" and python_version >= "3.12") and extra == "all"
104
+ Requires-Dist: pint>=0.23; (python_version < "3.13" and python_version >= "3.12") and extra == "all"
105
+ Requires-Dist: pandas>=1.1.5; (python_version < "3.7" and python_version >= "3.6") and extra == "all"
106
+ Requires-Dist: pytest>=6.2.0; (python_version < "3.7" and python_version >= "3.6") and extra == "all"
107
+ Requires-Dist: coverage>=6.1.1; (python_version < "3.7" and python_version >= "3.6") and extra == "all"
108
+ Requires-Dist: networkx<=2.5.1,>=2.5.1; (python_version < "3.7.0" and python_version >= "3.6.0") and extra == "all"
109
+ Requires-Dist: networkx>=2.6.2; (python_version < "3.8" and python_version >= "3.7") and extra == "all"
110
+ Requires-Dist: pandas>=1.3.5; (python_version < "3.8" and python_version >= "3.7") and extra == "all"
111
+ Requires-Dist: coverage>=6.1.1; (python_version < "3.8" and python_version >= "3.7") and extra == "all"
112
+ Requires-Dist: pandas>=1.4.0; (python_version < "3.9" and python_version >= "3.8") and extra == "all"
113
+ Requires-Dist: coverage>=6.1.1; (python_version < "3.9" and python_version >= "3.8") and extra == "all"
114
+ Requires-Dist: pint>=0.18; (python_version < "3.9" and python_version >= "3.8") and extra == "all"
115
+ Requires-Dist: networkx>=2.8; (python_version < "4.0" and python_version >= "3.11") and extra == "all"
116
+ Requires-Dist: numpy>=2.1.0; (python_version < "4.0" and python_version >= "3.13") and extra == "all"
117
+ Requires-Dist: pandas>=2.2.3; (python_version < "4.0" and python_version >= "3.13") and extra == "all"
118
+ Requires-Dist: pint>=0.24.4; (python_version < "4.0" and python_version >= "3.13") and extra == "all"
119
+ Requires-Dist: coverage>=6.1.1; python_version >= "3.10" and extra == "all"
120
+ Requires-Dist: pytest-cov>=3.0.0; python_version >= "3.6.0" and extra == "all"
121
+ Requires-Dist: pytest>=7.1.0; python_version >= "3.7" and extra == "all"
122
+ Requires-Dist: textual>=0.1.18; python_version >= "3.7" and extra == "all"
123
+ Provides-Extra: docs
124
+ Requires-Dist: sphinx>=5.0.1; extra == "docs"
125
+ Requires-Dist: sphinx-autobuild>=2021.3.14; extra == "docs"
126
+ Requires-Dist: sphinx-rtd-theme>=1.0.0; extra == "docs"
127
+ Requires-Dist: sphinxcontrib-napoleon>=0.7; extra == "docs"
128
+ Requires-Dist: sphinx-autoapi>=1.8.4; extra == "docs"
129
+ Requires-Dist: Pygments>=2.9.0; extra == "docs"
130
+ Requires-Dist: myst-parser>=0.18.0; extra == "docs"
131
+ Requires-Dist: sphinx-reredirects>=0.0.1; extra == "docs"
132
+ Provides-Extra: docs-strict
133
+ Requires-Dist: sphinx==5.0.1; extra == "docs-strict"
134
+ Requires-Dist: sphinx-autobuild==2021.3.14; extra == "docs-strict"
135
+ Requires-Dist: sphinx-rtd-theme==1.0.0; extra == "docs-strict"
136
+ Requires-Dist: sphinxcontrib-napoleon==0.7; extra == "docs-strict"
137
+ Requires-Dist: sphinx-autoapi==1.8.4; extra == "docs-strict"
138
+ Requires-Dist: Pygments==2.9.0; extra == "docs-strict"
139
+ Requires-Dist: myst-parser==0.18.0; extra == "docs-strict"
140
+ Requires-Dist: sphinx-reredirects==0.0.1; extra == "docs-strict"
141
+ Provides-Extra: linting
142
+ Requires-Dist: flake8>=5.0.0; extra == "linting"
143
+ Provides-Extra: linting-strict
144
+ Requires-Dist: flake8==5.0.0; extra == "linting-strict"
145
+ Provides-Extra: optional
146
+ Provides-Extra: optional-strict
147
+ Requires-Dist: pint==0.18; (python_version < "3.10" and python_version >= "3.9") and extra == "optional-strict"
148
+ Requires-Dist: pint==0.18; (python_version < "3.11" and python_version >= "3.10") and extra == "optional-strict"
149
+ Requires-Dist: pint==0.18; (python_version < "3.12" and python_version >= "3.11") and extra == "optional-strict"
150
+ Requires-Dist: pint==0.23; (python_version < "3.13" and python_version >= "3.12") and extra == "optional-strict"
151
+ Requires-Dist: pint==0.18; (python_version < "3.9" and python_version >= "3.8") and extra == "optional-strict"
152
+ Requires-Dist: pint==0.24.4; (python_version < "4.0" and python_version >= "3.13") and extra == "optional-strict"
153
+ Requires-Dist: textual==0.1.18; python_version >= "3.7" and extra == "optional-strict"
154
+ Requires-Dist: pint>=0.18; (python_version < "3.10" and python_version >= "3.9") and extra == "optional"
155
+ Requires-Dist: pint>=0.18; (python_version < "3.11" and python_version >= "3.10") and extra == "optional"
156
+ Requires-Dist: pint>=0.18; (python_version < "3.12" and python_version >= "3.11") and extra == "optional"
157
+ Requires-Dist: pint>=0.23; (python_version < "3.13" and python_version >= "3.12") and extra == "optional"
158
+ Requires-Dist: pint>=0.18; (python_version < "3.9" and python_version >= "3.8") and extra == "optional"
159
+ Requires-Dist: pint>=0.24.4; (python_version < "4.0" and python_version >= "3.13") and extra == "optional"
160
+ Requires-Dist: textual>=0.1.18; python_version >= "3.7" and extra == "optional"
161
+ Provides-Extra: runtime
162
+ Requires-Dist: ubelt>=1.3.0; extra == "runtime"
163
+ Requires-Dist: rich>=12.5.1; extra == "runtime"
164
+ Requires-Dist: scriptconfig>=0.7.9; extra == "runtime"
165
+ Requires-Dist: psutil>=5.9.1; extra == "runtime"
166
+ Requires-Dist: ruamel.yaml>=0.17.22; extra == "runtime"
167
+ Provides-Extra: runtime-strict
168
+ Requires-Dist: ubelt==1.3.0; extra == "runtime-strict"
169
+ Requires-Dist: rich==12.5.1; extra == "runtime-strict"
170
+ Requires-Dist: scriptconfig==0.7.9; extra == "runtime-strict"
171
+ Requires-Dist: psutil==5.9.1; extra == "runtime-strict"
172
+ Requires-Dist: ruamel.yaml==0.17.22; extra == "runtime-strict"
173
+ Requires-Dist: numpy==1.19.3; (python_version < "3.10" and python_version >= "3.6.0") and extra == "runtime-strict"
174
+ Requires-Dist: pandas==1.4.0; (python_version < "3.10" and python_version >= "3.9") and extra == "runtime-strict"
175
+ Requires-Dist: numpy==1.21.6; (python_version < "3.11" and python_version >= "3.10") and extra == "runtime-strict"
176
+ Requires-Dist: pandas==1.4.0; (python_version < "3.11" and python_version >= "3.10") and extra == "runtime-strict"
177
+ Requires-Dist: networkx==2.7; (python_version < "3.11" and python_version >= "3.8") and extra == "runtime-strict"
178
+ Requires-Dist: numpy==1.23.2; (python_version < "3.12" and python_version >= "3.11") and extra == "runtime-strict"
179
+ Requires-Dist: pandas==1.5.0; (python_version < "3.12" and python_version >= "3.11") and extra == "runtime-strict"
180
+ Requires-Dist: numpy==1.26.0; (python_version < "3.13" and python_version >= "3.12") and extra == "runtime-strict"
181
+ Requires-Dist: pandas==2.1.1; (python_version < "3.13" and python_version >= "3.12") and extra == "runtime-strict"
182
+ Requires-Dist: pandas==1.1.5; (python_version < "3.7" and python_version >= "3.6") and extra == "runtime-strict"
183
+ Requires-Dist: networkx<=2.5.1,==2.5.1; (python_version < "3.7.0" and python_version >= "3.6.0") and extra == "runtime-strict"
184
+ Requires-Dist: networkx==2.6.2; (python_version < "3.8" and python_version >= "3.7") and extra == "runtime-strict"
185
+ Requires-Dist: pandas==1.3.5; (python_version < "3.8" and python_version >= "3.7") and extra == "runtime-strict"
186
+ Requires-Dist: pandas==1.4.0; (python_version < "3.9" and python_version >= "3.8") and extra == "runtime-strict"
187
+ Requires-Dist: networkx==2.8; (python_version < "4.0" and python_version >= "3.11") and extra == "runtime-strict"
188
+ Requires-Dist: numpy==2.1.0; (python_version < "4.0" and python_version >= "3.13") and extra == "runtime-strict"
189
+ Requires-Dist: pandas==2.2.3; (python_version < "4.0" and python_version >= "3.13") and extra == "runtime-strict"
190
+ Requires-Dist: numpy>=1.19.3; (python_version < "3.10" and python_version >= "3.6.0") and extra == "runtime"
191
+ Requires-Dist: pandas>=1.4.0; (python_version < "3.10" and python_version >= "3.9") and extra == "runtime"
192
+ Requires-Dist: numpy>=1.21.6; (python_version < "3.11" and python_version >= "3.10") and extra == "runtime"
193
+ Requires-Dist: pandas>=1.4.0; (python_version < "3.11" and python_version >= "3.10") and extra == "runtime"
194
+ Requires-Dist: networkx>=2.7; (python_version < "3.11" and python_version >= "3.8") and extra == "runtime"
195
+ Requires-Dist: numpy>=1.23.2; (python_version < "3.12" and python_version >= "3.11") and extra == "runtime"
196
+ Requires-Dist: pandas>=1.5.0; (python_version < "3.12" and python_version >= "3.11") and extra == "runtime"
197
+ Requires-Dist: numpy>=1.26.0; (python_version < "3.13" and python_version >= "3.12") and extra == "runtime"
198
+ Requires-Dist: pandas>=2.1.1; (python_version < "3.13" and python_version >= "3.12") and extra == "runtime"
199
+ Requires-Dist: pandas>=1.1.5; (python_version < "3.7" and python_version >= "3.6") and extra == "runtime"
200
+ Requires-Dist: networkx<=2.5.1,>=2.5.1; (python_version < "3.7.0" and python_version >= "3.6.0") and extra == "runtime"
201
+ Requires-Dist: networkx>=2.6.2; (python_version < "3.8" and python_version >= "3.7") and extra == "runtime"
202
+ Requires-Dist: pandas>=1.3.5; (python_version < "3.8" and python_version >= "3.7") and extra == "runtime"
203
+ Requires-Dist: pandas>=1.4.0; (python_version < "3.9" and python_version >= "3.8") and extra == "runtime"
204
+ Requires-Dist: networkx>=2.8; (python_version < "4.0" and python_version >= "3.11") and extra == "runtime"
205
+ Requires-Dist: numpy>=2.1.0; (python_version < "4.0" and python_version >= "3.13") and extra == "runtime"
206
+ Requires-Dist: pandas>=2.2.3; (python_version < "4.0" and python_version >= "3.13") and extra == "runtime"
207
+ Provides-Extra: tests
208
+ Requires-Dist: xdoctest>=1.0.1; extra == "tests"
209
+ Provides-Extra: tests-strict
210
+ Requires-Dist: xdoctest==1.0.1; extra == "tests-strict"
211
+ Requires-Dist: coverage==5.3.1; (python_version < "3.10" and python_version >= "3.9") and extra == "tests-strict"
212
+ Requires-Dist: pytest==6.2.0; (python_version < "3.7" and python_version >= "3.6") and extra == "tests-strict"
213
+ Requires-Dist: coverage==6.1.1; (python_version < "3.7" and python_version >= "3.6") and extra == "tests-strict"
214
+ Requires-Dist: coverage==6.1.1; (python_version < "3.8" and python_version >= "3.7") and extra == "tests-strict"
215
+ Requires-Dist: coverage==6.1.1; (python_version < "3.9" and python_version >= "3.8") and extra == "tests-strict"
216
+ Requires-Dist: coverage==6.1.1; python_version >= "3.10" and extra == "tests-strict"
217
+ Requires-Dist: pytest-cov==3.0.0; python_version >= "3.6.0" and extra == "tests-strict"
218
+ Requires-Dist: pytest==7.1.0; python_version >= "3.7" and extra == "tests-strict"
219
+ Requires-Dist: coverage>=5.3.1; (python_version < "3.10" and python_version >= "3.9") and extra == "tests"
220
+ Requires-Dist: pytest>=6.2.0; (python_version < "3.7" and python_version >= "3.6") and extra == "tests"
221
+ Requires-Dist: coverage>=6.1.1; (python_version < "3.7" and python_version >= "3.6") and extra == "tests"
222
+ Requires-Dist: coverage>=6.1.1; (python_version < "3.8" and python_version >= "3.7") and extra == "tests"
223
+ Requires-Dist: coverage>=6.1.1; (python_version < "3.9" and python_version >= "3.8") and extra == "tests"
224
+ Requires-Dist: coverage>=6.1.1; python_version >= "3.10" and extra == "tests"
225
+ Requires-Dist: pytest-cov>=3.0.0; python_version >= "3.6.0" and extra == "tests"
226
+ Requires-Dist: pytest>=7.1.0; python_version >= "3.7" and extra == "tests"
227
+
228
+ Command Queue - cmd_queue
229
+ =========================
230
+
231
+ .. .. |Appveyor| |Codecov|
232
+
233
+ |Pypi| |Downloads| |GitlabCIPipeline| |GitlabCICoverage| |ReadTheDocs|
234
+
235
+
236
+ +------------------+-------------------------------------------------------------------------------------+
237
+ | Read the docs | https://cmd-queue.readthedocs.io |
238
+ +------------------+-------------------------------------------------------------------------------------+
239
+ | Gitlab | https://gitlab.kitware.com/computer-vision/cmd_queue |
240
+ +------------------+-------------------------------------------------------------------------------------+
241
+ | Pypi | https://pypi.org/project/cmd_queue |
242
+ +------------------+-------------------------------------------------------------------------------------+
243
+ | Slides | https://docs.google.com/presentation/d/1BjJkjMx6bxu1uek-hAGpwj760u9rraVn7st8J5OsZME |
244
+ +------------------+-------------------------------------------------------------------------------------+
245
+
246
+
247
+ This is a simple module for "generating" a bash script that schedules multiples
248
+ jobs (in parallel if possible) on a single machine. There are 3 backends with
249
+ increasing levels of complexity: serial, tmux, and slurm.
250
+
251
+ In serial mode, a single bash script gets written that executes your jobs in
252
+ sequence. There are no external dependencies
253
+
254
+ In tmux mode, multiple tmux sessions get opened and each of them executes your
255
+ independent parts of your jobs. Dependencies are handled.
256
+
257
+ In slurm mode, a real heavy-weight scheduling algorithm is used. In this mode
258
+ we simply convert your jobs to slurm commands and execute them.
259
+
260
+ Under the hood we build a DAG based on your specified dependencies and use this
261
+ to appropriately order jobs.
262
+
263
+ By default, bash scripts that would execute your jobs print to the console.
264
+ This gives the user fine-grained control if they only want to run a subset of a
265
+ pipeline manually. But if asked to run, cmd_queue will execute the bash jobs.
266
+
267
+ Features
268
+ ~~~~~~~~
269
+
270
+ * Bash command scheduling
271
+
272
+ * Execution is optional, can just print commands instead
273
+
274
+ * No-parallelism always-available serial backend
275
+
276
+ * Tmux based lightweight backend
277
+
278
+ * Slurm based heavyweight backend
279
+
280
+ * Python and Bash interface
281
+
282
+ * Rich monitoring / live-control
283
+
284
+
285
+ Installation
286
+ ============
287
+
288
+ The cmd_queue package is available on pypi.
289
+
290
+ .. code:: bash
291
+
292
+ pip install cmd_queue
293
+
294
+ The serial queue backend will always work. To gain access other backends you
295
+ must install their associated dependencies. The tmux backend is the easiest and
296
+ simply requires that tmux is installed (e.g. ``sudo apt install tmux`` on
297
+ Debian systems).
298
+
299
+ Other backends require more complex setups. The slurm backend will require that
300
+ `slurm is installed <https://slurm.schedmd.com/quickstart_admin.html>`_ and the
301
+ daemon is running. The slurm backend is functional and tested, but improvements
302
+ can still be made (help wanted). The airflow backend similarly requires a
303
+ configured airflow server, but is not fully functional or tested (contributions
304
+ to make airflow work / easier are wanted!).
305
+
306
+
307
+ Tmux Queue Demo
308
+ ===============
309
+
310
+ After installing, the following command runs a demo of the tmux queue:
311
+
312
+ .. code:: bash
313
+
314
+ # Reproduce the
315
+ INTERACTIVE_TEST=1 xdoctest -m cmd_queue.tmux_queue TMUXMultiQueue.monitor:1
316
+
317
+
318
+ This executes the following code, which creates two parallel tmux workers and
319
+ submits several bash jobs with non-trivial dependencies.
320
+
321
+ .. code:: python
322
+
323
+ # xdoctest: +REQUIRES(env:INTERACTIVE_TEST)
324
+ from cmd_queue.tmux_queue import * # NOQA
325
+ # Setup a lot of longer running jobs
326
+ n = 2
327
+ self = TMUXMultiQueue(size=n, name='demo_cmd_queue')
328
+ first_job = None
329
+ for i in range(n):
330
+ prev_job = None
331
+ for j in range(4):
332
+ command = f'sleep 1 && echo "This is job {i}.{j}"'
333
+ job = self.submit(command, depends=prev_job)
334
+ prev_job = job
335
+ first_job = first_job or job
336
+ command = f'sleep 1 && echo "this is the last job"'
337
+ job = self.submit(command, depends=[prev_job, first_job])
338
+ self.print_commands(style='rich')
339
+ self.print_graph()
340
+ if self.is_available():
341
+ self.run(block=True, other_session_handler='kill')
342
+
343
+
344
+ When running the ``print_commands`` command will first display all of the submitted
345
+ commands that will be distributed across multiple new tmux sessions. These are
346
+ the commands will be executed. This is useful for spot checking that your bash
347
+ command templating is correct before the queue is executed with ``run``.
348
+
349
+
350
+ .. .. Screenshot of the print_commands output
351
+ .. image:: https://i.imgur.com/rVbyHzM.png
352
+ :height: 300px
353
+ :align: left
354
+
355
+
356
+ The ``print_graph`` command will render the DAG to be executed using
357
+ `network text <https://networkx.org/documentation/stable/reference/readwrite/generated/networkx.readwrite.text.write_network_text.html#networkx.readwrite.text.write_network_text>`_.
358
+ And finally ``run`` is called with ``block=True``, which starts executing the
359
+ DAG and displays progress and job status in rich or textual monitor.
360
+
361
+ .. .. image:: https://i.imgur.com/RbyTvP9.png
362
+ .. :height: 300px
363
+ .. :align: left
364
+
365
+ .. .. Animated gif of the queue from dev/record_demo.sh
366
+ .. image:: https://i.imgur.com/4mxFIMk.gif
367
+ :height: 300px
368
+ :align: left
369
+
370
+
371
+ While this is running it is possible to simply attach to a tmux sessions (e.g.
372
+ ``tmux a``) and inspect a specific queue while it is running. (We recommend
373
+ using ``<ctrl-b>s`` inside of a tmux session to view and navigate through the
374
+ tmux sessions). Unlike the slurm backend, the entire execution of the DAG is
375
+ entirely transparent to the developer! The following screenshot shows the tmux
376
+ sessions spawned while running this demo.
377
+
378
+ .. .. Screenshot of the tmux sessions
379
+ .. image:: https://i.imgur.com/46LRK8M.png
380
+ :height: 300px
381
+ :align: left
382
+
383
+ By default, if there are no errors, these sessions will exit after execution
384
+ completes, but this is configurable. Likewise if there are errors, the tmux
385
+ sessions will persist to allow for debugging.
386
+
387
+
388
+ Modivation
389
+ ==========
390
+ Recently, I needed to run several jobs on 4 jobs across 2 GPUs and then execute
391
+ a script after all of them were done. What I should have done was use slurm or
392
+ some other proper queuing system to schedule the jobs, but instead I wrote my
393
+ own hacky scheduler using tmux. I opened N (number of parallel workers) tmux
394
+ sessions and then I ran independent jobs in each different sessions.
395
+
396
+ This worked unreasonably well for my use cases, and it was nice to be able to effectively schedule jobs without heavyweight software like slurm on my machine.
397
+
398
+ Eventually I did get slurm on my machine, and I abstracted the API of my
399
+ tmux_queue to be a general "command queue" that can use 1 of 3 backends:
400
+ serial, tmux, or slurm.
401
+
402
+
403
+ Niche
404
+ =====
405
+ There are many DAG schedulers out there:
406
+
407
+ * airflow
408
+ * luigi
409
+ * submitit
410
+ * rq_scheduler
411
+
412
+
413
+ The the niche for this is when you have large pipelines of bash commands that
414
+ depend on each other and you want to template out those parameters with logic
415
+ that you define in Python.
416
+
417
+ We plan on adding an airflow backend.
418
+
419
+
420
+ Usage
421
+ =====
422
+
423
+
424
+ There are two ways to use ``cmd_queue``:
425
+
426
+ 1. In Python create a Queue object, and then call the .submit method to pass it
427
+ a shell invocation. It returns an object that you can use to specify
428
+ dependencies of any further calls to .submit. This simply organizes all of
429
+ your CLI invocations into a bash script, which can be inspected and then
430
+ run. There are different backends that enable parallel execution of jobs
431
+ when dependencies allow.
432
+
433
+ 2. There is a way to use it via the CLI, with details shown in cmd_queue
434
+ --help. Usage is basically the same. You create a queue, submit jobs to it,
435
+ you can inspect it, and you can run it.
436
+
437
+
438
+ Example usage in Python:
439
+
440
+ .. code:: python
441
+
442
+ import cmd_queue
443
+
444
+ # Create a Queue object
445
+ self = cmd_queue.Queue.create(name='demo_queue', backend='serial')
446
+
447
+ # Submit bash invocations that you want to run, and mark dependencies.
448
+ job1 = self.submit('echo hello')
449
+ job2 = self.submit('echo world', depends=[job1])
450
+ job3 = self.submit('echo foo')
451
+ job4 = self.submit('echo bar', depends=[job2, job3])
452
+ job5 = self.submit('echo spam', depends=[job1])
453
+
454
+ # Print a graph of job dependencies
455
+ self.print_graph()
456
+
457
+ # Display the simplified bash script to be executed.
458
+ self.print_commands()
459
+
460
+ # Execute the jobs
461
+ self.run()
462
+
463
+
464
+ Example usage in the CLI:
465
+
466
+ .. code:: bash
467
+
468
+ # Create a Queue
469
+ cmd_queue new "demo_cli_queue"
470
+
471
+ # Submit bash invocations that you want to run, and mark dependencies.
472
+ cmd_queue submit --jobname job1 "demo_cli_queue" -- echo hello
473
+ cmd_queue submit --jobname job2 --depends job1 "demo_cli_queue" -- echo world
474
+ cmd_queue submit --jobname job3 "demo_cli_queue" -- echo foo
475
+ cmd_queue submit --jobname job4 --depends job1,job2 "demo_cli_queue" -- echo bar
476
+ cmd_queue submit --jobname job5 --depends job1 "demo_cli_queue" -- echo spam
477
+
478
+ # Display the simplified bash script to be executed.
479
+ cmd_queue show "demo_cli_queue" --backend=serial
480
+
481
+ # Execute the jobs
482
+ cmd_queue run "demo_cli_queue" --backend=serial
483
+
484
+
485
+
486
+
487
+ Examples
488
+ ========
489
+
490
+
491
+ All of the dependency checking and book keeping logic is handled in bash
492
+ itself. Write (or better yet template) your bash scripts in Python, and then
493
+ use cmd_queue to "transpile" these sequences of commands to pure bash.
494
+
495
+
496
+ .. code:: python
497
+
498
+ import cmd_queue
499
+
500
+ # Create a Queue object
501
+ self = cmd_queue.Queue.create(name='demo_queue', backend='serial')
502
+
503
+ # Submit bash invocations that you want to run, and mark dependencies.
504
+ job1 = self.submit('echo hello && sleep 0.5')
505
+ job2 = self.submit('echo world && sleep 0.5', depends=[job1])
506
+ job3 = self.submit('echo foo && sleep 0.5')
507
+ job4 = self.submit('echo bar && sleep 0.5')
508
+ job5 = self.submit('echo spam && sleep 0.5', depends=[job1])
509
+ job6 = self.submit('echo spam && sleep 0.5')
510
+ job7 = self.submit('echo err && false')
511
+ job8 = self.submit('echo spam && sleep 0.5')
512
+ job9 = self.submit('echo eggs && sleep 0.5', depends=[job8])
513
+ job10 = self.submit('echo bazbiz && sleep 0.5', depends=[job9])
514
+
515
+ # Display the simplified bash script to be executed.
516
+ self.print_commands()
517
+
518
+ # Execute the jobs
519
+ self.run()
520
+
521
+
522
+ This prints the bash commands in an appropriate order to resolve dependencies.
523
+
524
+
525
+ .. code:: bash
526
+
527
+ # --- /home/joncrall/.cache/base_queue/demo_queue_2022-04-08_cc9d551e/demo_queue_2022-04-08_cc9d551e.sh
528
+
529
+ #!/bin/bash
530
+ #
531
+ # Jobs
532
+ #
533
+ ### Command 1 / 10 - demo_queue-job-0
534
+ echo hello && sleep 0.5
535
+ #
536
+ ### Command 2 / 10 - demo_queue-job-1
537
+ echo world && sleep 0.5
538
+ #
539
+ ### Command 3 / 10 - demo_queue-job-2
540
+ echo foo && sleep 0.5
541
+ #
542
+ ### Command 4 / 10 - demo_queue-job-3
543
+ echo bar && sleep 0.5
544
+ #
545
+ ### Command 5 / 10 - demo_queue-job-4
546
+ echo spam && sleep 0.5
547
+ #
548
+ ### Command 6 / 10 - demo_queue-job-5
549
+ echo spam && sleep 0.5
550
+ #
551
+ ### Command 7 / 10 - demo_queue-job-6
552
+ echo err && false
553
+ #
554
+ ### Command 8 / 10 - demo_queue-job-7
555
+ echo spam && sleep 0.5
556
+ #
557
+ ### Command 9 / 10 - demo_queue-job-8
558
+ echo eggs && sleep 0.5
559
+ #
560
+ ### Command 10 / 10 - demo_queue-job-9
561
+ echo bazbiz && sleep 0.5
562
+
563
+
564
+ The same code can be run in parallel by chosing a more powerful backend.
565
+ The tmux backend is the lightest weight parallel backend.
566
+
567
+ .. code:: python
568
+
569
+ # Need to tell the tmux queue how many processes can run at the same time
570
+ import cmd_queue
571
+ self = cmd_queue.Queue.create(size=4, name='demo_queue', backend='tmux')
572
+ job1 = self.submit('echo hello && sleep 0.5')
573
+ job2 = self.submit('echo world && sleep 0.5', depends=[job1])
574
+ job3 = self.submit('echo foo && sleep 0.5')
575
+ job4 = self.submit('echo bar && sleep 0.5')
576
+ job5 = self.submit('echo spam && sleep 0.5', depends=[job1])
577
+ job6 = self.submit('echo spam && sleep 0.5')
578
+ job7 = self.submit('echo err && false')
579
+ job8 = self.submit('echo spam && sleep 0.5')
580
+ job9 = self.submit('echo eggs && sleep 0.5', depends=[job8])
581
+ job10 = self.submit('echo bazbiz && sleep 0.5', depends=[job9])
582
+
583
+ # Display the "user-friendly" pure bash
584
+ self.print_commands()
585
+
586
+ # Display the real bash that gets executed under the hood
587
+ # that is independencly executable, tracks the success / failure of each job,
588
+ # and manages dependencies.
589
+ self.print_commands(1, 1)
590
+
591
+ # Blocking will display a job monitor while it waits for everything to
592
+ # complete
593
+ self.run(block=True)
594
+
595
+
596
+ This prints the sequence of bash commands that will be executed in each tmux session.
597
+
598
+ .. code:: bash
599
+
600
+ # --- /home/joncrall/.cache/base_queue/demo_queue_2022-04-08_a1ef7600/queue_demo_queue_0_2022-04-08_a1ef7600.sh
601
+
602
+ #!/bin/bash
603
+ #
604
+ # Jobs
605
+ #
606
+ ### Command 1 / 3 - demo_queue-job-7
607
+ echo spam && sleep 0.5
608
+ #
609
+ ### Command 2 / 3 - demo_queue-job-8
610
+ echo eggs && sleep 0.5
611
+ #
612
+ ### Command 3 / 3 - demo_queue-job-9
613
+ echo bazbiz && sleep 0.5
614
+
615
+ # --- /home/joncrall/.cache/base_queue/demo_queue_2022-04-08_a1ef7600/queue_demo_queue_1_2022-04-08_a1ef7600.sh
616
+
617
+ #!/bin/bash
618
+ #
619
+ # Jobs
620
+ #
621
+ ### Command 1 / 2 - demo_queue-job-2
622
+ echo foo && sleep 0.5
623
+ #
624
+ ### Command 2 / 2 - demo_queue-job-6
625
+ echo err && false
626
+
627
+ # --- /home/joncrall/.cache/base_queue/demo_queue_2022-04-08_a1ef7600/queue_demo_queue_2_2022-04-08_a1ef7600.sh
628
+
629
+ #!/bin/bash
630
+ #
631
+ # Jobs
632
+ #
633
+ ### Command 1 / 2 - demo_queue-job-0
634
+ echo hello && sleep 0.5
635
+ #
636
+ ### Command 2 / 2 - demo_queue-job-5
637
+ echo spam && sleep 0.5
638
+
639
+ # --- /home/joncrall/.cache/base_queue/demo_queue_2022-04-08_a1ef7600/queue_demo_queue_3_2022-04-08_a1ef7600.sh
640
+
641
+ #!/bin/bash
642
+ #
643
+ # Jobs
644
+ #
645
+ ### Command 1 / 1 - demo_queue-job-3
646
+ echo bar && sleep 0.5
647
+
648
+ # --- /home/joncrall/.cache/base_queue/demo_queue_2022-04-08_a1ef7600/queue_demo_queue_4_2022-04-08_a1ef7600.sh
649
+
650
+ #!/bin/bash
651
+ #
652
+ # Jobs
653
+ #
654
+ ### Command 1 / 1 - demo_queue-job-4
655
+ echo spam && sleep 0.5
656
+
657
+ # --- /home/joncrall/.cache/base_queue/demo_queue_2022-04-08_a1ef7600/queue_demo_queue_5_2022-04-08_a1ef7600.sh
658
+
659
+ #!/bin/bash
660
+ #
661
+ # Jobs
662
+ #
663
+ ### Command 1 / 1 - demo_queue-job-1
664
+ echo world && sleep 0.5
665
+
666
+
667
+
668
+ Slurm mode is the real deal. But you need slurm installed on your machint to
669
+ use it. Asking for tmux is a might ligher weight tool. We can specify slurm
670
+ options here
671
+
672
+ .. code:: python
673
+
674
+ import cmd_queue
675
+ self = cmd_queue.Queue.create(name='demo_queue', backend='slurm')
676
+ job1 = self.submit('echo hello && sleep 0.5', cpus=4, mem='8GB')
677
+ job2 = self.submit('echo world && sleep 0.5', depends=[job1], parition='default')
678
+ job3 = self.submit('echo foo && sleep 0.5')
679
+ job4 = self.submit('echo bar && sleep 0.5')
680
+ job5 = self.submit('echo spam && sleep 0.5', depends=[job1])
681
+ job6 = self.submit('echo spam && sleep 0.5')
682
+ job7 = self.submit('echo err && false')
683
+ job8 = self.submit('echo spam && sleep 0.5')
684
+ job9 = self.submit('echo eggs && sleep 0.5', depends=[job8])
685
+ job10 = self.submit('echo bazbiz && sleep 0.5', depends=[job9])
686
+
687
+ # Display the "user-friendly" pure bash
688
+ self.print_commands()
689
+
690
+ # Display the real bash that gets executed under the hood
691
+ # that is independencly executable, tracks the success / failure of each job,
692
+ # and manages dependencies.
693
+ self.print_commands(1, 1)
694
+
695
+ # Blocking will display a job monitor while it waits for everything to
696
+ # complete
697
+ self.run(block=True)
698
+
699
+
700
+ This prints the very simple slurm submission script:
701
+
702
+ .. code:: bash
703
+
704
+ # --- /home/joncrall/.cache/slurm_queue/demo_queue-20220408T170615-a9e238b5/demo_queue-20220408T170615-a9e238b5.sh
705
+
706
+ mkdir -p "$HOME/.cache/slurm_queue/demo_queue-20220408T170615-a9e238b5/logs"
707
+ JOB_000=$(sbatch --job-name="J0000-demo_queue-20220408T170615-a9e238b5" --cpus-per-task=4 --mem=8000 --output="/home/joncrall/.cache/slurm_queue/demo_queue-20220408T170615-a9e238b5/logs/J0000-demo_queue-20220408T170615-a9e238b5.sh" --wrap 'echo hello && sleep 0.5' --parsable)
708
+ JOB_001=$(sbatch --job-name="J0002-demo_queue-20220408T170615-a9e238b5" --output="/home/joncrall/.cache/slurm_queue/demo_queue-20220408T170615-a9e238b5/logs/J0002-demo_queue-20220408T170615-a9e238b5.sh" --wrap 'echo foo && sleep 0.5' --parsable)
709
+ JOB_002=$(sbatch --job-name="J0003-demo_queue-20220408T170615-a9e238b5" --output="/home/joncrall/.cache/slurm_queue/demo_queue-20220408T170615-a9e238b5/logs/J0003-demo_queue-20220408T170615-a9e238b5.sh" --wrap 'echo bar && sleep 0.5' --parsable)
710
+ JOB_003=$(sbatch --job-name="J0005-demo_queue-20220408T170615-a9e238b5" --output="/home/joncrall/.cache/slurm_queue/demo_queue-20220408T170615-a9e238b5/logs/J0005-demo_queue-20220408T170615-a9e238b5.sh" --wrap 'echo spam && sleep 0.5' --parsable)
711
+ JOB_004=$(sbatch --job-name="J0006-demo_queue-20220408T170615-a9e238b5" --output="/home/joncrall/.cache/slurm_queue/demo_queue-20220408T170615-a9e238b5/logs/J0006-demo_queue-20220408T170615-a9e238b5.sh" --wrap 'echo err && false' --parsable)
712
+ JOB_005=$(sbatch --job-name="J0007-demo_queue-20220408T170615-a9e238b5" --output="/home/joncrall/.cache/slurm_queue/demo_queue-20220408T170615-a9e238b5/logs/J0007-demo_queue-20220408T170615-a9e238b5.sh" --wrap 'echo spam && sleep 0.5' --parsable)
713
+ JOB_006=$(sbatch --job-name="J0001-demo_queue-20220408T170615-a9e238b5" --output="/home/joncrall/.cache/slurm_queue/demo_queue-20220408T170615-a9e238b5/logs/J0001-demo_queue-20220408T170615-a9e238b5.sh" --wrap 'echo world && sleep 0.5' "--dependency=afterok:${JOB_000}" --parsable)
714
+ JOB_007=$(sbatch --job-name="J0004-demo_queue-20220408T170615-a9e238b5" --output="/home/joncrall/.cache/slurm_queue/demo_queue-20220408T170615-a9e238b5/logs/J0004-demo_queue-20220408T170615-a9e238b5.sh" --wrap 'echo spam && sleep 0.5' "--dependency=afterok:${JOB_000}" --parsable)
715
+ JOB_008=$(sbatch --job-name="J0008-demo_queue-20220408T170615-a9e238b5" --output="/home/joncrall/.cache/slurm_queue/demo_queue-20220408T170615-a9e238b5/logs/J0008-demo_queue-20220408T170615-a9e238b5.sh" --wrap 'echo eggs && sleep 0.5' "--dependency=afterok:${JOB_005}" --parsable)
716
+ JOB_009=$(sbatch --job-name="J0009-demo_queue-20220408T170615-a9e238b5" --output="/home/joncrall/.cache/slurm_queue/demo_queue-20220408T170615-a9e238b5/logs/J0009-demo_queue-20220408T170615-a9e238b5.sh" --wrap 'echo bazbiz && sleep 0.5' "--dependency=afterok:${JOB_008}" --parsable)
717
+
718
+
719
+
720
+ .. |Pypi| image:: https://img.shields.io/pypi/v/cmd_queue.svg
721
+ :target: https://pypi.python.org/pypi/cmd_queue
722
+
723
+ .. |Downloads| image:: https://img.shields.io/pypi/dm/cmd_queue.svg
724
+ :target: https://pypistats.org/packages/cmd_queue
725
+
726
+ .. |ReadTheDocs| image:: https://readthedocs.org/projects/cmd-queue/badge/?version=release
727
+ :target: https://cmd-queue.readthedocs.io/en/release/
728
+
729
+ .. # See: https://ci.appveyor.com/project/jon.crall/cmd_queue/settings/badges
730
+ .. |Appveyor| image:: https://ci.appveyor.com/api/projects/status/py3s2d6tyfjc8lm3/branch/main?svg=true
731
+ :target: https://ci.appveyor.com/project/jon.crall/cmd_queue/branch/main
732
+
733
+ .. |GitlabCIPipeline| image:: https://gitlab.kitware.com/computer-vision/cmd_queue/badges/main/pipeline.svg
734
+ :target: https://gitlab.kitware.com/computer-vision/cmd_queue/-/jobs
735
+
736
+ .. |GitlabCICoverage| image:: https://gitlab.kitware.com/computer-vision/cmd_queue/badges/main/coverage.svg?job=coverage
737
+ :target: https://gitlab.kitware.com/computer-vision/cmd_queue/commits/main
738
+
739
+ .. |CircleCI| image:: https://circleci.com/gh/Erotemic/cmd_queue.svg?style=svg
740
+ :target: https://circleci.com/gh/Erotemic/cmd_queue
741
+
742
+ .. |Travis| image:: https://img.shields.io/travis/Erotemic/cmd_queue/main.svg?label=Travis%20CI
743
+ :target: https://travis-ci.org/Erotemic/cmd_queue
744
+
745
+ .. |Codecov| image:: https://codecov.io/github/Erotemic/cmd_queue/badge.svg?branch=main&service=github
746
+ :target: https://codecov.io/github/Erotemic/cmd_queue?branch=main