Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
80e449a
Add monitor manifest module for queue rehydration
Erotemic Apr 30, 2026
bfd6100
Write monitor manifest from run() for tmux and slurm queues
Erotemic Apr 30, 2026
858c776
Add `cmd_queue monitor <qname>` CLI subcommand
Erotemic Apr 30, 2026
02de117
Move post-run cleanup ownership from run() into monitor()
Erotemic Apr 30, 2026
d2f5352
Add monitor='inline'|'tmux'|'none' kwarg to run()
Erotemic Apr 30, 2026
6022168
Add examples/tmux_example.py demonstrating monitor= modes
Erotemic Apr 30, 2026
db9eb5f
Make tmux monitor opt-in with press-key attach prompt
Erotemic Apr 30, 2026
c9cbcaa
Print done summary after headless/tmux monitor completes; expand exam…
Erotemic Apr 30, 2026
1fae48d
Show failed-job log paths in done summary; note when logs are not ena…
Erotemic Apr 30, 2026
642c11d
Inject forced failures into tmux example to exercise the new summary
Erotemic Apr 30, 2026
3e42fd3
Surface failed jobs (with log paths) inside the live monitor view
Erotemic Apr 30, 2026
bc193da
Persist per-job info in monitor manifest so out-of-process monitor se…
Erotemic Apr 30, 2026
dd31e3b
Stop double-marking skipped jobs as failed; render skipped section in…
Erotemic Apr 30, 2026
435ceab
manual example updates
Erotemic Apr 30, 2026
60837a0
Add type and ruff configs
Erotemic Apr 30, 2026
45fa370
Update tests. Drop 3.9 support
Erotemic Apr 30, 2026
7bdf8ac
Update xcookie
Erotemic Apr 30, 2026
85df7c6
Add monitor to boilerplate cli
Erotemic Apr 30, 2026
96bb14c
Use pytest 8+ everywhere
Erotemic Apr 30, 2026
2736398
Fix type errors
Erotemic Apr 30, 2026
b1c3869
Fix type errors
Erotemic Apr 30, 2026
18b2b03
Fix types
Erotemic Apr 30, 2026
96565f0
Ruff format
Erotemic Apr 30, 2026
5355ca0
Ruff check fix
Erotemic Apr 30, 2026
725f3f6
Tweaks
Erotemic Apr 30, 2026
63ddc99
tests: cover Queue.submit log-flag plumbing to BashJob
Erotemic Apr 30, 2026
44c7e93
Add monitor='hybrid' mode: inline UI + attachable tmux side session
Erotemic May 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 94 additions & 94 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,30 +94,36 @@ stages:
- 'echo "Installing helpers: setuptools"'
- python -m uv pip install setuptools>=0.8 setuptools_scm wheel build -U
- 'echo "Installing helpers: tomli and pkginfo"'
- python -m uv pip install tomli pkginfo
- python -m uv pip install tomli pkginfo packaging
- |-
export WHEEL_FPATH=$(python -c "if 1:
import pathlib
from packaging import tags
from packaging.utils import parse_wheel_filename
dist_dpath = pathlib.Path('dist')
candidates = list(dist_dpath.glob('cmd_queue*.whl'))
candidates += list(dist_dpath.glob('cmd_queue*.tar.gz'))
fpath = sorted(candidates)[-1]
wheels = sorted(dist_dpath.glob('cmd_queue*.whl'))
if wheels:
sys_tags = set(tags.sys_tags())
matching = []
for w in wheels:
try:
_, _, _, wheel_tags = parse_wheel_filename(w.name)
except Exception:
continue
if any(t in sys_tags for t in wheel_tags):
matching.append(w)
fpath = sorted(matching or wheels)[-1]
else:
sdists = sorted(dist_dpath.glob('cmd_queue*.tar.gz'))
if not sdists:
raise SystemExit('No wheel artifacts found in wheelhouse')
fpath = sdists[-1]
print(str(fpath).replace(chr(92), chr(47)))
")
- |-
export MOD_VERSION=$(python -c "if 1:
from pkginfo import Wheel, SDist
import pathlib
fpath = '$WHEEL_FPATH'
cls = Wheel if fpath.endswith('.whl') else SDist
item = cls(fpath)
print(item.version)
")
- echo "WHEEL_FPATH=$WHEEL_FPATH"
- echo "INSTALL_EXTRAS=$INSTALL_EXTRAS"
- echo "UV_RESOLUTION=$UV_RESOLUTION"
- echo "MOD_VERSION=$MOD_VERSION"
- python -m pip install --prefer-binary "cmd_queue[$INSTALL_EXTRAS]==$MOD_VERSION" -f dist
- python -m pip install --prefer-binary "${WHEEL_FPATH}[${INSTALL_EXTRAS}]"
- echo "Install finished."
- echo "Creating test sandbox directory"
- export WORKSPACE_DNAME="sandbox"
Expand Down Expand Up @@ -164,30 +170,36 @@ stages:
- 'echo "Installing helpers: setuptools"'
- python -m uv pip install setuptools>=0.8 setuptools_scm wheel build -U
- 'echo "Installing helpers: tomli and pkginfo"'
- python -m uv pip install tomli pkginfo
- python -m uv pip install tomli pkginfo packaging
- |-
export WHEEL_FPATH=$(python -c "if 1:
import pathlib
from packaging import tags
from packaging.utils import parse_wheel_filename
dist_dpath = pathlib.Path('dist')
candidates = list(dist_dpath.glob('cmd_queue*.whl'))
candidates += list(dist_dpath.glob('cmd_queue*.tar.gz'))
fpath = sorted(candidates)[-1]
wheels = sorted(dist_dpath.glob('cmd_queue*.whl'))
if wheels:
sys_tags = set(tags.sys_tags())
matching = []
for w in wheels:
try:
_, _, _, wheel_tags = parse_wheel_filename(w.name)
except Exception:
continue
if any(t in sys_tags for t in wheel_tags):
matching.append(w)
fpath = sorted(matching or wheels)[-1]
else:
sdists = sorted(dist_dpath.glob('cmd_queue*.tar.gz'))
if not sdists:
raise SystemExit('No wheel artifacts found in wheelhouse')
fpath = sdists[-1]
print(str(fpath).replace(chr(92), chr(47)))
")
- |-
export MOD_VERSION=$(python -c "if 1:
from pkginfo import Wheel, SDist
import pathlib
fpath = '$WHEEL_FPATH'
cls = Wheel if fpath.endswith('.whl') else SDist
item = cls(fpath)
print(item.version)
")
- echo "WHEEL_FPATH=$WHEEL_FPATH"
- echo "INSTALL_EXTRAS=$INSTALL_EXTRAS"
- echo "UV_RESOLUTION=$UV_RESOLUTION"
- echo "MOD_VERSION=$MOD_VERSION"
- python -m pip install --prefer-binary "cmd_queue[$INSTALL_EXTRAS]==$MOD_VERSION" -f dist
- python -m pip install --prefer-binary "${WHEEL_FPATH}[${INSTALL_EXTRAS}]"
- echo "Install finished."
- echo "Creating test sandbox directory"
- export WORKSPACE_DNAME="sandbox"
Expand Down Expand Up @@ -234,30 +246,36 @@ stages:
- 'echo "Installing helpers: setuptools"'
- python -m uv pip install setuptools>=0.8 setuptools_scm wheel build -U
- 'echo "Installing helpers: tomli and pkginfo"'
- python -m uv pip install tomli pkginfo
- python -m uv pip install tomli pkginfo packaging
- |-
export WHEEL_FPATH=$(python -c "if 1:
import pathlib
from packaging import tags
from packaging.utils import parse_wheel_filename
dist_dpath = pathlib.Path('dist')
candidates = list(dist_dpath.glob('cmd_queue*.whl'))
candidates += list(dist_dpath.glob('cmd_queue*.tar.gz'))
fpath = sorted(candidates)[-1]
wheels = sorted(dist_dpath.glob('cmd_queue*.whl'))
if wheels:
sys_tags = set(tags.sys_tags())
matching = []
for w in wheels:
try:
_, _, _, wheel_tags = parse_wheel_filename(w.name)
except Exception:
continue
if any(t in sys_tags for t in wheel_tags):
matching.append(w)
fpath = sorted(matching or wheels)[-1]
else:
sdists = sorted(dist_dpath.glob('cmd_queue*.tar.gz'))
if not sdists:
raise SystemExit('No wheel artifacts found in wheelhouse')
fpath = sdists[-1]
print(str(fpath).replace(chr(92), chr(47)))
")
- |-
export MOD_VERSION=$(python -c "if 1:
from pkginfo import Wheel, SDist
import pathlib
fpath = '$WHEEL_FPATH'
cls = Wheel if fpath.endswith('.whl') else SDist
item = cls(fpath)
print(item.version)
")
- echo "WHEEL_FPATH=$WHEEL_FPATH"
- echo "INSTALL_EXTRAS=$INSTALL_EXTRAS"
- echo "UV_RESOLUTION=$UV_RESOLUTION"
- echo "MOD_VERSION=$MOD_VERSION"
- python -m pip install --prefer-binary "cmd_queue[$INSTALL_EXTRAS]==$MOD_VERSION" -f dist
- python -m pip install --prefer-binary "${WHEEL_FPATH}[${INSTALL_EXTRAS}]"
- echo "Install finished."
- echo "Creating test sandbox directory"
- export WORKSPACE_DNAME="sandbox"
Expand Down Expand Up @@ -304,30 +322,36 @@ stages:
- 'echo "Installing helpers: setuptools"'
- python -m uv pip install setuptools>=0.8 setuptools_scm wheel build -U
- 'echo "Installing helpers: tomli and pkginfo"'
- python -m uv pip install tomli pkginfo
- python -m uv pip install tomli pkginfo packaging
- |-
export WHEEL_FPATH=$(python -c "if 1:
import pathlib
from packaging import tags
from packaging.utils import parse_wheel_filename
dist_dpath = pathlib.Path('dist')
candidates = list(dist_dpath.glob('cmd_queue*.whl'))
candidates += list(dist_dpath.glob('cmd_queue*.tar.gz'))
fpath = sorted(candidates)[-1]
wheels = sorted(dist_dpath.glob('cmd_queue*.whl'))
if wheels:
sys_tags = set(tags.sys_tags())
matching = []
for w in wheels:
try:
_, _, _, wheel_tags = parse_wheel_filename(w.name)
except Exception:
continue
if any(t in sys_tags for t in wheel_tags):
matching.append(w)
fpath = sorted(matching or wheels)[-1]
else:
sdists = sorted(dist_dpath.glob('cmd_queue*.tar.gz'))
if not sdists:
raise SystemExit('No wheel artifacts found in wheelhouse')
fpath = sdists[-1]
print(str(fpath).replace(chr(92), chr(47)))
")
- |-
export MOD_VERSION=$(python -c "if 1:
from pkginfo import Wheel, SDist
import pathlib
fpath = '$WHEEL_FPATH'
cls = Wheel if fpath.endswith('.whl') else SDist
item = cls(fpath)
print(item.version)
")
- echo "WHEEL_FPATH=$WHEEL_FPATH"
- echo "INSTALL_EXTRAS=$INSTALL_EXTRAS"
- echo "UV_RESOLUTION=$UV_RESOLUTION"
- echo "MOD_VERSION=$MOD_VERSION"
- python -m pip install --prefer-binary "cmd_queue[$INSTALL_EXTRAS]==$MOD_VERSION" -f dist
- python -m pip install --prefer-binary "${WHEEL_FPATH}[${INSTALL_EXTRAS}]"
- echo "Install finished."
- echo "Creating test sandbox directory"
- export WORKSPACE_DNAME="sandbox"
Expand Down Expand Up @@ -359,29 +383,6 @@ test/sdist/minimal-loose/cp314-linux-x86_64:
image: python:3.14
needs:
- build/sdist
build/cp39-linux-x86_64:
<<: *build_wheel_template
image: python:3.9
test/minimal-loose/cp39-linux-x86_64:
<<: *test_minimal-loose_template
image: python:3.9
needs:
- build/cp39-linux-x86_64
test/full-loose/cp39-linux-x86_64:
<<: *test_full-loose_template
image: python:3.9
needs:
- build/cp39-linux-x86_64
test/minimal-strict/cp39-linux-x86_64:
<<: *test_minimal-strict_template
image: python:3.9
needs:
- build/cp39-linux-x86_64
test/full-strict/cp39-linux-x86_64:
<<: *test_full-strict_template
image: python:3.9
needs:
- build/cp39-linux-x86_64
build/cp310-linux-x86_64:
<<: *build_wheel_template
image: python:3.10
Expand Down Expand Up @@ -507,6 +508,9 @@ lint:
- python -m pip install pip uv -U
- python -m uv pip install -r requirements/linting.txt
- ./run_linter.sh
- python -m pip install ty
- pip install -r requirements/runtime.txt
- ty check ./cmd_queue
allow_failure: true
gpgsign/wheels:
<<: *common_template
Expand All @@ -531,17 +535,16 @@ gpgsign/wheels:
- export GPG_EXECUTABLE=gpg
- export GPG_KEYID=$(cat dev/public_gpg_key)
- echo "GPG_KEYID = $GPG_KEYID"
# Decrypt and import GPG Keys / trust
# note the variable pointed to by VARNAME_CI_SECRET is a protected variables only available on main and release branch
- source dev/secrets_configuration.sh
- CI_SECRET=${!VARNAME_CI_SECRET}
- $GPG_EXECUTABLE --version
- openssl version
- $GPG_EXECUTABLE --list-keys
# note CI_KITWARE_SECRET is a protected variables only available on main and release branch
- CIS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:CIS -d -a -in dev/ci_public_gpg_key.pgp.enc | $GPG_EXECUTABLE --import
- CIS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:CIS -d -a -in dev/gpg_owner_trust.enc | $GPG_EXECUTABLE --import-ownertrust
- CIS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:CIS -d -a -in dev/ci_secret_gpg_subkeys.pgp.enc | $GPG_EXECUTABLE --import
- echo "Importing GPG keys from CI secrets"
- printf '%s' "$GPG_PUBLIC_KEY_B64" | base64 -d | $GPG_EXECUTABLE --import
- printf '%s' "$GPG_OWNER_TRUST_B64" | base64 -d | $GPG_EXECUTABLE --import-ownertrust
- printf '%s' "$GPG_SECRET_SIGNING_SUBKEY_B64" | base64 -d | $GPG_EXECUTABLE --import
- "IMPORTED_FPR=$($GPG_EXECUTABLE --list-keys --with-colons \"$GPG_KEYID\" | awk -F: '/^fpr/ { print $10; exit }')"
- '[[ "$IMPORTED_FPR" == "$GPG_KEYID" ]] || { echo "ERROR: fingerprint mismatch: $IMPORTED_FPR != $GPG_KEYID"; exit 1; }'
- 'echo "GPG fingerprint verified: $IMPORTED_FPR"'
- GPG_SIGN_CMD="$GPG_EXECUTABLE --batch --yes --detach-sign --armor --local-user $GPG_KEYID"
- |-
WHEEL_PATHS=(dist/*.whl dist/*.tar.gz)
Expand All @@ -562,8 +565,6 @@ gpgsign/wheels:
needs:
- job: build/sdist
artifacts: true
- job: build/cp39-linux-x86_64
artifacts: true
- job: build/cp310-linux-x86_64
artifacts: true
- job: build/cp311-linux-x86_64
Expand Down Expand Up @@ -605,7 +606,6 @@ deploy/wheels:
# do sed twice to handle the case of https clone with and without a read token
URL_HOST=$(git remote get-url origin | sed -e 's|https\?://.*@||g' | sed -e 's|https\?://||g' | sed -e 's|git@||g' | sed -e 's|:|/|g')
source dev/secrets_configuration.sh
CI_SECRET=${!VARNAME_CI_SECRET}
PUSH_TOKEN=${!VARNAME_PUSH_TOKEN}
echo "URL_HOST = $URL_HOST"
# A git config user name and email is required. Set if needed.
Expand Down
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,21 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## Version 0.3.0 - Unreleased

### Added:
* generalized the monitor so it can be launched in an independent process and reports errors better.
* New `monitor='hybrid'` mode (now the default for tmux and slurm `run()`): renders the live status table inline in the current shell and *also* spawns a detached `cmd_queue monitor` tmux session. Press `[a]` from the inline UI to attach (or `switch-client` when already inside tmux), `[q]` to stop watching while the queue keeps running. The side session is killed when the inline monitor exits.

### Changed
* `monitor` kwarg accepted values are now `'hybrid' | 'inline' | 'tmux' | 'none'`. `'inline'` reverts to its original pure-current-shell meaning; the `'hybrid'` mode covers the inline+tmux combination. The default is `'hybrid'`, so a no-arg `run()` now spawns an attachable tmux side session whenever tmux is available.

### Fixed:
* cwd will now handle failures if the directory doesnt exist in the bash queue
* general improvements to bash script construction with per-job preamble commands
* slurm now correctly respects header/preamble commands

### Changed
* deprecate `header_commands` for `preamble`
* Dropped support for 3.8
* Dropped support for 3.8 and 3.9
* Transition from stubs to type annotations.


Expand Down
5 changes: 3 additions & 2 deletions cmd_queue/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,8 @@
'base_queue': ['Queue'],
}
from cmd_queue import base_queue

from cmd_queue.base_queue import (Queue,)
from cmd_queue.base_queue import (
Queue,
)

__all__ = ['Queue', 'base_queue']
Loading