calkit-python 0.35.3__tar.gz → 0.35.5__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.35.3 → calkit_python-0.35.5}/Makefile +7 -2
- {calkit_python-0.35.3 → calkit_python-0.35.5}/PKG-INFO +1 -1
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/conda.py +78 -28
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/git.py +42 -4
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/overleaf.py +32 -1
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/pipeline.py +37 -8
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/releases.py +1 -1
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/cli/test_overleaf.py +38 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/test_conda.py +120 -2
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/test_git.py +51 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/test_pipeline.py +47 -0
- calkit_python-0.35.5/calkit/tests/test_releases.py +44 -0
- calkit_python-0.35.5/docs/cli-reference.md +2586 -0
- calkit_python-0.35.5/scripts/generate-cli-reference.py +257 -0
- calkit_python-0.35.3/docs/cli-reference.md +0 -3
- {calkit_python-0.35.3 → calkit_python-0.35.5}/.gitignore +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/.pre-commit-config.yaml +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/.prettierignore +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/.python-version +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/.yarnrc.yml +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/CITATION.cff +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/CODE_OF_CONDUCT.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/CONTRIBUTING.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/LICENSE +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/README.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/babel.config.js +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/__init__.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/__main__.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/calc.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/check.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/cli/__init__.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/cli/check.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/cli/cloud.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/cli/config.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/cli/core.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/cli/describe.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/cli/import_.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/cli/latex.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/cli/list.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/cli/main/__init__.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/cli/main/core.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/cli/main/xr.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/cli/new.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/cli/notebooks.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/cli/office.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/cli/overleaf.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/cli/slurm.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/cli/update.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/cloud.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/config.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/core.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/datasets.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/detect.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/docker.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/dvc.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/environments.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/fs.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/github.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/gui.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/invenio.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/julia.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/jupyter.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/jupyterlab/__init__.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/jupyterlab/routes.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/labextension/package.json +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/labextension/schemas/calkit/package.json.orig +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/labextension/schemas/calkit/plugin.json +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/labextension/static/502.9a2c5772a15466e923ef.js +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/labextension/static/695.2c41003a452d43d2b358.js +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/labextension/static/867.a42a046aa5108f54f8fb.js +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/labextension/static/909.651be47ca47390b78a92.js +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/labextension/static/946.050af2abf7845cfbdbd2.js +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/labextension/static/946.050af2abf7845cfbdbd2.js.LICENSE.txt +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/labextension/static/b2f1c3efe70cb539d121.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/labextension/static/remoteEntry.c091821b3d7f2d287a67.js +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/labextension/static/style.js +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/labextension/static/third-party-licenses.json +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/licenses.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/magics.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/matlab.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/models/__init__.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/models/core.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/models/io.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/models/iteration.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/models/pipeline.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/notebooks.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/office.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/ops.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/server.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/templates/__init__.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/templates/core.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/templates/latex/__init__.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/templates/latex/article/paper.tex +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/templates/latex/core.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/templates/latex/jfm/jfm.bst +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/templates/latex/jfm/jfm.cls +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/templates/latex/jfm/lineno-FLM.sty +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/templates/latex/jfm/paper.tex +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/templates/latex/jfm/upmath.sty +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/__init__.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/cli/__init__.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/cli/main/__init__.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/cli/main/test_core.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/cli/main/test_xr.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/cli/test_check.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/cli/test_config.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/cli/test_import.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/cli/test_latex.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/cli/test_list.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/cli/test_new.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/cli/test_notebooks.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/jupyterlab/__init__.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/jupyterlab/test_routes.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/models/__init__.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/models/test_iteration.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/models/test_pipeline.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/test_calc.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/test_check.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/test_core.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/test_detect.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/test_docker.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/test_dvc.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/test_environments.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/test_fs.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/test_invenio.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/test_julia.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/test_jupyter.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/test_magics.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/test_matlab.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/test_notebooks.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/calkit/tests/test_templates.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/conftest.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/CNAME +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/apps.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/calculations.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/calkit-yaml.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/cloud-integration.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/datasets.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/dependencies.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/environments.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/examples.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/governance.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/help.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/img/c-to-the-k-white.svg +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/img/calkit-fragmentation-compendium.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/img/calkit-no-bg.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/img/connect-zenodo.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/img/jupyterlab/all-green.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/img/jupyterlab/collect-data-stale.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/img/jupyterlab/new-env.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/img/jupyterlab/new-notebook.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/img/jupyterlab/pipeline-badge.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/img/jupyterlab-params.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/img/plos-osi-code-2024-03.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/img/vscode-nb-params.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/index.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/installation.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/jupyterlab.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/local-server.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/notebooks.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/overleaf.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/pipeline/index.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/pipeline/manual-steps.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/pipeline/running-and-logging.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/pipeline/slurm.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/quickstart.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/references.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/releases.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/reproducibility.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/adding-latex-pub-docker.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/conda-envs.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/existing-project.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/first-project.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/github-actions.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/actions-repo-secrets.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/latex-codespaces/building-codespace.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/latex-codespaces/codespaces-secrets-2.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/latex-codespaces/editor-split.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/latex-codespaces/go-to-linked-code.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/latex-codespaces/issue-from-selection.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/latex-codespaces/new-project.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/latex-codespaces/new-pub-2.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/latex-codespaces/new-token.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/latex-codespaces/paper.tex.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/latex-codespaces/project-home-3.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/latex-codespaces/push.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/latex-codespaces/stage.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/office/anakin-excel.jpg +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/office/chart-more-rows.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/office/create-project.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/office/elsevier-research-data-guidelines.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/office/excel-chart.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/office/excel-data.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/office/insert-link-to-file.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/office/needs-clone.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/office/new-stage.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/office/phd-comics-version-control.webp +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/office/pipeline-out-of-date.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/office/status-more-rows.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/office/uncommitted-changes.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/office/untracked-data.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/office/updated-publication.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/office/word-to-pdf-stage-2.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/office/workflow-page.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/openfoam/clone.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/openfoam/create-project.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/openfoam/datasets-page.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/openfoam/figure-on-website-updated.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/openfoam/figure-on-website.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/openfoam/new-token.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/openfoam/reclone.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/openfoam/status-after-import-dataset.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/quick-actions.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/img/run-proc.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/index.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/jupyterlab.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/latex-codespaces.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/matlab.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/notebook-pipeline.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/office.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/openfoam.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/tutorials/procedures.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/docs/version-control.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/install.json +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/jest.config.js +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/jupyter-config/server-config/calkit.json +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/mkdocs.yml +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/package.json +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/pyproject.toml +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/schema/plugin.json +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/scripts/install.ps1 +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/scripts/install.sh +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/scripts/make-calk9.sh +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/scripts/sync-docs.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/src/__tests__/useQueries.spec.ts +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/src/calkit-config.ts +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/src/cell-output-marker.ts +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/src/components/commit-dialog.tsx +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/src/components/environment-editor.tsx +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/src/components/notebook-registration.tsx +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/src/components/notebook-toolbar.tsx +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/src/components/pipeline-status-bar.tsx +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/src/components/project-info-editor.tsx +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/src/components/sidebar-settings.tsx +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/src/components/sidebar.tsx +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/src/components/stage-editor.tsx +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/src/feature-flags.ts +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/src/file-browser-menu.ts +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/src/hooks/__tests__/useQueries.test.tsx +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/src/hooks/useQueries.ts +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/src/icons.ts +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/src/index.ts +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/src/io-tracker.ts +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/src/pipeline-state.ts +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/src/queryClient.ts +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/src/request.ts +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/src/shims-mainmenu.d.ts +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/style/base.css +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/style/cell-output-marker.css +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/style/environment-editor.css +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/style/environment-selector.css +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/style/img/calkit-no-bg.png +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/style/index.css +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/style/index.js +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/style/notebook-toolbar.css +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/style/pipeline-status-bar.css +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/style/sidebar.css +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/test/dvc-md5-dir/osx-arm64.txt +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/test/nb-julia.ipynb +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/test/nb-params.ipynb +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/test/nb-subdir.ipynb +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/test/pipeline.ipynb +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/test/script.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/test/test-log.log +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/tsconfig.json +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/tsconfig.test.json +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/ui-tests/.gitignore +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/ui-tests/README.md +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/ui-tests/jupyter_server_test_config.py +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/ui-tests/package.json +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/ui-tests/playwright.config.js +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/ui-tests/tests/calkit.spec.ts +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/ui-tests/yarn.lock +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/uv.lock +0 -0
- {calkit_python-0.35.3 → calkit_python-0.35.5}/yarn.lock +0 -0
|
@@ -37,12 +37,17 @@ test-docs: sync-docs ## Test if documentation can be built without warnings or e
|
|
|
37
37
|
@uv run mkdocs build -s
|
|
38
38
|
|
|
39
39
|
.PHONY: sync-docs
|
|
40
|
-
sync-docs: ## Sync documentation content from docs/*.md into README.md.
|
|
40
|
+
sync-docs: cli-reference ## Sync documentation content from docs/*.md into README.md.
|
|
41
41
|
@echo "🚀 Syncing documentation"
|
|
42
42
|
@uv run python scripts/sync-docs.py
|
|
43
43
|
|
|
44
|
+
.PHONY: cli-reference
|
|
45
|
+
cli-reference: ## Generate docs/cli-reference.md from CLI help output.
|
|
46
|
+
@echo "🚀 Generating CLI reference"
|
|
47
|
+
@uv run python scripts/generate-cli-reference.py
|
|
48
|
+
|
|
44
49
|
.PHONY: docs
|
|
45
|
-
docs: ## Build and serve the documentation.
|
|
50
|
+
docs: sync-docs ## Build and serve the documentation.
|
|
46
51
|
@uv run mkdocs serve --livereload
|
|
47
52
|
|
|
48
53
|
.PHONY: import-profile
|
|
@@ -9,6 +9,7 @@ import shutil
|
|
|
9
9
|
import subprocess
|
|
10
10
|
import warnings
|
|
11
11
|
from pathlib import Path
|
|
12
|
+
from typing import cast
|
|
12
13
|
|
|
13
14
|
import toml
|
|
14
15
|
from packaging.specifiers import SpecifierSet
|
|
@@ -123,7 +124,13 @@ def _editable_package_name_from_dir(dir_path: str) -> str:
|
|
|
123
124
|
elif os.path.isfile(os.path.join(dir_path, "pyproject.toml")):
|
|
124
125
|
# Read pyproject.toml to get the package name
|
|
125
126
|
with open(os.path.join(dir_path, "pyproject.toml")) as f:
|
|
126
|
-
|
|
127
|
+
try:
|
|
128
|
+
pyproject = toml.load(f)
|
|
129
|
+
except Exception as e:
|
|
130
|
+
raise type(e)(
|
|
131
|
+
f"Failed to load pyproject.toml from {dir_path}; "
|
|
132
|
+
"check that it is valid TOML"
|
|
133
|
+
) from e
|
|
127
134
|
if "project" in pyproject:
|
|
128
135
|
if "name" in pyproject["project"]:
|
|
129
136
|
return pyproject["project"]["name"]
|
|
@@ -139,6 +146,7 @@ def _check_single(
|
|
|
139
146
|
"""
|
|
140
147
|
# If this is an editable install it needs to be handled specially
|
|
141
148
|
# It also needs to be relative to the env spec dir
|
|
149
|
+
editable = False
|
|
142
150
|
if req.startswith("-e ") or req.startswith("--editable "):
|
|
143
151
|
req = req.split(" ", 1)[1]
|
|
144
152
|
if "#" in req:
|
|
@@ -147,6 +155,7 @@ def _check_single(
|
|
|
147
155
|
# Create path relative to env spec dir
|
|
148
156
|
req = os.path.join(env_spec_dir, req)
|
|
149
157
|
req = _editable_package_name_from_dir(req)
|
|
158
|
+
editable = True
|
|
150
159
|
# If this is a Git version, we can't check it
|
|
151
160
|
# TODO: Clone Git repos to check?
|
|
152
161
|
if "@git" in req:
|
|
@@ -182,7 +191,7 @@ def _check_single(
|
|
|
182
191
|
# TODO: Check exact version only
|
|
183
192
|
return True
|
|
184
193
|
spec = SpecifierSet(req_spec)
|
|
185
|
-
return spec.contains(version)
|
|
194
|
+
return spec.contains(version, prereleases=editable)
|
|
186
195
|
|
|
187
196
|
|
|
188
197
|
def _check_list(
|
|
@@ -193,6 +202,10 @@ def _check_list(
|
|
|
193
202
|
if "::" in req:
|
|
194
203
|
req = req.split("::", 1)[1]
|
|
195
204
|
for installed in actual:
|
|
205
|
+
if not isinstance(installed, str):
|
|
206
|
+
raise ValueError(
|
|
207
|
+
f"Expected installed package to be a string, got {installed}"
|
|
208
|
+
)
|
|
196
209
|
if _check_single(
|
|
197
210
|
req, installed, env_spec_dir=env_spec_dir, conda=conda
|
|
198
211
|
):
|
|
@@ -200,6 +213,46 @@ def _check_list(
|
|
|
200
213
|
return False
|
|
201
214
|
|
|
202
215
|
|
|
216
|
+
def _split_env_dependencies(
|
|
217
|
+
dependencies: list[str | dict[str, str | list[str]]],
|
|
218
|
+
) -> tuple[list[str], list[str]]:
|
|
219
|
+
"""Split an environment dependency list into conda and pip deps.
|
|
220
|
+
|
|
221
|
+
Conda environment files commonly include both the plain ``"pip"`` package
|
|
222
|
+
marker and a nested ``{"pip": [...]}`` section. This helper normalizes the
|
|
223
|
+
latter so callers do not need to assume it is the final list entry or that
|
|
224
|
+
the pip section is already represented as a list.
|
|
225
|
+
"""
|
|
226
|
+
conda_deps = []
|
|
227
|
+
pip_deps = []
|
|
228
|
+
for dep in dependencies:
|
|
229
|
+
if isinstance(dep, dict):
|
|
230
|
+
dep_pip = dep.get("pip", [])
|
|
231
|
+
if isinstance(dep_pip, str):
|
|
232
|
+
dep_pip = [dep_pip]
|
|
233
|
+
elif dep_pip is None:
|
|
234
|
+
dep_pip = []
|
|
235
|
+
pip_deps.extend(dep_pip)
|
|
236
|
+
else:
|
|
237
|
+
conda_deps.append(dep)
|
|
238
|
+
return conda_deps, pip_deps
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _get_pip_dependency_list(
|
|
242
|
+
dependencies: list[str | dict[str, str | list[str]]],
|
|
243
|
+
) -> list[str]:
|
|
244
|
+
"""Return a mutable pip dependency list from an env dependency list."""
|
|
245
|
+
for dep in dependencies:
|
|
246
|
+
if isinstance(dep, dict) and "pip" in dep:
|
|
247
|
+
dep_pip = dep["pip"]
|
|
248
|
+
if isinstance(dep_pip, str):
|
|
249
|
+
dep["pip"] = [dep_pip]
|
|
250
|
+
elif dep_pip is None:
|
|
251
|
+
dep["pip"] = []
|
|
252
|
+
return cast(list[str], dep["pip"])
|
|
253
|
+
return []
|
|
254
|
+
|
|
255
|
+
|
|
203
256
|
class EnvCheckResult(BaseModel):
|
|
204
257
|
env_exists: bool | None = None
|
|
205
258
|
env_needs_export: bool | None = None
|
|
@@ -393,18 +446,12 @@ def check_env(
|
|
|
393
446
|
ryaml.dump(env_check, f)
|
|
394
447
|
# Determine if the env matches
|
|
395
448
|
env_needs_rebuild = False
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
if isinstance(env_spec["dependencies"][-1], dict):
|
|
403
|
-
required_conda_deps = env_spec["dependencies"][:-1]
|
|
404
|
-
required_pip_deps = env_spec["dependencies"][-1]["pip"]
|
|
405
|
-
else:
|
|
406
|
-
required_conda_deps = env_spec["dependencies"]
|
|
407
|
-
required_pip_deps = []
|
|
449
|
+
existing_conda_deps, existing_pip_deps = _split_env_dependencies(
|
|
450
|
+
env_check["dependencies"]
|
|
451
|
+
)
|
|
452
|
+
required_conda_deps, required_pip_deps = _split_env_dependencies(
|
|
453
|
+
env_spec["dependencies"]
|
|
454
|
+
)
|
|
408
455
|
if relaxed:
|
|
409
456
|
log_func("Running in relaxed mode; combining pip and conda deps")
|
|
410
457
|
for dep in existing_pip_deps:
|
|
@@ -516,20 +563,23 @@ def check_env(
|
|
|
516
563
|
# Note that this needs to be relative to the env lock directory,
|
|
517
564
|
# since that's how pip will interpret it
|
|
518
565
|
editable_pip_deps = {}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
if
|
|
524
|
-
dir_path =
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
566
|
+
required_pip_deps = _get_pip_dependency_list(env_spec["dependencies"])
|
|
567
|
+
for dep in required_pip_deps:
|
|
568
|
+
if dep.startswith("-e ") or dep.startswith("--editable "):
|
|
569
|
+
dir_path = dep.split(" ", 1)[1]
|
|
570
|
+
if "#" in dir_path:
|
|
571
|
+
dir_path = dir_path.split("#", 1)[0]
|
|
572
|
+
dir_path = dir_path.strip()
|
|
573
|
+
dir_path = os.path.join(env_spec_dir, dir_path)
|
|
574
|
+
pkg_name = _editable_package_name_from_dir(dir_path)
|
|
575
|
+
if verbose:
|
|
576
|
+
log_func(
|
|
577
|
+
f"Found editable pip dependency '{pkg_name}' "
|
|
578
|
+
f"at '{dir_path}'"
|
|
579
|
+
)
|
|
580
|
+
editable_pip_deps[pkg_name] = dir_path
|
|
581
|
+
export_pip_deps = _get_pip_dependency_list(env_export["dependencies"])
|
|
582
|
+
if export_pip_deps:
|
|
533
583
|
for i, dep in enumerate(export_pip_deps):
|
|
534
584
|
dep_name = re.split("[=<>]+", dep, maxsplit=1)[0]
|
|
535
585
|
if dep_name in editable_pip_deps:
|
|
@@ -150,10 +150,48 @@ def ensure_path_is_not_ignored(
|
|
|
150
150
|
gitignore_txt = f.read()
|
|
151
151
|
lines = gitignore_txt.splitlines()
|
|
152
152
|
no_ignore_line = f"!{target_path}"
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
153
|
+
path_parts = Path(target_path).parts
|
|
154
|
+
if len(path_parts) == 1:
|
|
155
|
+
# Simple (non-nested) path: remove the direct ignore rule, or add a
|
|
156
|
+
# negation if the ignore comes from a glob or other pattern.
|
|
157
|
+
if target_path in lines:
|
|
158
|
+
lines.remove(target_path)
|
|
159
|
+
elif no_ignore_line not in lines:
|
|
160
|
+
lines.append(no_ignore_line)
|
|
161
|
+
else:
|
|
162
|
+
# Nested path: Git will not traverse into a directory excluded by a
|
|
163
|
+
# "dir/" pattern, so a bare "!dir/sub/file" negation has no effect.
|
|
164
|
+
# We need to:
|
|
165
|
+
# 1. Convert any "ancestor/" (or "ancestor") exclude to "ancestor/*"
|
|
166
|
+
# so that git traverses the directory while still ignoring its
|
|
167
|
+
# direct children by default.
|
|
168
|
+
# 2. Add "!ancestor/" un-ignore rules for each intermediate directory
|
|
169
|
+
# so git recurses into them.
|
|
170
|
+
# 3. Add "ancestor/*" re-ignore rules so that only explicitly
|
|
171
|
+
# un-ignored files within each intermediate directory are tracked.
|
|
172
|
+
# 4. Add the final "!target_path" negation for the specific file.
|
|
173
|
+
if target_path in lines:
|
|
174
|
+
lines.remove(target_path)
|
|
175
|
+
for i in range(1, len(path_parts)):
|
|
176
|
+
ancestor = "/".join(path_parts[:i])
|
|
177
|
+
reignore_glob = f"{ancestor}/*"
|
|
178
|
+
# Convert a directory-exclude pattern to a glob so git traverses it
|
|
179
|
+
if f"{ancestor}/" in lines:
|
|
180
|
+
idx = lines.index(f"{ancestor}/")
|
|
181
|
+
lines[idx] = reignore_glob
|
|
182
|
+
elif ancestor in lines:
|
|
183
|
+
idx = lines.index(ancestor)
|
|
184
|
+
lines[idx] = reignore_glob
|
|
185
|
+
# Un-ignore this intermediate directory so git recurses into it
|
|
186
|
+
no_ignore_dir = f"!{ancestor}/"
|
|
187
|
+
if no_ignore_dir not in lines:
|
|
188
|
+
lines.append(no_ignore_dir)
|
|
189
|
+
# Re-ignore everything inside this intermediate directory so that
|
|
190
|
+
# only explicitly un-ignored entries are tracked
|
|
191
|
+
if reignore_glob not in lines:
|
|
192
|
+
lines.append(reignore_glob)
|
|
193
|
+
if no_ignore_line not in lines:
|
|
194
|
+
lines.append(no_ignore_line)
|
|
157
195
|
with open(gitignore_path, "w") as f:
|
|
158
196
|
f.write(os.linesep.join(lines))
|
|
159
197
|
return True
|
|
@@ -317,6 +317,32 @@ class OverleafSyncPaths:
|
|
|
317
317
|
- self.files_in_overleaf_last_sync
|
|
318
318
|
)
|
|
319
319
|
|
|
320
|
+
@cached_property
|
|
321
|
+
def dvc_files(self) -> set[str]:
|
|
322
|
+
"""Files tracked by DVC within the Overleaf project folder.
|
|
323
|
+
|
|
324
|
+
These paths are relative to the project directory (i.e., relative to
|
|
325
|
+
the Overleaf repo root). Files tracked by DVC may not exist on disk if
|
|
326
|
+
they haven't been pulled, but should still be kept on Overleaf rather
|
|
327
|
+
than deleted.
|
|
328
|
+
"""
|
|
329
|
+
try:
|
|
330
|
+
import calkit.dvc
|
|
331
|
+
|
|
332
|
+
dvc_paths = calkit.dvc.list_paths(
|
|
333
|
+
wdir=str(self.main_repo.working_dir), recursive=True
|
|
334
|
+
)
|
|
335
|
+
except Exception as e:
|
|
336
|
+
warnings.warn(f"Could not list DVC files: {e}")
|
|
337
|
+
return set()
|
|
338
|
+
prefix = Path(self.path_in_project).as_posix().rstrip("/") + "/"
|
|
339
|
+
result = set()
|
|
340
|
+
for p in dvc_paths:
|
|
341
|
+
p_posix = Path(p).as_posix()
|
|
342
|
+
if p_posix.startswith(prefix):
|
|
343
|
+
result.add(p_posix[len(prefix) :])
|
|
344
|
+
return result
|
|
345
|
+
|
|
320
346
|
@cached_property
|
|
321
347
|
def files_to_keep_on_overleaf(self) -> set[str]:
|
|
322
348
|
"""Files that should be preserved on Overleaf.
|
|
@@ -324,9 +350,14 @@ class OverleafSyncPaths:
|
|
|
324
350
|
This includes:
|
|
325
351
|
1. All files being copied from local
|
|
326
352
|
2. Any files newly added on Overleaf since last sync
|
|
353
|
+
3. Any files tracked by DVC within the project path (these may not
|
|
354
|
+
exist on disk if not pulled, but should not be deleted from
|
|
355
|
+
Overleaf)
|
|
327
356
|
"""
|
|
328
357
|
return (
|
|
329
|
-
set(self.files_to_copy_to_overleaf)
|
|
358
|
+
set(self.files_to_copy_to_overleaf)
|
|
359
|
+
| self.newly_added_on_overleaf
|
|
360
|
+
| self.dvc_files
|
|
330
361
|
)
|
|
331
362
|
|
|
332
363
|
@cached_property
|
|
@@ -117,6 +117,20 @@ def to_dvc(
|
|
|
117
117
|
except Exception as e:
|
|
118
118
|
raise ValueError(f"Pipeline is not defined properly: {e}")
|
|
119
119
|
dvc_stages = {}
|
|
120
|
+
# Read existing dvc.yaml now so we can clean up stale .gitignore entries
|
|
121
|
+
# when stage outputs are renamed or removed
|
|
122
|
+
if write:
|
|
123
|
+
dvc_yaml_path = os.path.join(wdir, "dvc.yaml") if wdir else "dvc.yaml"
|
|
124
|
+
if os.path.isfile(dvc_yaml_path):
|
|
125
|
+
with open(dvc_yaml_path) as f:
|
|
126
|
+
existing_dvc_yaml = calkit.ryaml.load(f)
|
|
127
|
+
else:
|
|
128
|
+
existing_dvc_yaml = {}
|
|
129
|
+
if existing_dvc_yaml is None:
|
|
130
|
+
existing_dvc_yaml = {}
|
|
131
|
+
existing_dvc_stages = existing_dvc_yaml.get("stages", {})
|
|
132
|
+
else:
|
|
133
|
+
existing_dvc_stages = {}
|
|
120
134
|
# First, gather up any env lock paths we might need for DVC deps
|
|
121
135
|
used_envs = set(
|
|
122
136
|
[stage.inner_environment for stage in pipeline.stages.values()]
|
|
@@ -227,6 +241,27 @@ def to_dvc(
|
|
|
227
241
|
outputs += stage.notebook_outputs
|
|
228
242
|
elif stage.kind == "sbatch":
|
|
229
243
|
outputs.append(stage.log_output)
|
|
244
|
+
# Build the set of current output paths so we can detect stale
|
|
245
|
+
# .gitignore entries from the previous version of the stage
|
|
246
|
+
current_out_paths = set()
|
|
247
|
+
for out in outputs:
|
|
248
|
+
if isinstance(out, PathOutput):
|
|
249
|
+
current_out_paths.add(out.path)
|
|
250
|
+
elif isinstance(out, str):
|
|
251
|
+
current_out_paths.add(out)
|
|
252
|
+
# If this stage already existed, un-ignore any outputs that have
|
|
253
|
+
# been renamed or removed so .gitignore does not accumulate stale
|
|
254
|
+
# entries (e.g., after a capitalization change in the path)
|
|
255
|
+
old_stage = existing_dvc_stages.get(stage_name, {})
|
|
256
|
+
for old_out in old_stage.get("outs", []):
|
|
257
|
+
if isinstance(old_out, str):
|
|
258
|
+
old_path = old_out
|
|
259
|
+
elif isinstance(old_out, dict):
|
|
260
|
+
old_path = list(old_out.keys())[0]
|
|
261
|
+
else:
|
|
262
|
+
continue
|
|
263
|
+
if old_path not in current_out_paths:
|
|
264
|
+
calkit.git.ensure_path_is_not_ignored(repo, path=old_path)
|
|
230
265
|
# Deal with any gitignore changes necessary
|
|
231
266
|
for out in outputs:
|
|
232
267
|
if isinstance(out, PathOutput) and out.storage is None:
|
|
@@ -268,14 +303,8 @@ def to_dvc(
|
|
|
268
303
|
else:
|
|
269
304
|
dvc_stages[stage_name]["deps"].append(out)
|
|
270
305
|
if write:
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
dvc_yaml = calkit.ryaml.load(f)
|
|
274
|
-
else:
|
|
275
|
-
dvc_yaml = {}
|
|
276
|
-
if dvc_yaml is None:
|
|
277
|
-
dvc_yaml = {}
|
|
278
|
-
existing_stages = dvc_yaml.get("stages", {})
|
|
306
|
+
dvc_yaml = existing_dvc_yaml
|
|
307
|
+
existing_stages = existing_dvc_stages
|
|
279
308
|
for stage_name, stage in existing_stages.items():
|
|
280
309
|
# Skip private stages (ones whose names start with an underscore)
|
|
281
310
|
# and stages that are automatically generated
|
|
@@ -129,7 +129,7 @@ def create_citation_cff(
|
|
|
129
129
|
def ls_files() -> list[str]:
|
|
130
130
|
"""List all files to be released."""
|
|
131
131
|
repo = git.Repo()
|
|
132
|
-
git_files = repo.git.ls_files(".").
|
|
132
|
+
git_files = repo.git.ls_files(".", recurse_submodules=True).splitlines()
|
|
133
133
|
dvc_files = calkit.dvc.list_paths(recursive=True)
|
|
134
134
|
return git_files + dvc_files
|
|
135
135
|
|
|
@@ -181,6 +181,44 @@ def test_overleaf(tmp_dir):
|
|
|
181
181
|
subprocess.run(["calkit", "overleaf", "sync", "--verbose"], check=True)
|
|
182
182
|
print("Overleaf Git show after adding fig2 back:", ol_repo.git.show())
|
|
183
183
|
assert "ol-project/figs/fig2.txt" in ls_files(repo)
|
|
184
|
+
# Test that if a file is deleted from Git but added to DVC, it is not
|
|
185
|
+
# deleted from Overleaf (the file still logically exists in the DVC repo)
|
|
186
|
+
with open(
|
|
187
|
+
os.path.join(repo.working_dir, "ol-project", "figs", "fig3.txt"), "w"
|
|
188
|
+
) as f:
|
|
189
|
+
f.write("Fig3 created in main repo")
|
|
190
|
+
repo.git.add("ol-project/figs/fig3.txt")
|
|
191
|
+
repo.git.commit(["-m", "Add figure 3"])
|
|
192
|
+
assert "ol-project/figs/fig3.txt" in ls_files(repo)
|
|
193
|
+
subprocess.run(["calkit", "overleaf", "sync", "--verbose"], check=True)
|
|
194
|
+
ol_repo_git_show = ol_repo.git.show()
|
|
195
|
+
assert "diff --git a/figs/fig3.txt b/figs/fig3.txt" in ol_repo_git_show
|
|
196
|
+
# Now move from Git to DVC: first remove from Git index (keeping file on
|
|
197
|
+
# disk), then add to DVC so it gets moved to DVC cache
|
|
198
|
+
repo.git.rm(["--cached", "ol-project/figs/fig3.txt"])
|
|
199
|
+
subprocess.run(
|
|
200
|
+
["dvc", "add", "ol-project/figs/fig3.txt"],
|
|
201
|
+
check=True,
|
|
202
|
+
cwd=repo.working_dir,
|
|
203
|
+
)
|
|
204
|
+
# Commit the DVC pointer file (fig3.txt is now tracked by DVC, not Git)
|
|
205
|
+
repo.git.add("ol-project/figs/fig3.txt.dvc", "ol-project/figs/.gitignore")
|
|
206
|
+
repo.git.commit(["-m", "Move figure 3 from git to DVC"])
|
|
207
|
+
assert "ol-project/figs/fig3.txt" not in ls_files(repo)
|
|
208
|
+
# Also remove the local file to simulate the file not being pulled from
|
|
209
|
+
# DVC (i.e., only the DVC pointer exists locally, not the actual file)
|
|
210
|
+
fig3_path = os.path.join(
|
|
211
|
+
repo.working_dir, "ol-project", "figs", "fig3.txt"
|
|
212
|
+
)
|
|
213
|
+
if os.path.exists(fig3_path):
|
|
214
|
+
os.remove(fig3_path)
|
|
215
|
+
assert not os.path.exists(fig3_path)
|
|
216
|
+
subprocess.run(["calkit", "overleaf", "sync", "--verbose"], check=True)
|
|
217
|
+
ol_repo_git_show = ol_repo.git.show()
|
|
218
|
+
print("Git show in OL repo after moving fig3 to DVC:\n", ol_repo_git_show)
|
|
219
|
+
# The file should not have been deleted from Overleaf
|
|
220
|
+
assert "deleted file mode" not in ol_repo_git_show
|
|
221
|
+
assert "--- a/figs/fig3.txt" not in ol_repo_git_show
|
|
184
222
|
|
|
185
223
|
|
|
186
224
|
def test_extract_title_from_tex(tmp_dir):
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
"""Tests for the ``conda`` module."""
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
+
import shutil
|
|
4
5
|
import subprocess
|
|
5
6
|
|
|
6
7
|
import pytest
|
|
7
8
|
|
|
8
9
|
import calkit
|
|
9
|
-
from calkit.conda import
|
|
10
|
+
from calkit.conda import (
|
|
11
|
+
_check_list,
|
|
12
|
+
_check_single,
|
|
13
|
+
_get_pip_dependency_list,
|
|
14
|
+
_split_env_dependencies,
|
|
15
|
+
check_env,
|
|
16
|
+
)
|
|
10
17
|
|
|
11
18
|
ENV_NAME = "main"
|
|
12
19
|
|
|
@@ -37,6 +44,25 @@ def test_check_list():
|
|
|
37
44
|
assert not _check_list("pandas", installed, env_spec_dir=".", conda=False)
|
|
38
45
|
|
|
39
46
|
|
|
47
|
+
def test_split_env_dependencies():
|
|
48
|
+
dependencies = [
|
|
49
|
+
"python=3.12",
|
|
50
|
+
"pip",
|
|
51
|
+
"numpy=2",
|
|
52
|
+
{"pip": ["sqlalchemy==2.0.39"]},
|
|
53
|
+
]
|
|
54
|
+
conda_deps, pip_deps = _split_env_dependencies(dependencies)
|
|
55
|
+
assert conda_deps == ["python=3.12", "pip", "numpy=2"]
|
|
56
|
+
assert pip_deps == ["sqlalchemy==2.0.39"]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_get_pip_dependency_list():
|
|
60
|
+
dependencies = ["python=3.12", "pip", {"pip": "sqlalchemy==2.0.39"}]
|
|
61
|
+
pip_deps = _get_pip_dependency_list(dependencies)
|
|
62
|
+
assert pip_deps == ["sqlalchemy==2.0.39"]
|
|
63
|
+
assert dependencies[-1]["pip"] == ["sqlalchemy==2.0.39"]
|
|
64
|
+
|
|
65
|
+
|
|
40
66
|
def delete_env(name: str):
|
|
41
67
|
subprocess.check_call(["conda", "env", "remove", "-y", "-n", name])
|
|
42
68
|
|
|
@@ -262,7 +288,7 @@ def test_check_prefix_env(tmp_dir, conda_env_prefix):
|
|
|
262
288
|
)
|
|
263
289
|
|
|
264
290
|
|
|
265
|
-
def
|
|
291
|
+
def test_check_env_editable(tmp_dir, conda_env_name):
|
|
266
292
|
subprocess.check_call(["calkit", "init"])
|
|
267
293
|
# Create a dummy package named 'src' to install in editable mode
|
|
268
294
|
os.makedirs("src", exist_ok=True)
|
|
@@ -306,6 +332,98 @@ setup(
|
|
|
306
332
|
lock = calkit.ryaml.load(f)
|
|
307
333
|
pip_deps = lock["dependencies"][-1]["pip"]
|
|
308
334
|
assert "-e ." in pip_deps
|
|
335
|
+
# Now let's make sure we get proper output if the editable package is
|
|
336
|
+
# has an invalid pyproject.toml
|
|
337
|
+
os.remove("setup.py")
|
|
338
|
+
shutil.rmtree("src.egg-info")
|
|
339
|
+
toml_txt = """[build-system]
|
|
340
|
+
requires = ["setuptools>=61.0.0", "wheel", "setuptools-scm>=8"]
|
|
341
|
+
build-backend = "setuptools.build_meta"
|
|
342
|
+
|
|
343
|
+
[project]
|
|
344
|
+
name = "src-thing"
|
|
345
|
+
dynamic = ["version"]
|
|
346
|
+
authors = [
|
|
347
|
+
{name = "Someone"}
|
|
348
|
+
]
|
|
349
|
+
description = "Test"
|
|
350
|
+
|
|
351
|
+
dependencies = [
|
|
352
|
+
"numpy>=1.21",
|
|
353
|
+
"scipy>=1.7",
|
|
354
|
+
"pandas>=1.5",
|
|
355
|
+
"matplotlib>=3.5",
|
|
356
|
+
"h5netcdf>=0.12",
|
|
357
|
+
"h5py>=3.0",
|
|
358
|
+
"xarray>=2023.0",
|
|
359
|
+
"streamlit>=1.0"
|
|
360
|
+
]
|
|
361
|
+
|
|
362
|
+
[tool.setuptools]
|
|
363
|
+
package-dir = {"" = "src"}
|
|
364
|
+
packages = ["src-thing"]
|
|
365
|
+
|
|
366
|
+
[tool.setuptools.packages.find]
|
|
367
|
+
where = ["src"]
|
|
368
|
+
|
|
369
|
+
[tool.setuptools.package-data]
|
|
370
|
+
"src-thing" = [] # Explicitly state no package data
|
|
371
|
+
|
|
372
|
+
[tool.setuptools_scm]
|
|
373
|
+
local_scheme = "no-local-version"
|
|
374
|
+
fallback_version = "0+unknown"
|
|
375
|
+
"""
|
|
376
|
+
with open("pyproject.toml", "w") as f:
|
|
377
|
+
f.write(toml_txt)
|
|
378
|
+
with pytest.raises(Exception, match="Failed to load pyproject.toml"):
|
|
379
|
+
res = check_env()
|
|
380
|
+
# Fix it and make sure it runs with relaxed mode
|
|
381
|
+
toml_txt = """[build-system]
|
|
382
|
+
requires = ["setuptools>=61.0.0", "wheel", "setuptools-scm>=8"]
|
|
383
|
+
build-backend = "setuptools.build_meta"
|
|
384
|
+
|
|
385
|
+
[project]
|
|
386
|
+
name = "src-thing"
|
|
387
|
+
dynamic = ["version"]
|
|
388
|
+
authors = [
|
|
389
|
+
{name = "Someone"}
|
|
390
|
+
]
|
|
391
|
+
description = "Test"
|
|
392
|
+
|
|
393
|
+
dependencies = []
|
|
394
|
+
|
|
395
|
+
[tool.setuptools]
|
|
396
|
+
packages = ["src"]
|
|
397
|
+
|
|
398
|
+
[tool.setuptools_scm]
|
|
399
|
+
local_scheme = "no-local-version"
|
|
400
|
+
fallback_version = "0+unknown"
|
|
401
|
+
"""
|
|
402
|
+
with open("pyproject.toml", "w") as f:
|
|
403
|
+
f.write(toml_txt)
|
|
404
|
+
res = check_env(relaxed=True)
|
|
405
|
+
assert res.env_exists
|
|
406
|
+
assert res.env_needs_rebuild
|
|
407
|
+
assert res.env_needs_export
|
|
408
|
+
# Make sure we can import the editable package
|
|
409
|
+
os.makedirs("subdir")
|
|
410
|
+
subprocess.check_call(
|
|
411
|
+
[
|
|
412
|
+
"conda",
|
|
413
|
+
"run",
|
|
414
|
+
"-n",
|
|
415
|
+
conda_env_name,
|
|
416
|
+
"python",
|
|
417
|
+
"-c",
|
|
418
|
+
"import src; print('src file:', src.__file__);",
|
|
419
|
+
],
|
|
420
|
+
cwd="subdir",
|
|
421
|
+
)
|
|
422
|
+
# Check again and make sure we don't need a rebuild since the editable
|
|
423
|
+
# package is still valid
|
|
424
|
+
res = check_env(relaxed=True)
|
|
425
|
+
assert res.env_exists
|
|
426
|
+
assert not res.env_needs_rebuild
|
|
309
427
|
|
|
310
428
|
|
|
311
429
|
def test_find_conda_exe():
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Tests for ``calkit.git``."""
|
|
2
2
|
|
|
3
|
+
import os
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
|
|
5
6
|
import git
|
|
@@ -97,3 +98,53 @@ def test_ensure_path_is_not_ignored(tmp_dir):
|
|
|
97
98
|
gi_sub = f.read().splitlines()
|
|
98
99
|
assert "test.txt" not in gi_sub
|
|
99
100
|
assert "!test.txt" not in gi_sub
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_ensure_path_is_not_ignored_nested(tmp_dir):
|
|
104
|
+
"""Test that nested paths in ignored directories are correctly un-ignored.
|
|
105
|
+
|
|
106
|
+
When a parent directory is excluded with a trailing slash (e.g.,
|
|
107
|
+
``results/``), git will not traverse into it, so a simple negation like
|
|
108
|
+
``!results/StageName/end.json`` has no effect. The fix converts the
|
|
109
|
+
directory exclude to a glob pattern and adds intermediate un-ignore rules.
|
|
110
|
+
"""
|
|
111
|
+
repo = git.Repo.init()
|
|
112
|
+
# Ignore the entire results/ directory
|
|
113
|
+
with open(".gitignore", "w") as f:
|
|
114
|
+
f.write("results/\n")
|
|
115
|
+
# Create the nested target file so git can evaluate ignore status
|
|
116
|
+
os.makedirs("results/StageName", exist_ok=True)
|
|
117
|
+
with open("results/StageName/end.json", "w") as f:
|
|
118
|
+
f.write("{}")
|
|
119
|
+
# The file must be ignored before we try to un-ignore it
|
|
120
|
+
assert repo.ignored("results/StageName/end.json")
|
|
121
|
+
result = calkit.git.ensure_path_is_not_ignored(
|
|
122
|
+
repo, path="results/StageName/end.json"
|
|
123
|
+
)
|
|
124
|
+
assert result is True
|
|
125
|
+
with open(".gitignore") as f:
|
|
126
|
+
lines = f.read().splitlines()
|
|
127
|
+
# The plain directory exclude should be replaced with a glob
|
|
128
|
+
assert "results/" not in lines
|
|
129
|
+
assert "results/*" in lines
|
|
130
|
+
# Intermediate directory must be un-ignored so git traverses into it
|
|
131
|
+
assert "!results/StageName/" in lines
|
|
132
|
+
# The intermediate directory's other contents must be re-ignored
|
|
133
|
+
assert "results/StageName/*" in lines
|
|
134
|
+
# The specific file must be explicitly un-ignored
|
|
135
|
+
assert "!results/StageName/end.json" in lines
|
|
136
|
+
# Verify git no longer considers the target file as ignored
|
|
137
|
+
assert not repo.ignored("results/StageName/end.json")
|
|
138
|
+
# Other files in results/ must still be ignored
|
|
139
|
+
with open("results/other.txt", "w") as f:
|
|
140
|
+
f.write("other")
|
|
141
|
+
assert repo.ignored("results/other.txt")
|
|
142
|
+
# Other files in the intermediate directory must still be ignored
|
|
143
|
+
with open("results/StageName/other.json", "w") as f:
|
|
144
|
+
f.write("{}")
|
|
145
|
+
assert repo.ignored("results/StageName/other.json")
|
|
146
|
+
# Calling again should be a no-op (path is no longer ignored)
|
|
147
|
+
result2 = calkit.git.ensure_path_is_not_ignored(
|
|
148
|
+
repo, path="results/StageName/end.json"
|
|
149
|
+
)
|
|
150
|
+
assert result2 is None
|