calkit-python 0.41.11__tar.gz → 0.41.13__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.
- {calkit_python-0.41.11 → calkit_python-0.41.13}/PKG-INFO +1 -1
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/cli/list.py +94 -28
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/cli/main/core.py +5 -3
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/cli/scheduler.py +231 -31
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/cloud.py +29 -12
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/detect.py +433 -75
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/dvc/core.py +67 -2
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/fs.py +78 -40
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/cli/main/test_core.py +3 -2
- calkit_python-0.41.13/calkit/tests/cli/test_scheduler.py +404 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/dvc/test_core.py +69 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/test_cloud.py +96 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/test_detect.py +116 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/test_fs.py +121 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/cli-reference.md +16 -2
- {calkit_python-0.41.11 → calkit_python-0.41.13}/vscode-ext/package.json +34 -1
- {calkit_python-0.41.11 → calkit_python-0.41.13}/vscode-ext/src/extension.ts +198 -152
- {calkit_python-0.41.11 → calkit_python-0.41.13}/vscode-ext/src/sidebar.ts +5 -0
- calkit_python-0.41.11/calkit/tests/cli/test_scheduler.py +0 -201
- {calkit_python-0.41.11 → calkit_python-0.41.13}/.claude-plugin/marketplace.json +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/.gitattributes +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/.gitignore +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/.pre-commit-config.yaml +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/.prettierignore +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/.python-version +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/.vscode/launch.json +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/.vscode/tasks.json +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/.yarnrc.yml +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/AGENTS.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/CITATION.cff +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/CODE_OF_CONDUCT.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/CONTRIBUTING.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/LICENSE +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/Makefile +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/README.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/agent-plugin/.claude-plugin/plugin.json +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/agent-plugin/skills/add-pipeline-stage/SKILL.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/agent-plugin/skills/conventions/SKILL.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/agent-plugin/skills/create-pipeline/SKILL.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/babel.config.js +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/__init__.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/__main__.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/calc.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/check.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/cli/__init__.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/cli/check.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/cli/cloud.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/cli/config.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/cli/core.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/cli/describe.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/cli/dev.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/cli/import_.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/cli/latex.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/cli/main/__init__.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/cli/main/xr.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/cli/new.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/cli/notebooks.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/cli/office.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/cli/overleaf.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/cli/update.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/conda.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/config.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/core.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/datasets.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/dependencies.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/docker.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/dvc/__init__.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/dvc/zip.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/environments.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/git.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/github.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/gui.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/install.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/invenio.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/julia.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/jupyter.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/jupyterlab/__init__.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/jupyterlab/routes.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/labextension/package.json +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/labextension/schemas/calkit/package.json.orig +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/labextension/schemas/calkit/plugin.json +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/labextension/static/502.9a2c5772a15466e923ef.js +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/labextension/static/695.2c41003a452d43d2b358.js +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/labextension/static/867.a42a046aa5108f54f8fb.js +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/labextension/static/909.e3f9cc3408834a7fdcc3.js +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/labextension/static/946.050af2abf7845cfbdbd2.js +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/labextension/static/946.050af2abf7845cfbdbd2.js.LICENSE.txt +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/labextension/static/b2f1c3efe70cb539d121.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/labextension/static/remoteEntry.65469af996e7a96aa983.js +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/labextension/static/style.js +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/labextension/static/third-party-licenses.json +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/licenses.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/magics.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/matlab.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/models/__init__.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/models/core.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/models/io.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/models/iteration.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/models/pipeline.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/notebooks.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/office.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/ops.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/overleaf.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/pipeline.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/releases.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/server.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/templates/__init__.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/templates/core.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/templates/latex/__init__.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/templates/latex/article/paper.tex +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/templates/latex/core.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/templates/latex/jfm/jfm.bst +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/templates/latex/jfm/jfm.cls +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/templates/latex/jfm/lineno-FLM.sty +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/templates/latex/jfm/paper.tex +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/templates/latex/jfm/upmath.sty +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/__init__.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/cli/__init__.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/cli/main/__init__.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/cli/main/test_subprojects.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/cli/main/test_xr.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/cli/test_check.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/cli/test_cloud.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/cli/test_config.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/cli/test_import.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/cli/test_latex.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/cli/test_list.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/cli/test_new.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/cli/test_notebooks.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/cli/test_overleaf.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/cli/test_update.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/dvc/__init__.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/dvc/test_zip.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/jupyterlab/__init__.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/jupyterlab/test_routes.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/models/__init__.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/models/test_iteration.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/models/test_pipeline.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/test_calc.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/test_check.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/test_conda.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/test_core.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/test_dependencies.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/test_docker.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/test_environments.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/test_git.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/test_install.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/test_invenio.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/test_julia.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/test_jupyter.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/test_licenses.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/test_magics.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/test_matlab.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/test_notebooks.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/test_pipeline.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/test_releases.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/calkit/tests/test_templates.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/conftest.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/CNAME +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/ai-tools.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/apps.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/calculations.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/calkit-yaml.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/cloud-integration.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/datasets.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/dependencies.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/environments.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/examples.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/governance.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/help.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/hpc.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/img/c-to-the-k-white.svg +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/img/calkit-fragmentation-compendium.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/img/calkit-no-bg.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/img/connect-zenodo.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/img/jupyterlab/all-green.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/img/jupyterlab/collect-data-stale.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/img/jupyterlab/new-env.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/img/jupyterlab/new-notebook.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/img/jupyterlab/pipeline-badge.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/img/jupyterlab-params.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/img/plos-osi-code-2024-03.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/img/vscode-nb-params.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/index.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/installation.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/jupyterlab.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/local-server.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/notebooks.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/overleaf.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/pipeline/index.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/pipeline/manual-steps.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/pipeline/running-and-logging.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/quickstart.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/references.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/releases.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/reproducibility.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/subprojects.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/adding-latex-pub-docker.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/ai-agents.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/conda-envs.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/existing-project.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/first-project.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/github-actions.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/actions-repo-secrets.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/latex-codespaces/building-codespace.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/latex-codespaces/codespaces-secrets-2.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/latex-codespaces/editor-split.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/latex-codespaces/go-to-linked-code.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/latex-codespaces/issue-from-selection.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/latex-codespaces/new-project.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/latex-codespaces/new-pub-2.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/latex-codespaces/new-token.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/latex-codespaces/paper.tex.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/latex-codespaces/project-home-3.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/latex-codespaces/push.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/latex-codespaces/stage.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/office/anakin-excel.jpg +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/office/chart-more-rows.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/office/create-project.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/office/elsevier-research-data-guidelines.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/office/excel-chart.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/office/excel-data.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/office/insert-link-to-file.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/office/needs-clone.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/office/new-stage.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/office/phd-comics-version-control.webp +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/office/pipeline-out-of-date.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/office/status-more-rows.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/office/uncommitted-changes.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/office/untracked-data.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/office/updated-publication.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/office/word-to-pdf-stage-2.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/office/workflow-page.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/openfoam/clone.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/openfoam/create-project.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/openfoam/datasets-page.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/openfoam/figure-on-website-updated.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/openfoam/figure-on-website.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/openfoam/new-token.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/openfoam/reclone.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/openfoam/status-after-import-dataset.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/quick-actions.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/run-proc.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/vscode-slurm-notebook/create-calkit-env.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/vscode-slurm-notebook/create-inner-env.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/vscode-slurm-notebook/create-new-calkit-env.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/vscode-slurm-notebook/select-calkit-env.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/vscode-slurm-notebook/slurm-job-running.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/vscode-slurm-notebook/slurm-launch-options.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/img/vscode-slurm-notebook/starting-slurm-job.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/index.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/jupyterlab.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/latex-codespaces.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/matlab.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/notebook-pipeline.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/office.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/openfoam.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/procedures.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/tutorials/vscode-slurm-notebook.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/docs/version-control.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/flake.lock +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/flake.nix +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/install.json +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/jest.config.js +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/jupyter-config/server-config/calkit.json +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/mkdocs.yml +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/package.json +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/pyproject.toml +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/schema/plugin.json +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/scripts/generate-docs-references.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/scripts/install.ps1 +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/scripts/install.sh +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/scripts/make-calk9.sh +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/scripts/sync-docs.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/src/__tests__/useQueries.spec.ts +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/src/calkit-config.ts +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/src/cell-output-marker.ts +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/src/components/commit-dialog.tsx +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/src/components/environment-editor.tsx +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/src/components/notebook-registration.tsx +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/src/components/notebook-toolbar.tsx +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/src/components/pipeline-status-bar.tsx +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/src/components/project-info-editor.tsx +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/src/components/sidebar-settings.tsx +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/src/components/sidebar.tsx +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/src/components/stage-editor.tsx +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/src/feature-flags.ts +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/src/file-browser-menu.ts +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/src/hooks/__tests__/useQueries.test.tsx +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/src/hooks/useQueries.ts +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/src/icons.ts +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/src/index.ts +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/src/io-tracker.ts +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/src/pipeline-state.ts +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/src/queryClient.ts +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/src/request.ts +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/src/shims-mainmenu.d.ts +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/style/base.css +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/style/cell-output-marker.css +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/style/environment-editor.css +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/style/environment-selector.css +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/style/img/calkit-no-bg.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/style/index.css +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/style/index.js +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/style/notebook-toolbar.css +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/style/pipeline-status-bar.css +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/style/sidebar.css +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/test/dvc-md5-dir/osx-arm64.txt +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/test/nb-julia.ipynb +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/test/nb-params.ipynb +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/test/nb-subdir.ipynb +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/test/pipeline.ipynb +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/test/script.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/test/test-log.log +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/tsconfig.json +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/tsconfig.test.json +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/ui-tests/.gitignore +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/ui-tests/README.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/ui-tests/jupyter_server_test_config.py +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/ui-tests/package.json +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/ui-tests/playwright.config.js +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/ui-tests/tests/calkit.spec.ts +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/ui-tests/yarn.lock +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/uv.lock +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/vscode-ext/.gitignore +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/vscode-ext/.vscodeignore +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/vscode-ext/CHANGELOG.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/vscode-ext/LICENSE +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/vscode-ext/README.md +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/vscode-ext/images/calkit-icon.svg +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/vscode-ext/images/calkit-no-bg.png +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/vscode-ext/package-lock.json +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/vscode-ext/scripts/set-proposed-api.js +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/vscode-ext/src/environments.ts +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/vscode-ext/src/notebooks.ts +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/vscode-ext/src/test/environments.test.ts +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/vscode-ext/src/test/kernel-selection.test.ts +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/vscode-ext/src/test/notebooks.test.ts +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/vscode-ext/src/types.ts +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/vscode-ext/tsconfig.json +0 -0
- {calkit_python-0.41.11 → calkit_python-0.41.13}/yarn.lock +0 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import json
|
|
5
6
|
from typing import Annotated, Literal
|
|
6
7
|
|
|
7
8
|
import typer
|
|
@@ -12,6 +13,32 @@ from calkit.cli import AliasGroup, raise_error, warn
|
|
|
12
13
|
list_app = typer.Typer(cls=AliasGroup, no_args_is_help=True)
|
|
13
14
|
|
|
14
15
|
|
|
16
|
+
def _echo_object(obj: dict) -> None:
|
|
17
|
+
"""Print a single object in the human-readable YAML-ish listing format."""
|
|
18
|
+
# Copy so popping 'path' doesn't mutate the caller's dict.
|
|
19
|
+
obj = dict(obj)
|
|
20
|
+
path = obj.pop("path", None)
|
|
21
|
+
typer.echo(f"- path: {path}")
|
|
22
|
+
for k, v in obj.items():
|
|
23
|
+
if isinstance(v, dict):
|
|
24
|
+
typer.echo(f" {k}:")
|
|
25
|
+
for k1, v1 in v.items():
|
|
26
|
+
typer.echo(f" {k1}: {v1}")
|
|
27
|
+
elif isinstance(v, list):
|
|
28
|
+
typer.echo(f" {k}:")
|
|
29
|
+
for item in v:
|
|
30
|
+
if isinstance(item, dict):
|
|
31
|
+
for n, (k1, v1) in enumerate(item.items()):
|
|
32
|
+
if n == 0:
|
|
33
|
+
typer.echo(f" - {k1}: {v1}")
|
|
34
|
+
else:
|
|
35
|
+
typer.echo(f" {k1}: {v1}")
|
|
36
|
+
else:
|
|
37
|
+
typer.echo(f" - {item}")
|
|
38
|
+
else:
|
|
39
|
+
typer.echo(f" {k}: {v}")
|
|
40
|
+
|
|
41
|
+
|
|
15
42
|
def _list_objects(
|
|
16
43
|
kind: Literal[
|
|
17
44
|
"notebooks",
|
|
@@ -21,33 +48,44 @@ def _list_objects(
|
|
|
21
48
|
"publications",
|
|
22
49
|
],
|
|
23
50
|
):
|
|
24
|
-
"""List objects.
|
|
51
|
+
"""List objects."""
|
|
52
|
+
ck_info = calkit.load_calkit_info()
|
|
53
|
+
for obj in ck_info.get(kind, []) or []:
|
|
54
|
+
_echo_object(obj)
|
|
55
|
+
|
|
25
56
|
|
|
26
|
-
|
|
57
|
+
def _list_artifacts(
|
|
58
|
+
kind: Literal["figures", "datasets"],
|
|
59
|
+
json_output: bool,
|
|
60
|
+
declared_only: bool,
|
|
61
|
+
):
|
|
62
|
+
"""List figures or datasets, optionally including auto-detected ones.
|
|
63
|
+
|
|
64
|
+
By default, artifacts declared in ``calkit.yaml`` are merged with any
|
|
65
|
+
auto-detected from the project's files; ``--declared-only`` returns just the
|
|
66
|
+
declared ones. Each entry carries a ``detected`` flag so callers can tell
|
|
67
|
+
declared and auto-detected artifacts apart.
|
|
27
68
|
"""
|
|
28
69
|
ck_info = calkit.load_calkit_info()
|
|
29
|
-
|
|
30
|
-
for
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
typer.echo(f" - {item}")
|
|
49
|
-
else:
|
|
50
|
-
typer.echo(f" {k}: {v}")
|
|
70
|
+
declared = ck_info.get(kind, []) or []
|
|
71
|
+
declared_paths = {o.get("path") for o in declared if isinstance(o, dict)}
|
|
72
|
+
detected: list[dict] = []
|
|
73
|
+
if not declared_only:
|
|
74
|
+
found = calkit.detect.detect_project_artifacts(ck_info=ck_info)
|
|
75
|
+
for path in found.get(kind, []):
|
|
76
|
+
if path not in declared_paths:
|
|
77
|
+
detected.append({"path": path})
|
|
78
|
+
if json_output:
|
|
79
|
+
result = [
|
|
80
|
+
{**o, "detected": False} for o in declared if isinstance(o, dict)
|
|
81
|
+
]
|
|
82
|
+
result += [{**o, "detected": True} for o in detected]
|
|
83
|
+
typer.echo(json.dumps(result))
|
|
84
|
+
return
|
|
85
|
+
for obj in declared:
|
|
86
|
+
_echo_object(obj)
|
|
87
|
+
for obj in detected:
|
|
88
|
+
_echo_object({**obj, "detected": True})
|
|
51
89
|
|
|
52
90
|
|
|
53
91
|
@list_app.command(name="notebooks|nb")
|
|
@@ -57,15 +95,43 @@ def list_notebooks():
|
|
|
57
95
|
|
|
58
96
|
|
|
59
97
|
@list_app.command(name="figures|figs")
|
|
60
|
-
def list_figures(
|
|
98
|
+
def list_figures(
|
|
99
|
+
json_output: Annotated[
|
|
100
|
+
bool, typer.Option("--json", help="Output result as JSON.")
|
|
101
|
+
] = False,
|
|
102
|
+
declared_only: Annotated[
|
|
103
|
+
bool,
|
|
104
|
+
typer.Option(
|
|
105
|
+
"--declared-only",
|
|
106
|
+
help=(
|
|
107
|
+
"Only list figures declared in calkit.yaml; "
|
|
108
|
+
"skip auto-detection."
|
|
109
|
+
),
|
|
110
|
+
),
|
|
111
|
+
] = False,
|
|
112
|
+
):
|
|
61
113
|
"""List figures in the project."""
|
|
62
|
-
|
|
114
|
+
_list_artifacts("figures", json_output, declared_only)
|
|
63
115
|
|
|
64
116
|
|
|
65
117
|
@list_app.command(name="datasets")
|
|
66
|
-
def list_datasets(
|
|
118
|
+
def list_datasets(
|
|
119
|
+
json_output: Annotated[
|
|
120
|
+
bool, typer.Option("--json", help="Output result as JSON.")
|
|
121
|
+
] = False,
|
|
122
|
+
declared_only: Annotated[
|
|
123
|
+
bool,
|
|
124
|
+
typer.Option(
|
|
125
|
+
"--declared-only",
|
|
126
|
+
help=(
|
|
127
|
+
"Only list datasets declared in calkit.yaml; "
|
|
128
|
+
"skip auto-detection."
|
|
129
|
+
),
|
|
130
|
+
),
|
|
131
|
+
] = False,
|
|
132
|
+
):
|
|
67
133
|
"""List datasets in the project."""
|
|
68
|
-
|
|
134
|
+
_list_artifacts("datasets", json_output, declared_only)
|
|
69
135
|
|
|
70
136
|
|
|
71
137
|
@list_app.command(name="publications|pubs")
|
|
@@ -1172,7 +1172,7 @@ def pull(
|
|
|
1172
1172
|
and "-R" not in dvc_args
|
|
1173
1173
|
):
|
|
1174
1174
|
dvc_args.append("--recursive")
|
|
1175
|
-
result = calkit.dvc.run_dvc_command(["pull"] + dvc_args)
|
|
1175
|
+
result = calkit.dvc.run_dvc_command(["pull"] + dvc_args, lock_retries=5)
|
|
1176
1176
|
if result != 0:
|
|
1177
1177
|
raise_error("DVC pull failed")
|
|
1178
1178
|
calkit.dvc.zip.sync_all(direction="to-workspace")
|
|
@@ -1187,7 +1187,7 @@ def pull(
|
|
|
1187
1187
|
continue
|
|
1188
1188
|
typer.echo(f"DVC pulling subproject: {sp_path}")
|
|
1189
1189
|
sp_result = calkit.dvc.run_dvc_command(
|
|
1190
|
-
["pull"] + dvc_args, cwd=sp_path
|
|
1190
|
+
["pull"] + dvc_args, cwd=sp_path, lock_retries=5
|
|
1191
1191
|
)
|
|
1192
1192
|
if sp_result != 0:
|
|
1193
1193
|
raise_error(f"DVC pull failed for subproject: {sp_path}")
|
|
@@ -1224,7 +1224,9 @@ def push(
|
|
|
1224
1224
|
calkit.dvc.set_remote_auth(remote_name=name)
|
|
1225
1225
|
if remotes:
|
|
1226
1226
|
typer.echo("Pushing to DVC remote")
|
|
1227
|
-
result = calkit.dvc.run_dvc_command(
|
|
1227
|
+
result = calkit.dvc.run_dvc_command(
|
|
1228
|
+
["push"] + dvc_args, lock_retries=5
|
|
1229
|
+
)
|
|
1228
1230
|
if result != 0:
|
|
1229
1231
|
raise_error("DVC push failed")
|
|
1230
1232
|
else:
|
|
@@ -15,15 +15,17 @@ import shutil
|
|
|
15
15
|
import signal
|
|
16
16
|
import socket
|
|
17
17
|
import subprocess
|
|
18
|
+
import sys
|
|
18
19
|
import time
|
|
19
20
|
import uuid
|
|
21
|
+
from typing import Any
|
|
20
22
|
|
|
21
23
|
import typer
|
|
22
24
|
from sqlitedict import SqliteDict
|
|
23
25
|
from typing_extensions import Annotated
|
|
24
26
|
|
|
25
27
|
import calkit
|
|
26
|
-
from calkit.cli import AliasGroup, raise_error
|
|
28
|
+
from calkit.cli import AliasGroup, raise_error, warn
|
|
27
29
|
|
|
28
30
|
scheduler_app = typer.Typer(cls=AliasGroup, no_args_is_help=True)
|
|
29
31
|
|
|
@@ -177,15 +179,20 @@ def _mock_submit(job_id: str, job_command: str, log_path: str) -> int:
|
|
|
177
179
|
env = dict(os.environ)
|
|
178
180
|
env.setdefault("PBS_O_WORKDIR", os.getcwd())
|
|
179
181
|
with open(log_path, "w") as log_file:
|
|
180
|
-
|
|
182
|
+
# Annotated so the mixed value types don't defeat Popen's overloads.
|
|
183
|
+
popen_kwargs: dict[str, Any] = dict(
|
|
181
184
|
stdout=log_file,
|
|
182
185
|
stderr=subprocess.STDOUT,
|
|
183
186
|
env=env,
|
|
184
187
|
)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
+
# Detach the job into its own session/process group so it outlives
|
|
189
|
+
# this command and can be signaled as a group when canceled. Guard on
|
|
190
|
+
# `sys.platform` (not `os.name`) so the unused branch reads as
|
|
191
|
+
# platform-specific dead code rather than an unreachable bug.
|
|
192
|
+
if sys.platform == "win32":
|
|
188
193
|
popen_kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP
|
|
194
|
+
else:
|
|
195
|
+
popen_kwargs["start_new_session"] = True
|
|
189
196
|
proc = subprocess.Popen(
|
|
190
197
|
["bash", "-c", wrapped],
|
|
191
198
|
**popen_kwargs,
|
|
@@ -213,31 +220,145 @@ def _mock_cancel(job_id: str) -> tuple[bool, str]:
|
|
|
213
220
|
return True, ""
|
|
214
221
|
|
|
215
222
|
|
|
216
|
-
def
|
|
223
|
+
def _mock_exit_code(job_id: str) -> int | None:
|
|
224
|
+
# The job writes its exit code to the status sentinel before exiting; a
|
|
225
|
+
# canceled job writes "canceled" instead, which isn't an integer.
|
|
226
|
+
try:
|
|
227
|
+
with open(_mock_status_path(job_id)) as f:
|
|
228
|
+
content = f.read().strip()
|
|
229
|
+
except OSError:
|
|
230
|
+
return None
|
|
231
|
+
try:
|
|
232
|
+
return int(content)
|
|
233
|
+
except ValueError:
|
|
234
|
+
return None
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _parse_slurm_exit_code(value: str) -> int | None:
|
|
238
|
+
# SLURM reports exit status as "<code>:<signal>"; a job killed by a signal
|
|
239
|
+
# (e.g. out-of-memory, walltime) has a non-zero signal even if code is 0.
|
|
240
|
+
code_part, _, signal_part = value.strip().partition(":")
|
|
241
|
+
try:
|
|
242
|
+
code = int(code_part)
|
|
243
|
+
# An empty signal part means "no signal"; a non-empty one that won't
|
|
244
|
+
# parse is malformed, so report the whole value as unknown rather than
|
|
245
|
+
# silently treating it as a clean exit.
|
|
246
|
+
sig = int(signal_part) if signal_part else 0
|
|
247
|
+
except ValueError:
|
|
248
|
+
return None
|
|
249
|
+
return code if sig == 0 else 128 + sig
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _slurm_exit_code(job_id: str) -> int | None:
|
|
253
|
+
# Prefer scontrol, which reports a finished job while it lingers in the
|
|
254
|
+
# controller's memory and is available even without accounting; fall back
|
|
255
|
+
# to sacct for jobs that have already aged out of scontrol.
|
|
256
|
+
p = subprocess.run(
|
|
257
|
+
["scontrol", "show", "job", job_id],
|
|
258
|
+
capture_output=True,
|
|
259
|
+
text=True,
|
|
260
|
+
check=False,
|
|
261
|
+
)
|
|
262
|
+
if p.returncode == 0 and p.stdout.strip():
|
|
263
|
+
state = None
|
|
264
|
+
exit_code = None
|
|
265
|
+
for token in p.stdout.split():
|
|
266
|
+
if token.startswith("JobState="):
|
|
267
|
+
state = token.split("=", 1)[1]
|
|
268
|
+
elif token.startswith("ExitCode="):
|
|
269
|
+
exit_code = _parse_slurm_exit_code(token.split("=", 1)[1])
|
|
270
|
+
if state is not None:
|
|
271
|
+
if state == "COMPLETED":
|
|
272
|
+
return exit_code or 0
|
|
273
|
+
return exit_code or 1
|
|
274
|
+
p = subprocess.run(
|
|
275
|
+
["sacct", "-j", job_id, "-n", "-P", "-o", "State,ExitCode"],
|
|
276
|
+
capture_output=True,
|
|
277
|
+
text=True,
|
|
278
|
+
check=False,
|
|
279
|
+
)
|
|
280
|
+
if p.returncode == 0 and p.stdout.strip():
|
|
281
|
+
# The first line is the job itself (later lines are its steps).
|
|
282
|
+
state, _, code = p.stdout.strip().splitlines()[0].partition("|")
|
|
283
|
+
exit_code = _parse_slurm_exit_code(code)
|
|
284
|
+
if state.strip() == "COMPLETED":
|
|
285
|
+
return exit_code or 0
|
|
286
|
+
return exit_code or 1
|
|
287
|
+
return None
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _poll_job(kind: str, job_id: str) -> tuple[bool, int | None]:
|
|
291
|
+
"""Return ``(active, exit_code)`` for a scheduler job.
|
|
292
|
+
|
|
293
|
+
``active`` is whether the job is still queued or running. ``exit_code`` is
|
|
294
|
+
the job's exit status once it has finished---0 for success, non-zero for
|
|
295
|
+
failure---or ``None`` while the job is still active or when the scheduler
|
|
296
|
+
no longer has a record from which to read it. A ``None`` exit code on a
|
|
297
|
+
finished job is an unknown outcome the caller must decide how to treat.
|
|
298
|
+
"""
|
|
217
299
|
if _mock_enabled():
|
|
218
|
-
|
|
300
|
+
if _mock_active(job_id):
|
|
301
|
+
return True, None
|
|
302
|
+
return False, _mock_exit_code(job_id)
|
|
219
303
|
if kind == "slurm":
|
|
220
304
|
p = subprocess.run(
|
|
221
|
-
["squeue", "--job", job_id],
|
|
305
|
+
["squeue", "--job", job_id],
|
|
306
|
+
capture_output=True,
|
|
307
|
+
text=True,
|
|
308
|
+
check=False,
|
|
222
309
|
)
|
|
223
|
-
if p.returncode
|
|
224
|
-
|
|
225
|
-
|
|
310
|
+
if p.returncode == 0:
|
|
311
|
+
# squeue prints a header row plus one row per matching job, so more
|
|
312
|
+
# than the header means the job is still queued or running.
|
|
313
|
+
if len(p.stdout.strip().split("\n")) > 1:
|
|
314
|
+
return True, None
|
|
315
|
+
return False, _slurm_exit_code(job_id)
|
|
316
|
+
# A non-zero exit is ambiguous like PBS: an invalid/unknown job id
|
|
317
|
+
# means the job is gone, but a transient controller failure should not
|
|
318
|
+
# end the wait early (nor let `_slurm_exit_code` call a still-running
|
|
319
|
+
# job failed), so only conclude the job is done when squeue positively
|
|
320
|
+
# reports the id is invalid; otherwise keep waiting.
|
|
321
|
+
stderr = (p.stderr or "").lower()
|
|
322
|
+
if "invalid job id" in stderr:
|
|
323
|
+
return False, _slurm_exit_code(job_id)
|
|
324
|
+
return True, None
|
|
226
325
|
# Use `qstat -f` and parse job_state: on Torque/OpenPBS, plain `qstat
|
|
227
326
|
# <id>` returns exit 0 even for completed (C) jobs, so checking the
|
|
228
327
|
# return code alone would cause `calkit sched batch` to hang forever
|
|
229
|
-
# after a PBS job finishes. States C and F mean the job is done
|
|
328
|
+
# after a PBS job finishes. States C and F mean the job is done, and a
|
|
329
|
+
# finished job's record carries its `Exit_status`.
|
|
230
330
|
p = subprocess.run(
|
|
231
331
|
["qstat", "-f", job_id], capture_output=True, text=True, check=False
|
|
232
332
|
)
|
|
233
333
|
if p.returncode != 0:
|
|
234
|
-
|
|
334
|
+
# A non-zero exit is ambiguous: either the job is gone (completed and
|
|
335
|
+
# purged from history) or `qstat` itself failed transiently---a busy
|
|
336
|
+
# PBS server periodically refuses connections or times out. Treating a
|
|
337
|
+
# transient failure as completion would stop the wait while the job is
|
|
338
|
+
# still running and writing its log, so only conclude the job is done
|
|
339
|
+
# when qstat positively reports it is unknown; otherwise keep waiting.
|
|
340
|
+
# A purged job (unknown to qstat) is done but with an unknowable exit
|
|
341
|
+
# status; any other error is transient, so report the job as active.
|
|
342
|
+
stderr = (p.stderr or "").lower()
|
|
343
|
+
return ("unknown job" not in stderr), None
|
|
344
|
+
state = None
|
|
345
|
+
exit_code = None
|
|
235
346
|
for line in p.stdout.splitlines():
|
|
236
347
|
stripped = line.strip()
|
|
237
348
|
if stripped.startswith("job_state"):
|
|
238
349
|
state = stripped.split("=", 1)[-1].strip()
|
|
239
|
-
|
|
240
|
-
|
|
350
|
+
elif stripped.lower().startswith("exit_status"):
|
|
351
|
+
try:
|
|
352
|
+
exit_code = int(stripped.split("=", 1)[-1].strip())
|
|
353
|
+
except ValueError:
|
|
354
|
+
exit_code = None
|
|
355
|
+
if state in ("C", "F"):
|
|
356
|
+
return False, exit_code
|
|
357
|
+
return True, None
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _is_active(kind: str, job_id: str) -> bool:
|
|
361
|
+
return _poll_job(kind, job_id)[0]
|
|
241
362
|
|
|
242
363
|
|
|
243
364
|
def _cancel(kind: str, job_id: str) -> tuple[bool, str]:
|
|
@@ -250,14 +371,19 @@ def _cancel(kind: str, job_id: str) -> tuple[bool, str]:
|
|
|
250
371
|
return p.returncode == 0, p.stderr
|
|
251
372
|
|
|
252
373
|
|
|
253
|
-
def _wait_until_done(kind: str, job_id: str, name: str) -> None:
|
|
254
|
-
"""Poll until the job finishes,
|
|
374
|
+
def _wait_until_done(kind: str, job_id: str, name: str) -> int | None:
|
|
375
|
+
"""Poll until the job finishes, returning its exit code.
|
|
255
376
|
|
|
377
|
+
The exit code is the scheduler-reported status of the finished job (0 for
|
|
378
|
+
success, non-zero for failure), or ``None`` when it can't be determined.
|
|
256
379
|
The job is submitted and tracked, so interrupting the local wait cancels
|
|
257
380
|
the scheduler job before exiting rather than leaving it orphaned.
|
|
258
381
|
"""
|
|
259
382
|
try:
|
|
260
|
-
while
|
|
383
|
+
while True:
|
|
384
|
+
active, exit_code = _poll_job(kind, job_id)
|
|
385
|
+
if not active:
|
|
386
|
+
return exit_code
|
|
261
387
|
time.sleep(1)
|
|
262
388
|
except KeyboardInterrupt:
|
|
263
389
|
typer.echo(f"Interrupted; canceling job '{name}' ({job_id})")
|
|
@@ -267,6 +393,68 @@ def _wait_until_done(kind: str, job_id: str, name: str) -> None:
|
|
|
267
393
|
raise typer.Exit(130)
|
|
268
394
|
|
|
269
395
|
|
|
396
|
+
def _wait_for_output_file(log_path: str, timeout: float = 120.0) -> None:
|
|
397
|
+
"""Wait for a finished job's output log to be fully written.
|
|
398
|
+
|
|
399
|
+
A scheduler reports a job as done as soon as it leaves the queue, but the
|
|
400
|
+
job's ``-o`` log---which is the batch stage's declared DVC output---may not
|
|
401
|
+
be in place yet: PBS stages it back from the exec host's spool after the
|
|
402
|
+
job exits, and a job can still be flushing its final lines. Returning
|
|
403
|
+
before the file lands (or while it is still growing) makes the subsequent
|
|
404
|
+
``dvc repro`` see a missing or mid-write output and wrongly report the job
|
|
405
|
+
as failed. Poll until the file exists and its size holds steady, or until
|
|
406
|
+
``timeout`` elapses (then return and let DVC surface the real state).
|
|
407
|
+
"""
|
|
408
|
+
deadline = time.monotonic() + timeout
|
|
409
|
+
last_size = -1
|
|
410
|
+
# Require the size to repeat across polls so we don't snapshot mid-write;
|
|
411
|
+
# a missing file reports -1, which never counts as stable.
|
|
412
|
+
stable_polls = 0
|
|
413
|
+
while time.monotonic() < deadline:
|
|
414
|
+
try:
|
|
415
|
+
size = os.path.getsize(log_path)
|
|
416
|
+
except OSError:
|
|
417
|
+
size = -1
|
|
418
|
+
if size >= 0 and size == last_size:
|
|
419
|
+
stable_polls += 1
|
|
420
|
+
if stable_polls >= 2:
|
|
421
|
+
return
|
|
422
|
+
else:
|
|
423
|
+
stable_polls = 0
|
|
424
|
+
last_size = size
|
|
425
|
+
time.sleep(1)
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def _finalize_job(
|
|
429
|
+
name: str, job_id: str, exit_code: int | None, log_path: str
|
|
430
|
+
) -> None:
|
|
431
|
+
"""Fail the command if the finished job did not succeed.
|
|
432
|
+
|
|
433
|
+
Waiting for the job to leave the queue only tells us it stopped, not
|
|
434
|
+
whether it worked, so a non-zero scheduler exit code is surfaced as an
|
|
435
|
+
error---raising a non-zero exit that propagates up through ``dvc repro``
|
|
436
|
+
so the stage is marked failed rather than silently recorded as done. An
|
|
437
|
+
unknown exit code (the scheduler purged the record before we could read
|
|
438
|
+
it) can't be judged, so we warn and let the stage's declared outputs be
|
|
439
|
+
the arbiter.
|
|
440
|
+
"""
|
|
441
|
+
# Wait for the job's `-o` log---this stage's declared DVC output---to be
|
|
442
|
+
# staged back before we read from or point at it.
|
|
443
|
+
if not _mock_enabled():
|
|
444
|
+
_wait_for_output_file(log_path)
|
|
445
|
+
if exit_code is None:
|
|
446
|
+
warn(
|
|
447
|
+
f"Could not determine exit status for job '{name}' (ID {job_id}); "
|
|
448
|
+
f"assuming success. Check the log at {log_path}"
|
|
449
|
+
)
|
|
450
|
+
return
|
|
451
|
+
if exit_code != 0:
|
|
452
|
+
raise_error(
|
|
453
|
+
f"Job '{name}' (ID {job_id}) failed with exit code {exit_code}. "
|
|
454
|
+
f"See the log at {log_path}"
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
|
|
270
458
|
@scheduler_app.command(name="batch")
|
|
271
459
|
def run_batch(
|
|
272
460
|
name: Annotated[
|
|
@@ -496,7 +684,11 @@ def run_batch(
|
|
|
496
684
|
# The recorded job may have been submitted under a different
|
|
497
685
|
# scheduler kind; use its own kind for activity/cancel checks.
|
|
498
686
|
prev_kind = job_info.get("kind", kind)
|
|
499
|
-
|
|
687
|
+
# Capture the exit code alongside liveness: if the job already left the
|
|
688
|
+
# queue (e.g. while we were disconnected) the scheduler may still have
|
|
689
|
+
# its status in history, and reading it now avoids losing it to a later
|
|
690
|
+
# purge.
|
|
691
|
+
running_or_queued, prev_exit_code = _poll_job(prev_kind, job_id)
|
|
500
692
|
should_wait = True
|
|
501
693
|
|
|
502
694
|
def _cancel_with_reason(reason: str) -> None:
|
|
@@ -543,14 +735,19 @@ def run_batch(
|
|
|
543
735
|
break
|
|
544
736
|
if should_wait:
|
|
545
737
|
typer.echo("Waiting for job to finish")
|
|
546
|
-
_wait_until_done(prev_kind, job_id, name)
|
|
738
|
+
exit_code = _wait_until_done(prev_kind, job_id, name)
|
|
739
|
+
_finalize_job(name, job_id, exit_code, log_path)
|
|
547
740
|
raise typer.Exit(0)
|
|
548
741
|
elif not os.environ.get("CALKIT_FORCE"):
|
|
549
|
-
# The job has left the queue.
|
|
550
|
-
#
|
|
551
|
-
#
|
|
552
|
-
#
|
|
553
|
-
#
|
|
742
|
+
# The job has left the queue (e.g. it finished while the master
|
|
743
|
+
# process was disconnected). If nothing it depends on changed and
|
|
744
|
+
# the prior submission succeeded---or its status was purged, in
|
|
745
|
+
# which case we assume success since there is no record left to
|
|
746
|
+
# judge by and the stage's declared outputs are the final
|
|
747
|
+
# arbiter---harvest it rather than resubmitting and discarding the
|
|
748
|
+
# work. A known failure instead falls through to resubmit, so
|
|
749
|
+
# `calkit run` retries it (e.g. after the user fixed the cause).
|
|
750
|
+
# Under --force (CALKIT_FORCE) we skip this and always resubmit.
|
|
554
751
|
job_dep_md5s = job_info.get("dep_md5s", {})
|
|
555
752
|
deps_unchanged = set(job_deps) == set(deps) and all(
|
|
556
753
|
current_dep_md5s.get(dep) == job_dep_md5s.get(dep)
|
|
@@ -561,13 +758,15 @@ def run_batch(
|
|
|
561
758
|
and job_args == args
|
|
562
759
|
and job_setup == setup_cmds
|
|
563
760
|
)
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
761
|
+
if (
|
|
762
|
+
deps_unchanged
|
|
763
|
+
and command_unchanged
|
|
764
|
+
and prev_exit_code in (0, None)
|
|
765
|
+
):
|
|
568
766
|
typer.echo(
|
|
569
|
-
f"Job '{name}' already
|
|
767
|
+
f"Job '{name}' already left the queue; using its result"
|
|
570
768
|
)
|
|
769
|
+
_finalize_job(name, job_id, prev_exit_code, log_path)
|
|
571
770
|
raise typer.Exit(0)
|
|
572
771
|
# Job is not running or queued, so we can submit. First, delete any
|
|
573
772
|
# non-persistent outputs.
|
|
@@ -632,7 +831,8 @@ def run_batch(
|
|
|
632
831
|
)
|
|
633
832
|
raise typer.Exit(130)
|
|
634
833
|
typer.echo("Waiting for job to finish")
|
|
635
|
-
_wait_until_done(kind, job_id, name)
|
|
834
|
+
exit_code = _wait_until_done(kind, job_id, name)
|
|
835
|
+
_finalize_job(name, job_id, exit_code, log_path)
|
|
636
836
|
|
|
637
837
|
|
|
638
838
|
def _detect_interpreter(target: str) -> list[str]:
|
|
@@ -15,7 +15,8 @@ from functools import partial
|
|
|
15
15
|
from typing import Literal
|
|
16
16
|
|
|
17
17
|
import requests
|
|
18
|
-
from requests.exceptions import
|
|
18
|
+
from requests.exceptions import ConnectionError as RequestsConnectionError
|
|
19
|
+
from requests.exceptions import HTTPError, Timeout
|
|
19
20
|
|
|
20
21
|
from . import config
|
|
21
22
|
|
|
@@ -104,8 +105,7 @@ def run_device_flow() -> str:
|
|
|
104
105
|
) from e
|
|
105
106
|
if "Device code not found" in txt:
|
|
106
107
|
raise DeviceLoginError(
|
|
107
|
-
"Device code not found; "
|
|
108
|
-
"Run 'calkit cloud login' again"
|
|
108
|
+
"Device code not found; Run 'calkit cloud login' again"
|
|
109
109
|
) from e
|
|
110
110
|
raise DeviceLoginError(
|
|
111
111
|
f"Error while polling for device authorization: {e}"
|
|
@@ -304,6 +304,9 @@ def _request(
|
|
|
304
304
|
max_retries = 10
|
|
305
305
|
base_delay_seconds = 0.25
|
|
306
306
|
max_delay_seconds = 30
|
|
307
|
+
# Bound how long a single attempt can hang so stalled connections become
|
|
308
|
+
# retryable timeouts rather than blocking forever. Callers can override.
|
|
309
|
+
kwargs.setdefault("timeout", (10, 120))
|
|
307
310
|
func = getattr(requests, kind)
|
|
308
311
|
if base_url is None:
|
|
309
312
|
base_url = get_base_url()
|
|
@@ -333,15 +336,29 @@ def _request(
|
|
|
333
336
|
run_device_flow()
|
|
334
337
|
continue
|
|
335
338
|
raise
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
339
|
+
try:
|
|
340
|
+
resp = func(
|
|
341
|
+
base_url + path,
|
|
342
|
+
params=params,
|
|
343
|
+
json=json,
|
|
344
|
+
data=data,
|
|
345
|
+
headers=req_headers,
|
|
346
|
+
**kwargs,
|
|
347
|
+
)
|
|
348
|
+
except (Timeout, RequestsConnectionError):
|
|
349
|
+
# Transient network failure (read/connect timeout, connection
|
|
350
|
+
# reset/refused). Retry with the same backoff as transient 5xx
|
|
351
|
+
# responses; re-raise once retries are exhausted.
|
|
352
|
+
if retry_num >= max_retries:
|
|
353
|
+
raise
|
|
354
|
+
wait = min(base_delay_seconds * (2**retry_num), max_delay_seconds)
|
|
355
|
+
time.sleep(wait)
|
|
356
|
+
continue
|
|
357
|
+
# Retry transient server-side errors (bad gateway, overloaded,
|
|
358
|
+
# unavailable, gateway timeout) with exponential backoff. These are
|
|
359
|
+
# typically infrastructure hiccups that resolve on a retry.
|
|
360
|
+
is_transient_5xx = resp.status_code in (500, 502, 503, 504)
|
|
361
|
+
if is_transient_5xx and retry_num < max_retries:
|
|
345
362
|
wait = min(base_delay_seconds * (2**retry_num), max_delay_seconds)
|
|
346
363
|
time.sleep(wait)
|
|
347
364
|
continue
|