Metadata-Version: 2.1
Name: cmd-queue
Version: 0.1.2
Summary: The cmd_queue module for a DAG of bash commands
Home-page: https://gitlab.kitware.com/computer-vision/cmd_queue
Author: Kitware Inc., Jon Crall
Author-email: kitware@kitware.com, jon.crall@kitware.com
License: Apache 2
Platform: UNKNOWN
Classifier: Development Status :: 1 - Planning
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Utilities
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Requires-Python: >=3.6
Description-Content-Type: text/x-rst
Requires-Dist: ubelt
Requires-Dist: rich
Requires-Dist: psutil
Requires-Dist: numpy ; python_version < "3.10" and python_version >= "3.6.0"
Requires-Dist: pandas ; python_version < "3.10" and python_version >= "3.9"
Requires-Dist: pandas ; python_version < "3.6.1" and python_version >= "3.6"
Requires-Dist: pint ; python_version < "3.7" and python_version >= "3.6"
Requires-Dist: pandas ; python_version < "3.7" and python_version >= "3.6.1"
Requires-Dist: networkx ; python_version < "3.7.0" and python_version >= "3.6.0"
Requires-Dist: pandas ; python_version < "3.7.1" and python_version >= "3.7"
Requires-Dist: networkx ; python_version < "3.8" and python_version >= "3.7"
Requires-Dist: pandas ; python_version < "3.8" and python_version >= "3.7.1"
Requires-Dist: pandas ; python_version < "3.9" and python_version >= "3.8"
Requires-Dist: numpy ; python_version >= "3.10"
Requires-Dist: pandas ; python_version >= "3.10"
Requires-Dist: pint ; python_version >= "3.7"
Requires-Dist: networkx ; python_version >= "3.8"
Provides-Extra: all
Requires-Dist: ubelt ; extra == 'all'
Requires-Dist: rich ; extra == 'all'
Requires-Dist: psutil ; extra == 'all'
Requires-Dist: xdoctest ; extra == 'all'
Requires-Dist: textual ; extra == 'all'
Provides-Extra: all-strict
Requires-Dist: ubelt (==1.1.2) ; extra == 'all-strict'
Requires-Dist: rich (==12.5.1) ; extra == 'all-strict'
Requires-Dist: psutil (==5.9.1) ; extra == 'all-strict'
Requires-Dist: xdoctest (==1.0.1) ; extra == 'all-strict'
Requires-Dist: textual (==0.1.18) ; extra == 'all-strict'
Requires-Dist: numpy (==1.19.3) ; (python_version < "3.10" and python_version >= "3.6.0") and extra == 'all-strict'
Requires-Dist: pandas (==1.4.0) ; (python_version < "3.10" and python_version >= "3.9") and extra == 'all-strict'
Requires-Dist: coverage (==5.3.1) ; (python_version < "3.10" and python_version >= "3.9") and extra == 'all-strict'
Requires-Dist: pandas (==1.1.4) ; (python_version < "3.6.1" and python_version >= "3.6") and extra == 'all-strict'
Requires-Dist: pint (==0.10) ; (python_version < "3.7" and python_version >= "3.6") and extra == 'all-strict'
Requires-Dist: pytest (==6.2.0) ; (python_version < "3.7" and python_version >= "3.6") and extra == 'all-strict'
Requires-Dist: coverage (==6.1.1) ; (python_version < "3.7" and python_version >= "3.6") and extra == 'all-strict'
Requires-Dist: pandas (==1.1.4) ; (python_version < "3.7" and python_version >= "3.6.1") and extra == 'all-strict'
Requires-Dist: networkx (<=2.5.1,==2.5.1) ; (python_version < "3.7.0" and python_version >= "3.6.0") and extra == 'all-strict'
Requires-Dist: pandas (==1.1.4) ; (python_version < "3.7.1" and python_version >= "3.7") and extra == 'all-strict'
Requires-Dist: networkx (==2.6.2) ; (python_version < "3.8" and python_version >= "3.7") and extra == 'all-strict'
Requires-Dist: coverage (==6.1.1) ; (python_version < "3.8" and python_version >= "3.7") and extra == 'all-strict'
Requires-Dist: pandas (==1.2.0) ; (python_version < "3.8" and python_version >= "3.7.1") and extra == 'all-strict'
Requires-Dist: pandas (==1.4.0) ; (python_version < "3.9" and python_version >= "3.8") and extra == 'all-strict'
Requires-Dist: coverage (==6.1.1) ; (python_version < "3.9" and python_version >= "3.8") and extra == 'all-strict'
Requires-Dist: numpy (==1.21.6) ; (python_version >= "3.10") and extra == 'all-strict'
Requires-Dist: pandas (==1.3.5) ; (python_version >= "3.10") and extra == 'all-strict'
Requires-Dist: coverage (==6.1.1) ; (python_version >= "3.10") and extra == 'all-strict'
Requires-Dist: pytest-cov (==3.0.0) ; (python_version >= "3.6.0") and extra == 'all-strict'
Requires-Dist: pint (==0.18) ; (python_version >= "3.7") and extra == 'all-strict'
Requires-Dist: pytest (==7.1.0) ; (python_version >= "3.7") and extra == 'all-strict'
Requires-Dist: networkx (==2.7) ; (python_version >= "3.8") and extra == 'all-strict'
Requires-Dist: numpy ; (python_version < "3.10" and python_version >= "3.6.0") and extra == 'all'
Requires-Dist: pandas ; (python_version < "3.10" and python_version >= "3.9") and extra == 'all'
Requires-Dist: coverage ; (python_version < "3.10" and python_version >= "3.9") and extra == 'all'
Requires-Dist: pandas ; (python_version < "3.6.1" and python_version >= "3.6") and extra == 'all'
Requires-Dist: pint ; (python_version < "3.7" and python_version >= "3.6") and extra == 'all'
Requires-Dist: pytest ; (python_version < "3.7" and python_version >= "3.6") and extra == 'all'
Requires-Dist: coverage ; (python_version < "3.7" and python_version >= "3.6") and extra == 'all'
Requires-Dist: pandas ; (python_version < "3.7" and python_version >= "3.6.1") and extra == 'all'
Requires-Dist: networkx ; (python_version < "3.7.0" and python_version >= "3.6.0") and extra == 'all'
Requires-Dist: pandas ; (python_version < "3.7.1" and python_version >= "3.7") and extra == 'all'
Requires-Dist: networkx ; (python_version < "3.8" and python_version >= "3.7") and extra == 'all'
Requires-Dist: coverage ; (python_version < "3.8" and python_version >= "3.7") and extra == 'all'
Requires-Dist: pandas ; (python_version < "3.8" and python_version >= "3.7.1") and extra == 'all'
Requires-Dist: pandas ; (python_version < "3.9" and python_version >= "3.8") and extra == 'all'
Requires-Dist: coverage ; (python_version < "3.9" and python_version >= "3.8") and extra == 'all'
Requires-Dist: numpy ; (python_version >= "3.10") and extra == 'all'
Requires-Dist: pandas ; (python_version >= "3.10") and extra == 'all'
Requires-Dist: coverage ; (python_version >= "3.10") and extra == 'all'
Requires-Dist: pytest-cov ; (python_version >= "3.6.0") and extra == 'all'
Requires-Dist: pint ; (python_version >= "3.7") and extra == 'all'
Requires-Dist: pytest ; (python_version >= "3.7") and extra == 'all'
Requires-Dist: networkx ; (python_version >= "3.8") and extra == 'all'
Provides-Extra: optional
Requires-Dist: textual ; extra == 'optional'
Provides-Extra: optional-strict
Requires-Dist: textual (==0.1.18) ; extra == 'optional-strict'
Provides-Extra: runtime-strict
Requires-Dist: ubelt (==1.1.2) ; extra == 'runtime-strict'
Requires-Dist: rich (==12.5.1) ; extra == 'runtime-strict'
Requires-Dist: psutil (==5.9.1) ; extra == 'runtime-strict'
Requires-Dist: numpy (==1.19.3) ; (python_version < "3.10" and python_version >= "3.6.0") and extra == 'runtime-strict'
Requires-Dist: pandas (==1.4.0) ; (python_version < "3.10" and python_version >= "3.9") and extra == 'runtime-strict'
Requires-Dist: pandas (==1.1.4) ; (python_version < "3.6.1" and python_version >= "3.6") and extra == 'runtime-strict'
Requires-Dist: pint (==0.10) ; (python_version < "3.7" and python_version >= "3.6") and extra == 'runtime-strict'
Requires-Dist: pandas (==1.1.4) ; (python_version < "3.7" and python_version >= "3.6.1") and extra == 'runtime-strict'
Requires-Dist: networkx (<=2.5.1,==2.5.1) ; (python_version < "3.7.0" and python_version >= "3.6.0") and extra == 'runtime-strict'
Requires-Dist: pandas (==1.1.4) ; (python_version < "3.7.1" and python_version >= "3.7") and extra == 'runtime-strict'
Requires-Dist: networkx (==2.6.2) ; (python_version < "3.8" and python_version >= "3.7") and extra == 'runtime-strict'
Requires-Dist: pandas (==1.2.0) ; (python_version < "3.8" and python_version >= "3.7.1") and extra == 'runtime-strict'
Requires-Dist: pandas (==1.4.0) ; (python_version < "3.9" and python_version >= "3.8") and extra == 'runtime-strict'
Requires-Dist: numpy (==1.21.6) ; (python_version >= "3.10") and extra == 'runtime-strict'
Requires-Dist: pandas (==1.3.5) ; (python_version >= "3.10") and extra == 'runtime-strict'
Requires-Dist: pint (==0.18) ; (python_version >= "3.7") and extra == 'runtime-strict'
Requires-Dist: networkx (==2.7) ; (python_version >= "3.8") and extra == 'runtime-strict'
Provides-Extra: tests
Requires-Dist: xdoctest ; extra == 'tests'
Provides-Extra: tests-strict
Requires-Dist: xdoctest (==1.0.1) ; extra == 'tests-strict'
Requires-Dist: coverage (==5.3.1) ; (python_version < "3.10" and python_version >= "3.9") and extra == 'tests-strict'
Requires-Dist: pytest (==6.2.0) ; (python_version < "3.7" and python_version >= "3.6") and extra == 'tests-strict'
Requires-Dist: coverage (==6.1.1) ; (python_version < "3.7" and python_version >= "3.6") and extra == 'tests-strict'
Requires-Dist: coverage (==6.1.1) ; (python_version < "3.8" and python_version >= "3.7") and extra == 'tests-strict'
Requires-Dist: coverage (==6.1.1) ; (python_version < "3.9" and python_version >= "3.8") and extra == 'tests-strict'
Requires-Dist: coverage (==6.1.1) ; (python_version >= "3.10") and extra == 'tests-strict'
Requires-Dist: pytest-cov (==3.0.0) ; (python_version >= "3.6.0") and extra == 'tests-strict'
Requires-Dist: pytest (==7.1.0) ; (python_version >= "3.7") and extra == 'tests-strict'
Requires-Dist: coverage ; (python_version < "3.10" and python_version >= "3.9") and extra == 'tests'
Requires-Dist: pytest ; (python_version < "3.7" and python_version >= "3.6") and extra == 'tests'
Requires-Dist: coverage ; (python_version < "3.7" and python_version >= "3.6") and extra == 'tests'
Requires-Dist: coverage ; (python_version < "3.8" and python_version >= "3.7") and extra == 'tests'
Requires-Dist: coverage ; (python_version < "3.9" and python_version >= "3.8") and extra == 'tests'
Requires-Dist: coverage ; (python_version >= "3.10") and extra == 'tests'
Requires-Dist: pytest-cov ; (python_version >= "3.6.0") and extra == 'tests'
Requires-Dist: pytest ; (python_version >= "3.7") and extra == 'tests'

Command Queue - cmd_queue
=========================

.. .. |GitlabCIPipeline| |GitlabCICoverage| |Appveyor| |Codecov| 

|Pypi| |Downloads| |ReadTheDocs|


.. The ``cmd_queue`` module.

+------------------+-------------------------------------------------------------------------------------+
| Read the docs    | https://cmd_queue.readthedocs.io                                                    |
+------------------+-------------------------------------------------------------------------------------+
| Github           | https://github.com/Erotemic/cmd_queue                                               |
+------------------+-------------------------------------------------------------------------------------+
| Pypi             | https://pypi.org/project/cmd_queue                                                  |
+------------------+-------------------------------------------------------------------------------------+
| Slides           | https://docs.google.com/presentation/d/1BjJkjMx6bxu1uek-hAGpwj760u9rraVn7st8J5OsZME |
+------------------+-------------------------------------------------------------------------------------+


This is a simple module for "generating" a bash script that schedules multiples
jobs (in parallel if possible) on a single machine. There are 3 backends with
increasing levels of complexity: serial, tmux, and slurm.

In serial mode, a single bash script gets written that executes your jobs in
sequence. There are no external dependencies 

In tmux mode, multiple tmux sessions get opened and each of them executes your
independent parts of your jobs. Dependencies are handled.

In slurm mode, a real heavy-weight scheduling algorithm is used. In this mode
we simply convert your jobs to slurm commands and execute them. 

Under the hood we build a DAG based on your specified dependencies and use this
to appropriately order jobs.

By default, bash scripts that would execute your jobs print to the console.
This gives the user fine-grained control if they only want to run a subset of a
pipeline manually. But if asked to run, cmd_queue will execute the bash jobs.



Modivation
==========
Recently, I needed to run several jobs on 4 jobs across 2 GPUs and then execute
a script after all of them were done. What I should have done was use slurm or
some other proper queuing system to schedule the jobs, but instead I wrote my
own hacky scheduler using tmux. I opened N (number of parallel workers) tmux
sessions and then I ran independent jobs in each different sessions.

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.

Eventually I did get slurm on my machine, and I abstracted the API of my
tmux_queue to be a general "command queue" that can use 1 of 3 backends:
serial, tmux, or slurm.


Niche
=====
There are many DAG schedulers out there:

 * airflow
 * luigi
 * submitit
 * rq_scheduler


The the niche for this is when you have large pipelines of bash commands that
depend on each other and you want to template out those parameters with logic
that you define in Python.

We plan on adding an airflow backend.


Examples
========


All of the dependency checking and book keeping logic is handled in bash
itself. Write (or better yet template) your bash scripts in Python, and then
use cmd_queue to "transpile" these sequences of commands to pure bash.


.. code:: python

   import cmd_queue
   self = cmd_queue.Queue.create(name='demo_queue', backend='serial')
   job1 = self.submit('echo hello && sleep 0.5')
   job2 = self.submit('echo world && sleep 0.5', depends=[job1])
   job3 = self.submit('echo foo && sleep 0.5')
   job4 = self.submit('echo bar && sleep 0.5')
   job5 = self.submit('echo spam && sleep 0.5', depends=[job1])
   job6 = self.submit('echo spam && sleep 0.5')
   job7 = self.submit('echo err && false')
   job8 = self.submit('echo spam && sleep 0.5')
   job9 = self.submit('echo eggs && sleep 0.5', depends=[job8])
   job10 = self.submit('echo bazbiz && sleep 0.5', depends=[job9])

   # Display the "user-friendly" pure bash
   self.rprint()

   # Display the real bash that gets executed under the hood
   # that is independencly executable, tracks the success / failure of each job, 
   # and manages dependencies.
   self.rprint(1, 1)

   # Blocking will display a job monitor while it waits for everything to
   # complete
   self.run(block=True)


This prints the bash commands in an appropriate order to resolve dependencies. 


.. code:: bash

    # --- /home/joncrall/.cache/base_queue/demo_queue_2022-04-08_cc9d551e/demo_queue_2022-04-08_cc9d551e.sh

    #!/bin/bash
    #
    # Jobs
    #
    ### Command 1 / 10 - demo_queue-job-0
    echo hello && sleep 0.5
    #
    ### Command 2 / 10 - demo_queue-job-1
    echo world && sleep 0.5
    #
    ### Command 3 / 10 - demo_queue-job-2
    echo foo && sleep 0.5
    #
    ### Command 4 / 10 - demo_queue-job-3
    echo bar && sleep 0.5
    #
    ### Command 5 / 10 - demo_queue-job-4
    echo spam && sleep 0.5
    #
    ### Command 6 / 10 - demo_queue-job-5
    echo spam && sleep 0.5
    #
    ### Command 7 / 10 - demo_queue-job-6
    echo err && false
    #
    ### Command 8 / 10 - demo_queue-job-7
    echo spam && sleep 0.5
    #
    ### Command 9 / 10 - demo_queue-job-8
    echo eggs && sleep 0.5
    #
    ### Command 10 / 10 - demo_queue-job-9
    echo bazbiz && sleep 0.5
       


.. code:: python

   # Need to tell the tmux queue how many processes can run at the same time
   import cmd_queue
   self = cmd_queue.Queue.create(size=4, name='demo_queue', backend='tmux')
   job1 = self.submit('echo hello && sleep 0.5')
   job2 = self.submit('echo world && sleep 0.5', depends=[job1])
   job3 = self.submit('echo foo && sleep 0.5')
   job4 = self.submit('echo bar && sleep 0.5')
   job5 = self.submit('echo spam && sleep 0.5', depends=[job1])
   job6 = self.submit('echo spam && sleep 0.5')
   job7 = self.submit('echo err && false')
   job8 = self.submit('echo spam && sleep 0.5')
   job9 = self.submit('echo eggs && sleep 0.5', depends=[job8])
   job10 = self.submit('echo bazbiz && sleep 0.5', depends=[job9])

   # Display the "user-friendly" pure bash
   self.rprint()

   # Display the real bash that gets executed under the hood
   # that is independencly executable, tracks the success / failure of each job, 
   # and manages dependencies.
   self.rprint(1, 1)

   # Blocking will display a job monitor while it waits for everything to
   # complete
   self.run(block=True)


This prints the sequence of bash commands that will be executed in each tmux session. 
 
.. code:: bash

    # --- /home/joncrall/.cache/base_queue/demo_queue_2022-04-08_a1ef7600/queue_demo_queue_0_2022-04-08_a1ef7600.sh

    #!/bin/bash
    #
    # Jobs
    #
    ### Command 1 / 3 - demo_queue-job-7
    echo spam && sleep 0.5
    #
    ### Command 2 / 3 - demo_queue-job-8
    echo eggs && sleep 0.5
    #
    ### Command 3 / 3 - demo_queue-job-9
    echo bazbiz && sleep 0.5

    # --- /home/joncrall/.cache/base_queue/demo_queue_2022-04-08_a1ef7600/queue_demo_queue_1_2022-04-08_a1ef7600.sh

    #!/bin/bash
    #
    # Jobs
    #
    ### Command 1 / 2 - demo_queue-job-2
    echo foo && sleep 0.5
    #
    ### Command 2 / 2 - demo_queue-job-6
    echo err && false

    # --- /home/joncrall/.cache/base_queue/demo_queue_2022-04-08_a1ef7600/queue_demo_queue_2_2022-04-08_a1ef7600.sh

    #!/bin/bash
    #
    # Jobs
    #
    ### Command 1 / 2 - demo_queue-job-0
    echo hello && sleep 0.5
    #
    ### Command 2 / 2 - demo_queue-job-5
    echo spam && sleep 0.5

    # --- /home/joncrall/.cache/base_queue/demo_queue_2022-04-08_a1ef7600/queue_demo_queue_3_2022-04-08_a1ef7600.sh

    #!/bin/bash
    #
    # Jobs
    #
    ### Command 1 / 1 - demo_queue-job-3
    echo bar && sleep 0.5

    # --- /home/joncrall/.cache/base_queue/demo_queue_2022-04-08_a1ef7600/queue_demo_queue_4_2022-04-08_a1ef7600.sh

    #!/bin/bash
    #
    # Jobs
    #
    ### Command 1 / 1 - demo_queue-job-4
    echo spam && sleep 0.5

    # --- /home/joncrall/.cache/base_queue/demo_queue_2022-04-08_a1ef7600/queue_demo_queue_5_2022-04-08_a1ef7600.sh

    #!/bin/bash
    #
    # Jobs
    #
    ### Command 1 / 1 - demo_queue-job-1
    echo world && sleep 0.5



Slurm mode is the real deal. But you need slurm installed on your machint to
use it. Asking for tmux is a might ligher weight tool. We can specify slurm
options here

.. code:: python

   import cmd_queue
   self = cmd_queue.Queue.create(name='demo_queue', backend='slurm')
   job1 = self.submit('echo hello && sleep 0.5', cpus=4, mem='8GB')
   job2 = self.submit('echo world && sleep 0.5', depends=[job1], parition='default')
   job3 = self.submit('echo foo && sleep 0.5')
   job4 = self.submit('echo bar && sleep 0.5')
   job5 = self.submit('echo spam && sleep 0.5', depends=[job1])
   job6 = self.submit('echo spam && sleep 0.5')
   job7 = self.submit('echo err && false')
   job8 = self.submit('echo spam && sleep 0.5')
   job9 = self.submit('echo eggs && sleep 0.5', depends=[job8])
   job10 = self.submit('echo bazbiz && sleep 0.5', depends=[job9])

   # Display the "user-friendly" pure bash
   self.rprint()

   # Display the real bash that gets executed under the hood
   # that is independencly executable, tracks the success / failure of each job, 
   # and manages dependencies.
   self.rprint(1, 1)

   # Blocking will display a job monitor while it waits for everything to
   # complete
   self.run(block=True)


This prints the very simple slurm submission script:
 
.. code:: bash

    # --- /home/joncrall/.cache/slurm_queue/demo_queue-20220408T170615-a9e238b5/demo_queue-20220408T170615-a9e238b5.sh

    mkdir -p "$HOME/.cache/slurm_queue/demo_queue-20220408T170615-a9e238b5/logs"
    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)
    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)
    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)
    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)
    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)
    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)
    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)
    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)
    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)
    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)



Installation
============
This will be on pypi once it is cleaned up, but for now:

python -m pip install git+https://gitlab.kitware.com/computer-vision/cmd_queue.git@main


   


.. |Pypi| image:: https://img.shields.io/pypi/v/cmd_queue.svg
   :target: https://pypi.python.org/pypi/cmd_queue

.. |Downloads| image:: https://img.shields.io/pypi/dm/cmd_queue.svg
   :target: https://pypistats.org/packages/cmd_queue

.. |ReadTheDocs| image:: https://readthedocs.org/projects/cmd_queue/badge/?version=release
    :target: https://cmd_queue.readthedocs.io/en/release/

.. # See: https://ci.appveyor.com/project/jon.crall/cmd_queue/settings/badges
.. |Appveyor| image:: https://ci.appveyor.com/api/projects/status/py3s2d6tyfjc8lm3/branch/master?svg=true
   :target: https://ci.appveyor.com/project/jon.crall/cmd_queue/branch/master

.. |GitlabCIPipeline| image:: https://gitlab.kitware.com/utils/cmd_queue/badges/master/pipeline.svg
   :target: https://gitlab.kitware.com/utils/cmd_queue/-/jobs

.. |GitlabCICoverage| image:: https://gitlab.kitware.com/utils/cmd_queue/badges/master/coverage.svg?job=coverage
    :target: https://gitlab.kitware.com/utils/cmd_queue/commits/master

.. |CircleCI| image:: https://circleci.com/gh/Erotemic/cmd_queue.svg?style=svg
    :target: https://circleci.com/gh/Erotemic/cmd_queue

.. |Travis| image:: https://img.shields.io/travis/Erotemic/cmd_queue/master.svg?label=Travis%20CI
   :target: https://travis-ci.org/Erotemic/cmd_queue

.. |Codecov| image:: https://codecov.io/github/Erotemic/cmd_queue/badge.svg?branch=master&service=github
   :target: https://codecov.io/github/Erotemic/cmd_queue?branch=master


