calkit-python 0.41.7__tar.gz → 0.41.9__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.7 → calkit_python-0.41.9}/.gitignore +1 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/PKG-INFO +1 -1
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/cli/main/core.py +10 -1
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/cli/new.py +80 -55
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/core.py +8 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/invenio.py +20 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/pipeline.py +113 -2
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/releases.py +103 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/cli/main/test_subprojects.py +53 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/cli/test_new.py +331 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/test_core.py +19 -0
- calkit_python-0.41.9/calkit/tests/test_invenio.py +35 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/test_pipeline.py +111 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/test_releases.py +85 -0
- calkit_python-0.41.7/calkit/tests/test_invenio.py +0 -15
- {calkit_python-0.41.7 → calkit_python-0.41.9}/.claude-plugin/marketplace.json +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/.gitattributes +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/.pre-commit-config.yaml +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/.prettierignore +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/.python-version +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/.vscode/launch.json +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/.vscode/tasks.json +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/.yarnrc.yml +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/AGENTS.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/CITATION.cff +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/CODE_OF_CONDUCT.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/CONTRIBUTING.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/LICENSE +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/Makefile +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/README.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/agent-plugin/.claude-plugin/plugin.json +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/agent-plugin/skills/add-pipeline-stage/SKILL.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/agent-plugin/skills/conventions/SKILL.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/agent-plugin/skills/create-pipeline/SKILL.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/babel.config.js +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/__init__.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/__main__.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/calc.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/check.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/cli/__init__.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/cli/check.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/cli/cloud.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/cli/config.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/cli/core.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/cli/describe.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/cli/dev.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/cli/import_.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/cli/latex.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/cli/list.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/cli/main/__init__.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/cli/main/xr.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/cli/notebooks.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/cli/office.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/cli/overleaf.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/cli/scheduler.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/cli/update.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/cloud.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/conda.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/config.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/datasets.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/dependencies.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/detect.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/docker.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/dvc/__init__.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/dvc/core.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/dvc/zip.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/environments.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/fs.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/git.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/github.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/gui.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/install.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/julia.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/jupyter.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/jupyterlab/__init__.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/jupyterlab/routes.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/labextension/package.json +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/labextension/schemas/calkit/package.json.orig +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/labextension/schemas/calkit/plugin.json +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/labextension/static/502.9a2c5772a15466e923ef.js +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/labextension/static/695.2c41003a452d43d2b358.js +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/labextension/static/867.a42a046aa5108f54f8fb.js +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/labextension/static/909.e3f9cc3408834a7fdcc3.js +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/labextension/static/946.050af2abf7845cfbdbd2.js +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/labextension/static/946.050af2abf7845cfbdbd2.js.LICENSE.txt +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/labextension/static/b2f1c3efe70cb539d121.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/labextension/static/remoteEntry.65469af996e7a96aa983.js +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/labextension/static/style.js +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/labextension/static/third-party-licenses.json +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/licenses.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/magics.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/matlab.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/models/__init__.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/models/core.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/models/io.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/models/iteration.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/models/pipeline.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/notebooks.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/office.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/ops.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/overleaf.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/server.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/templates/__init__.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/templates/core.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/templates/latex/__init__.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/templates/latex/article/paper.tex +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/templates/latex/core.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/templates/latex/jfm/jfm.bst +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/templates/latex/jfm/jfm.cls +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/templates/latex/jfm/lineno-FLM.sty +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/templates/latex/jfm/paper.tex +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/templates/latex/jfm/upmath.sty +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/__init__.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/cli/__init__.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/cli/main/__init__.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/cli/main/test_core.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/cli/main/test_xr.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/cli/test_check.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/cli/test_cloud.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/cli/test_config.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/cli/test_import.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/cli/test_latex.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/cli/test_list.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/cli/test_notebooks.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/cli/test_overleaf.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/cli/test_scheduler.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/cli/test_update.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/dvc/__init__.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/dvc/test_core.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/dvc/test_zip.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/jupyterlab/__init__.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/jupyterlab/test_routes.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/models/__init__.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/models/test_iteration.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/models/test_pipeline.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/test_calc.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/test_check.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/test_cloud.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/test_conda.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/test_dependencies.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/test_detect.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/test_docker.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/test_environments.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/test_fs.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/test_git.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/test_install.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/test_julia.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/test_jupyter.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/test_licenses.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/test_magics.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/test_matlab.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/test_notebooks.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/calkit/tests/test_templates.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/conftest.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/CNAME +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/ai-tools.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/apps.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/calculations.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/calkit-yaml.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/cli-reference.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/cloud-integration.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/datasets.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/dependencies.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/environments.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/examples.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/governance.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/help.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/hpc.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/img/c-to-the-k-white.svg +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/img/calkit-fragmentation-compendium.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/img/calkit-no-bg.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/img/connect-zenodo.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/img/jupyterlab/all-green.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/img/jupyterlab/collect-data-stale.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/img/jupyterlab/new-env.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/img/jupyterlab/new-notebook.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/img/jupyterlab/pipeline-badge.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/img/jupyterlab-params.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/img/plos-osi-code-2024-03.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/img/vscode-nb-params.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/index.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/installation.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/jupyterlab.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/local-server.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/notebooks.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/overleaf.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/pipeline/index.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/pipeline/manual-steps.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/pipeline/running-and-logging.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/quickstart.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/references.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/releases.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/reproducibility.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/subprojects.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/adding-latex-pub-docker.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/conda-envs.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/existing-project.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/first-project.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/github-actions.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/actions-repo-secrets.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/latex-codespaces/building-codespace.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/latex-codespaces/codespaces-secrets-2.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/latex-codespaces/editor-split.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/latex-codespaces/go-to-linked-code.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/latex-codespaces/issue-from-selection.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/latex-codespaces/new-project.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/latex-codespaces/new-pub-2.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/latex-codespaces/new-token.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/latex-codespaces/paper.tex.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/latex-codespaces/project-home-3.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/latex-codespaces/push.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/latex-codespaces/stage.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/office/anakin-excel.jpg +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/office/chart-more-rows.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/office/create-project.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/office/elsevier-research-data-guidelines.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/office/excel-chart.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/office/excel-data.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/office/insert-link-to-file.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/office/needs-clone.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/office/new-stage.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/office/phd-comics-version-control.webp +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/office/pipeline-out-of-date.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/office/status-more-rows.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/office/uncommitted-changes.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/office/untracked-data.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/office/updated-publication.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/office/word-to-pdf-stage-2.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/office/workflow-page.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/openfoam/clone.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/openfoam/create-project.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/openfoam/datasets-page.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/openfoam/figure-on-website-updated.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/openfoam/figure-on-website.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/openfoam/new-token.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/openfoam/reclone.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/openfoam/status-after-import-dataset.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/quick-actions.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/run-proc.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/vscode-slurm-notebook/create-calkit-env.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/vscode-slurm-notebook/create-inner-env.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/vscode-slurm-notebook/create-new-calkit-env.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/vscode-slurm-notebook/select-calkit-env.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/vscode-slurm-notebook/slurm-job-running.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/vscode-slurm-notebook/slurm-launch-options.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/img/vscode-slurm-notebook/starting-slurm-job.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/index.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/jupyterlab.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/latex-codespaces.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/matlab.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/notebook-pipeline.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/office.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/openfoam.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/procedures.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/tutorials/vscode-slurm-notebook.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/docs/version-control.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/flake.lock +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/flake.nix +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/install.json +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/jest.config.js +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/jupyter-config/server-config/calkit.json +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/mkdocs.yml +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/package.json +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/pyproject.toml +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/schema/plugin.json +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/scripts/generate-docs-references.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/scripts/install.ps1 +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/scripts/install.sh +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/scripts/make-calk9.sh +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/scripts/sync-docs.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/src/__tests__/useQueries.spec.ts +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/src/calkit-config.ts +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/src/cell-output-marker.ts +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/src/components/commit-dialog.tsx +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/src/components/environment-editor.tsx +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/src/components/notebook-registration.tsx +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/src/components/notebook-toolbar.tsx +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/src/components/pipeline-status-bar.tsx +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/src/components/project-info-editor.tsx +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/src/components/sidebar-settings.tsx +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/src/components/sidebar.tsx +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/src/components/stage-editor.tsx +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/src/feature-flags.ts +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/src/file-browser-menu.ts +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/src/hooks/__tests__/useQueries.test.tsx +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/src/hooks/useQueries.ts +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/src/icons.ts +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/src/index.ts +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/src/io-tracker.ts +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/src/pipeline-state.ts +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/src/queryClient.ts +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/src/request.ts +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/src/shims-mainmenu.d.ts +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/style/base.css +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/style/cell-output-marker.css +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/style/environment-editor.css +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/style/environment-selector.css +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/style/img/calkit-no-bg.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/style/index.css +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/style/index.js +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/style/notebook-toolbar.css +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/style/pipeline-status-bar.css +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/style/sidebar.css +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/test/dvc-md5-dir/osx-arm64.txt +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/test/nb-julia.ipynb +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/test/nb-params.ipynb +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/test/nb-subdir.ipynb +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/test/pipeline.ipynb +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/test/script.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/test/test-log.log +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/tsconfig.json +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/tsconfig.test.json +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/ui-tests/.gitignore +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/ui-tests/README.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/ui-tests/jupyter_server_test_config.py +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/ui-tests/package.json +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/ui-tests/playwright.config.js +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/ui-tests/tests/calkit.spec.ts +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/ui-tests/yarn.lock +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/uv.lock +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/vscode-ext/.gitignore +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/vscode-ext/.vscodeignore +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/vscode-ext/CHANGELOG.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/vscode-ext/LICENSE +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/vscode-ext/README.md +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/vscode-ext/images/calkit-no-bg.png +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/vscode-ext/package-lock.json +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/vscode-ext/package.json +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/vscode-ext/scripts/set-proposed-api.js +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/vscode-ext/src/environments.ts +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/vscode-ext/src/extension.ts +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/vscode-ext/src/notebooks.ts +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/vscode-ext/src/test/environments.test.ts +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/vscode-ext/src/test/kernel-selection.test.ts +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/vscode-ext/src/test/notebooks.test.ts +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/vscode-ext/src/types.ts +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/vscode-ext/tsconfig.json +0 -0
- {calkit_python-0.41.7 → calkit_python-0.41.9}/yarn.lock +0 -0
|
@@ -2418,7 +2418,16 @@ def run_in_env(
|
|
|
2418
2418
|
)
|
|
2419
2419
|
# TODO: Prefix should only be in the env file or calkit.yaml, not both?
|
|
2420
2420
|
prefix = env.get("prefix")
|
|
2421
|
-
|
|
2421
|
+
# Conda is often not on the PATH (especially on Windows), so search
|
|
2422
|
+
# typical install locations rather than relying on the bare name,
|
|
2423
|
+
# which would fail with a confusing FileNotFoundError traceback.
|
|
2424
|
+
conda_exe = calkit.conda.find_conda_exe()
|
|
2425
|
+
if conda_exe is None:
|
|
2426
|
+
raise_error(
|
|
2427
|
+
"Cannot find Conda executable; "
|
|
2428
|
+
"ensure Conda is installed and on the PATH"
|
|
2429
|
+
)
|
|
2430
|
+
conda_cmd = [conda_exe, "run"]
|
|
2422
2431
|
if prefix is not None:
|
|
2423
2432
|
conda_cmd += ["--prefix", os.path.abspath(prefix)]
|
|
2424
2433
|
else:
|
|
@@ -7,6 +7,7 @@ import os
|
|
|
7
7
|
import pathlib
|
|
8
8
|
import shutil
|
|
9
9
|
import subprocess
|
|
10
|
+
import time
|
|
10
11
|
from enum import Enum
|
|
11
12
|
|
|
12
13
|
import typer
|
|
@@ -3142,7 +3143,7 @@ def new_release(
|
|
|
3142
3143
|
)
|
|
3143
3144
|
with open("LICENSE", "w") as f:
|
|
3144
3145
|
f.write(license_txt)
|
|
3145
|
-
|
|
3146
|
+
repo.git.add("LICENSE")
|
|
3146
3147
|
license_ids = ["mit", "cc-by-4.0"]
|
|
3147
3148
|
else:
|
|
3148
3149
|
typer.echo(f"Detecting license(s) from {license_file}")
|
|
@@ -3325,22 +3326,23 @@ def new_release(
|
|
|
3325
3326
|
f"/records/{record_id}/draft/files/{filename}/commit",
|
|
3326
3327
|
service=to, # type: ignore
|
|
3327
3328
|
)
|
|
3328
|
-
#
|
|
3329
|
-
if
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3329
|
+
# Reserve a DOI on the draft before publishing. The publish action's
|
|
3330
|
+
# response only echoes back the DOI under "pids" if one was reserved
|
|
3331
|
+
# on the draft first (otherwise "pids" comes back empty), so always
|
|
3332
|
+
# reserve here to get a stable identifier.
|
|
3333
|
+
typer.echo(f"Reserving DOI for {to} draft record ID {record_id}")
|
|
3334
|
+
doi_resp = calkit.invenio.post(
|
|
3335
|
+
f"/records/{record_id}/draft/pids/doi",
|
|
3336
|
+
service=to, # type: ignore
|
|
3337
|
+
)
|
|
3338
|
+
if verbose:
|
|
3339
|
+
typer.echo(f"DOI reservation response:\n{doi_resp}")
|
|
3340
|
+
doi = calkit.invenio.extract_doi(doi_resp)
|
|
3341
|
+
if doi is None:
|
|
3342
|
+
raise_error(
|
|
3343
|
+
f"Failed to reserve DOI for {to} draft record {record_id}"
|
|
3335
3344
|
)
|
|
3336
|
-
|
|
3337
|
-
typer.echo(f"DOI response for draft:\n{doi_resp}")
|
|
3338
|
-
try:
|
|
3339
|
-
doi = doi_resp["pids"]["doi"]["identifier"]
|
|
3340
|
-
except KeyError:
|
|
3341
|
-
doi = doi_resp["doi"]
|
|
3342
|
-
except Exception as e:
|
|
3343
|
-
raise_error(f"Failed to reserve DOI for draft: {e}")
|
|
3345
|
+
if draft_only:
|
|
3344
3346
|
url = f"https://doi.org/{doi}"
|
|
3345
3347
|
typer.echo(f"Created {to} draft with reserved DOI: {doi}")
|
|
3346
3348
|
else:
|
|
@@ -3350,8 +3352,37 @@ def new_release(
|
|
|
3350
3352
|
f"/records/{record_id}/draft/actions/publish",
|
|
3351
3353
|
service=to, # type: ignore
|
|
3352
3354
|
)
|
|
3355
|
+
if verbose:
|
|
3356
|
+
typer.echo(f"Publish response:\n{invenio_dep}")
|
|
3353
3357
|
record_id = invenio_dep["id"]
|
|
3354
|
-
|
|
3358
|
+
# Prefer the DOI from the publish response, but fall back to the
|
|
3359
|
+
# reserved DOI (and a fetch of the published record) since the
|
|
3360
|
+
# publish action returns 202 and may not echo "pids" immediately.
|
|
3361
|
+
# Polling failures are tolerated: we already have the reserved DOI,
|
|
3362
|
+
# so a transient error while the record settles should not abort
|
|
3363
|
+
# the release.
|
|
3364
|
+
published_doi = calkit.invenio.extract_doi(invenio_dep)
|
|
3365
|
+
for _ in range(10):
|
|
3366
|
+
if published_doi is not None:
|
|
3367
|
+
break
|
|
3368
|
+
time.sleep(1)
|
|
3369
|
+
try:
|
|
3370
|
+
record = calkit.invenio.get(
|
|
3371
|
+
f"/records/{record_id}",
|
|
3372
|
+
service=to, # type: ignore
|
|
3373
|
+
)
|
|
3374
|
+
except Exception as e:
|
|
3375
|
+
if verbose:
|
|
3376
|
+
typer.echo(f"Polling for published DOI failed: {e}")
|
|
3377
|
+
continue
|
|
3378
|
+
published_doi = calkit.invenio.extract_doi(record)
|
|
3379
|
+
if published_doi is not None:
|
|
3380
|
+
doi = published_doi
|
|
3381
|
+
else:
|
|
3382
|
+
typer.echo(
|
|
3383
|
+
"Could not confirm minted DOI from published record; "
|
|
3384
|
+
f"falling back to reserved DOI {doi}"
|
|
3385
|
+
)
|
|
3355
3386
|
url = f"https://doi.org/{doi}"
|
|
3356
3387
|
typer.echo(f"Published to {to} with DOI: {doi}")
|
|
3357
3388
|
else:
|
|
@@ -3363,38 +3394,23 @@ def new_release(
|
|
|
3363
3394
|
doi_base_url = calkit.releases.SERVICES[to]["url"]
|
|
3364
3395
|
doi_md = (
|
|
3365
3396
|
f"[]"
|
|
3366
|
-
f"(https://
|
|
3397
|
+
f"(https://doi.org/{doi})"
|
|
3367
3398
|
)
|
|
3368
3399
|
if os.path.isfile("README.md"):
|
|
3369
3400
|
with open("README.md") as f:
|
|
3370
3401
|
readme_txt = f.read()
|
|
3371
3402
|
else:
|
|
3372
|
-
readme_txt =
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
first_content_line_index = None
|
|
3376
|
-
for n, line in enumerate(existing_lines):
|
|
3377
|
-
if line.startswith(doi_md[:6]):
|
|
3378
|
-
pass # Skip DOI lines
|
|
3379
|
-
else:
|
|
3380
|
-
if (
|
|
3381
|
-
n != 0
|
|
3382
|
-
and line.strip()
|
|
3383
|
-
and first_content_line_index is None
|
|
3384
|
-
):
|
|
3385
|
-
first_content_line_index = len(new_lines)
|
|
3386
|
-
new_lines.append(line)
|
|
3387
|
-
# Ensure first 3 lines are title, blank, DOI lines
|
|
3388
|
-
new_lines = (
|
|
3389
|
-
[new_lines[0]]
|
|
3390
|
-
+ ["", doi_md, ""]
|
|
3391
|
-
+ new_lines[first_content_line_index:]
|
|
3403
|
+
readme_txt = ""
|
|
3404
|
+
readme_txt = calkit.releases.add_doi_badge_to_readme(
|
|
3405
|
+
readme_txt, badge=doi_md, title=title
|
|
3392
3406
|
)
|
|
3393
|
-
readme_txt = "\n".join(new_lines)
|
|
3394
3407
|
if not dry_run:
|
|
3395
3408
|
with open("README.md", "w") as f:
|
|
3396
3409
|
f.write(readme_txt)
|
|
3397
|
-
|
|
3410
|
+
# Stage only after the file is closed; otherwise the buffered
|
|
3411
|
+
# contents may not be flushed to disk yet and Git would stage an
|
|
3412
|
+
# empty file
|
|
3413
|
+
repo.git.add("README.md")
|
|
3398
3414
|
else:
|
|
3399
3415
|
typer.echo(f"Would have updated README.md to:\n{readme_txt}")
|
|
3400
3416
|
# Create Git tag
|
|
@@ -3449,11 +3465,13 @@ def new_release(
|
|
|
3449
3465
|
references = reference_collections[0]
|
|
3450
3466
|
ref_path = references.get("path", "references.bib")
|
|
3451
3467
|
try:
|
|
3468
|
+
# Read the existing references as raw text so we can append the new
|
|
3469
|
+
# entry without reformatting the user's existing entries
|
|
3452
3470
|
if os.path.isfile(ref_path):
|
|
3453
3471
|
with open(ref_path) as f:
|
|
3454
|
-
|
|
3472
|
+
existing_text = f.read()
|
|
3455
3473
|
else:
|
|
3456
|
-
|
|
3474
|
+
existing_text = ""
|
|
3457
3475
|
bibtex_doi = doi
|
|
3458
3476
|
if bibtex_doi is None and dry_run:
|
|
3459
3477
|
mock_suffix = "".join(
|
|
@@ -3473,31 +3491,38 @@ def new_release(
|
|
|
3473
3491
|
if not new_entries:
|
|
3474
3492
|
raise ValueError("Failed to parse generated BibTeX entry")
|
|
3475
3493
|
new_entry = new_entries[0]
|
|
3476
|
-
# Search through entries for
|
|
3477
|
-
# there is a match
|
|
3494
|
+
# Search through existing entries for any with the same DOI, and
|
|
3495
|
+
# replace them (by citation key) if there is a match. Tolerate parse
|
|
3496
|
+
# errors here (e.g., non-standard or temporarily-invalid BibTeX) by
|
|
3497
|
+
# skipping replacement detection and still appending the new entry.
|
|
3478
3498
|
new_doi = new_entry.get("doi")
|
|
3479
|
-
|
|
3499
|
+
replace_ids = []
|
|
3480
3500
|
if new_doi:
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3501
|
+
try:
|
|
3502
|
+
existing_entries = bibtexparser.loads(existing_text).entries
|
|
3503
|
+
except Exception as e:
|
|
3504
|
+
warn(f"Could not parse existing references to dedupe: {e}")
|
|
3505
|
+
existing_entries = []
|
|
3506
|
+
replace_ids = [
|
|
3507
|
+
entry["ID"]
|
|
3508
|
+
for entry in existing_entries
|
|
3484
3509
|
if entry.get("doi") == new_doi
|
|
3485
3510
|
]
|
|
3486
|
-
if
|
|
3511
|
+
if replace_ids:
|
|
3487
3512
|
typer.echo(
|
|
3488
3513
|
"Found matching DOI in existing references "
|
|
3489
|
-
f"({len(
|
|
3490
|
-
f"{'y' if len(
|
|
3514
|
+
f"({len(replace_ids)} entr"
|
|
3515
|
+
f"{'y' if len(replace_ids) == 1 else 'ies'}); "
|
|
3491
3516
|
"replacing"
|
|
3492
3517
|
)
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3518
|
+
new_text = calkit.releases.add_bibtex_entry(
|
|
3519
|
+
existing_text, invenio_bibtex, replace_ids=replace_ids
|
|
3520
|
+
)
|
|
3496
3521
|
if dry_run:
|
|
3497
3522
|
typer.echo(f"Would write updated references to {ref_path}")
|
|
3498
3523
|
else:
|
|
3499
3524
|
with open(ref_path, "w") as f:
|
|
3500
|
-
|
|
3525
|
+
f.write(new_text)
|
|
3501
3526
|
repo.git.add(ref_path)
|
|
3502
3527
|
except Exception as e:
|
|
3503
3528
|
warn(f"Failed to add to references: {e}")
|
|
@@ -341,6 +341,14 @@ def check_dep_exists(
|
|
|
341
341
|
return True
|
|
342
342
|
if system_info is not None and system_info.get(f"{name}_version"):
|
|
343
343
|
return True
|
|
344
|
+
# Conda and mamba are frequently installed but not on the PATH (most
|
|
345
|
+
# commonly on Windows), so search their typical install locations
|
|
346
|
+
# rather than relying on the bare name being directly executable.
|
|
347
|
+
if name in ("conda", "mamba"):
|
|
348
|
+
from calkit.conda import find_conda_exe, find_mamba_exe
|
|
349
|
+
|
|
350
|
+
exe = find_conda_exe() if name == "conda" else find_mamba_exe()
|
|
351
|
+
return exe is not None
|
|
344
352
|
cmd = [name]
|
|
345
353
|
# Executables with non-conventional CLIs
|
|
346
354
|
if name == "matlab":
|
|
@@ -107,6 +107,26 @@ put = partial(_request, "put")
|
|
|
107
107
|
delete = partial(_request, "delete")
|
|
108
108
|
|
|
109
109
|
|
|
110
|
+
def extract_doi(record: dict) -> str | None:
|
|
111
|
+
"""Extract the DOI identifier from an InvenioRDM record or response.
|
|
112
|
+
|
|
113
|
+
Depending on the endpoint and whether the DOI has been minted yet, the
|
|
114
|
+
identifier can live in different places, so check all known locations and
|
|
115
|
+
return ``None`` if it isn't present yet.
|
|
116
|
+
"""
|
|
117
|
+
pids = record.get("pids")
|
|
118
|
+
if isinstance(pids, dict):
|
|
119
|
+
doi = pids.get("doi")
|
|
120
|
+
if isinstance(doi, dict) and doi.get("identifier"):
|
|
121
|
+
return doi["identifier"]
|
|
122
|
+
if record.get("doi"):
|
|
123
|
+
return record["doi"]
|
|
124
|
+
metadata = record.get("metadata")
|
|
125
|
+
if isinstance(metadata, dict) and metadata.get("doi"):
|
|
126
|
+
return metadata["doi"]
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
|
|
110
130
|
def get_download_urls(
|
|
111
131
|
record_id: int | str,
|
|
112
132
|
service: ServiceName = DEFAULT_SERVICE,
|
|
@@ -96,10 +96,14 @@ class PipelineStatus(BaseModel):
|
|
|
96
96
|
def always_run_stage_names(self) -> list[str]:
|
|
97
97
|
# Only list stages whose sole change indicator is always_run; stages
|
|
98
98
|
# that also have real changes are reported under stale_stage_names.
|
|
99
|
+
# Subproject stages are excluded: their always-changed status is a
|
|
100
|
+
# delegation detail, not a user-meaningful always-run stage of the
|
|
101
|
+
# parent project.
|
|
99
102
|
return [
|
|
100
103
|
name
|
|
101
104
|
for name, stage in self.stale_stages.items()
|
|
102
105
|
if stage.always_run
|
|
106
|
+
and not stage.is_subproject
|
|
103
107
|
and not stage.modified_command
|
|
104
108
|
and not stage.modified_inputs
|
|
105
109
|
and not stage.modified_outputs
|
|
@@ -145,6 +149,12 @@ class StaleStage(BaseModel):
|
|
|
145
149
|
modified_outputs: list[str] = Field(default_factory=list)
|
|
146
150
|
modified_command: bool = False
|
|
147
151
|
always_run: bool = False
|
|
152
|
+
# True for stages that originate from a subproject (either an individual
|
|
153
|
+
# "{sp}:{stage}" stage or a kept "{sp} (subproject)" wrapper). Subproject
|
|
154
|
+
# wrapper stages are marked always-changed purely as a delegation
|
|
155
|
+
# mechanism, so they should not be advertised as always-run stages of the
|
|
156
|
+
# parent project.
|
|
157
|
+
is_subproject: bool = False
|
|
148
158
|
|
|
149
159
|
@staticmethod
|
|
150
160
|
def _as_path_list(paths: object) -> list[str]:
|
|
@@ -227,6 +237,7 @@ class StaleStage(BaseModel):
|
|
|
227
237
|
status_data: list | dict | str,
|
|
228
238
|
configured_outputs: list[str] | None = None,
|
|
229
239
|
path_prefix: str | None = None,
|
|
240
|
+
is_subproject: bool = False,
|
|
230
241
|
) -> "StaleStage":
|
|
231
242
|
modified_inputs = []
|
|
232
243
|
output_paths = []
|
|
@@ -312,6 +323,7 @@ class StaleStage(BaseModel):
|
|
|
312
323
|
modified_outputs=modified_outputs,
|
|
313
324
|
modified_command=modified_command,
|
|
314
325
|
always_run=always_run,
|
|
326
|
+
is_subproject=is_subproject,
|
|
315
327
|
)
|
|
316
328
|
|
|
317
329
|
|
|
@@ -546,6 +558,74 @@ def collapse_dvc_stages(
|
|
|
546
558
|
return stage
|
|
547
559
|
|
|
548
560
|
|
|
561
|
+
def _status_target_matches(
|
|
562
|
+
target: str,
|
|
563
|
+
display_name: str,
|
|
564
|
+
bare_name: str,
|
|
565
|
+
subproject: str | None,
|
|
566
|
+
stale_stage: StaleStage,
|
|
567
|
+
) -> bool:
|
|
568
|
+
"""Return True if a status ``target`` selects this stage.
|
|
569
|
+
|
|
570
|
+
Because the full DVC status is now computed and filtered locally (rather
|
|
571
|
+
than asking DVC to filter by target), this is where each ``target`` passed
|
|
572
|
+
to :func:`get_status` is resolved against a single stage. A ``target`` may
|
|
573
|
+
be a stage name in any of calkit's forms (``stage``, ``dvc.yaml:stage``,
|
|
574
|
+
``subproject:stage``, or a whole ``subproject``) or a repo path, which is
|
|
575
|
+
matched against the stage's stale/modified inputs and outputs.
|
|
576
|
+
|
|
577
|
+
Iterated/matrix stages use a ``name@param`` key in DVC status, so the base
|
|
578
|
+
name (the part before ``@``) is matched too — targeting the base name
|
|
579
|
+
selects every iteration, consistent with how ``calkit run`` expands matrix
|
|
580
|
+
targets.
|
|
581
|
+
"""
|
|
582
|
+
base_name = bare_name.split("@")[0]
|
|
583
|
+
# Stage-name matching is done against the raw target so it covers all of
|
|
584
|
+
# calkit's stage-target forms as well as DVC's native ``dvc.yaml:stage``
|
|
585
|
+
# and ``<subproject>/dvc.yaml:stage`` forms (the latter is what
|
|
586
|
+
# translate_run_targets emits for inline subprojects).
|
|
587
|
+
stage_aliases = {display_name, bare_name, base_name}
|
|
588
|
+
if subproject is not None:
|
|
589
|
+
subproject_name = Path(subproject).name
|
|
590
|
+
if target in {subproject, subproject_name}:
|
|
591
|
+
return display_name.startswith(
|
|
592
|
+
f"{subproject}:"
|
|
593
|
+
) or display_name == (f"{subproject} (subproject)")
|
|
594
|
+
stage_aliases |= {
|
|
595
|
+
f"{subproject}:{bare_name}",
|
|
596
|
+
f"{subproject_name}:{bare_name}",
|
|
597
|
+
f"{subproject}:{base_name}",
|
|
598
|
+
f"{subproject_name}:{base_name}",
|
|
599
|
+
f"{subproject}/dvc.yaml:{bare_name}",
|
|
600
|
+
f"{subproject}/dvc.yaml:{base_name}",
|
|
601
|
+
}
|
|
602
|
+
else:
|
|
603
|
+
stage_aliases |= {
|
|
604
|
+
f"dvc.yaml:{bare_name}",
|
|
605
|
+
f"dvc.yaml:{base_name}",
|
|
606
|
+
}
|
|
607
|
+
if target in stage_aliases:
|
|
608
|
+
return True
|
|
609
|
+
# Otherwise treat the target as a repo path and match it against the
|
|
610
|
+
# stage's stale/modified inputs and outputs. A target is path-like if it
|
|
611
|
+
# exists on disk or contains a separator; this keeps root-level paths
|
|
612
|
+
# without separators (e.g. ``data`` or ``out.csv``) selectable, which a
|
|
613
|
+
# separator-only heuristic would miss. A target that looks like a stage
|
|
614
|
+
# selector (contains ``:``) but matched no stage above is not a path.
|
|
615
|
+
if ":" in target and not os.path.exists(target):
|
|
616
|
+
return False
|
|
617
|
+
is_pathlike = os.path.exists(target) or "/" in target or "\\" in target
|
|
618
|
+
if not is_pathlike:
|
|
619
|
+
return False
|
|
620
|
+
norm_target = Path(target).as_posix().rstrip("/")
|
|
621
|
+
candidate_paths = (
|
|
622
|
+
stale_stage.stale_outputs
|
|
623
|
+
+ stale_stage.modified_inputs
|
|
624
|
+
+ stale_stage.modified_outputs
|
|
625
|
+
)
|
|
626
|
+
return any(_paths_overlap(norm_target, p) for p in candidate_paths)
|
|
627
|
+
|
|
628
|
+
|
|
549
629
|
def get_status(
|
|
550
630
|
ck_info: dict | None = None,
|
|
551
631
|
targets: list[str] | None = None,
|
|
@@ -628,7 +708,11 @@ def get_status(
|
|
|
628
708
|
dvc_repo = calkit.dvc.get_dvc_repo()
|
|
629
709
|
# calkit.dvc.core installs a filter on dvc.repo.status that drops
|
|
630
710
|
# the noisy frozen-stage warning, so the call here stays quiet.
|
|
631
|
-
|
|
711
|
+
# Ask DVC for the complete stale set, then filter locally.
|
|
712
|
+
# DVC's own target filtering can miss stale propagation from
|
|
713
|
+
# isolated subprojects, which makes targeted status disagree with
|
|
714
|
+
# the full-project status view.
|
|
715
|
+
raw_status = dvc_repo.status()
|
|
632
716
|
raw_status = calkit.dvc.status_as_posix(raw_status)
|
|
633
717
|
except Exception as e:
|
|
634
718
|
result["errors"].append(
|
|
@@ -794,20 +878,47 @@ def get_status(
|
|
|
794
878
|
for output in stage_cfg.get("outputs", [])
|
|
795
879
|
]
|
|
796
880
|
if subproject:
|
|
881
|
+
# Use posix separators so these match DVC's reported paths
|
|
882
|
+
# (which are always forward-slash, repo-root-relative). Using
|
|
883
|
+
# the OS-native separator here would, on Windows, produce a
|
|
884
|
+
# backslash variant that escapes path dedup and shows up as a
|
|
885
|
+
# duplicate stale output alongside DVC's posix path.
|
|
797
886
|
configured_outputs = [
|
|
798
|
-
|
|
887
|
+
(Path(subproject) / p).as_posix() for p in raw_outputs
|
|
799
888
|
]
|
|
800
889
|
else:
|
|
801
890
|
configured_outputs = raw_outputs
|
|
802
891
|
# For isolated subprojects, DVC reports paths relative to the
|
|
803
892
|
# subproject dir; prefix them so all paths are parent-relative.
|
|
893
|
+
# A stage is subproject-originating if it carries a subproject
|
|
894
|
+
# path ("{sp}:{stage}") or is a kept wrapper for a subproject
|
|
895
|
+
# ("{sp} (subproject)", whose bare name is a known subproject).
|
|
896
|
+
is_subproject = (
|
|
897
|
+
subproject is not None or bare_name in sp_by_stage_name
|
|
898
|
+
)
|
|
804
899
|
ordered_stale_stages[display_name] = StaleStage.from_status_data(
|
|
805
900
|
status_data=status_data,
|
|
806
901
|
configured_outputs=configured_outputs,
|
|
807
902
|
path_prefix=subproject
|
|
808
903
|
if subproject in isolated_sp_paths
|
|
809
904
|
else None,
|
|
905
|
+
is_subproject=is_subproject,
|
|
810
906
|
)
|
|
907
|
+
if targets:
|
|
908
|
+
ordered_stale_stages = {
|
|
909
|
+
display_name: stale_stage
|
|
910
|
+
for display_name, stale_stage in ordered_stale_stages.items()
|
|
911
|
+
if any(
|
|
912
|
+
_status_target_matches(
|
|
913
|
+
target,
|
|
914
|
+
display_name,
|
|
915
|
+
raw_stale_stages[display_name][0],
|
|
916
|
+
raw_stale_stages[display_name][1],
|
|
917
|
+
stale_stage,
|
|
918
|
+
)
|
|
919
|
+
for target in targets
|
|
920
|
+
)
|
|
921
|
+
}
|
|
811
922
|
result["stale_stages"] = ordered_stale_stages
|
|
812
923
|
return PipelineStatus(
|
|
813
924
|
has_pipeline=result["has_pipeline"],
|
|
@@ -86,6 +86,109 @@ def create_bibtex(
|
|
|
86
86
|
)
|
|
87
87
|
|
|
88
88
|
|
|
89
|
+
def _find_bibtex_entry_span(
|
|
90
|
+
text: str, entry_id: str
|
|
91
|
+
) -> tuple[int, int] | None:
|
|
92
|
+
"""Locate the raw character span of a BibTeX entry by its citation key.
|
|
93
|
+
|
|
94
|
+
Returns ``(start, end)`` offsets into ``text`` where ``end`` is exclusive
|
|
95
|
+
(just past the entry's closing brace), or ``None`` if no entry with that
|
|
96
|
+
key is found.
|
|
97
|
+
"""
|
|
98
|
+
# Match e.g. ``@misc{the-key,`` allowing arbitrary surrounding whitespace
|
|
99
|
+
pattern = re.compile(
|
|
100
|
+
r"@\w+\s*\{\s*" + re.escape(entry_id) + r"\s*,", re.IGNORECASE
|
|
101
|
+
)
|
|
102
|
+
match = pattern.search(text)
|
|
103
|
+
if match is None:
|
|
104
|
+
return None
|
|
105
|
+
# Walk from the entry's opening brace to its matching close, counting depth
|
|
106
|
+
depth = 0
|
|
107
|
+
i = text.index("{", match.start())
|
|
108
|
+
while i < len(text):
|
|
109
|
+
if text[i] == "{":
|
|
110
|
+
depth += 1
|
|
111
|
+
elif text[i] == "}":
|
|
112
|
+
depth -= 1
|
|
113
|
+
if depth == 0:
|
|
114
|
+
return match.start(), i + 1
|
|
115
|
+
i += 1
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def add_bibtex_entry(
|
|
120
|
+
existing_text: str, new_entry: str, replace_ids: list[str] | None = None
|
|
121
|
+
) -> str:
|
|
122
|
+
"""Append a BibTeX entry to existing references without reformatting them.
|
|
123
|
+
|
|
124
|
+
Any entries whose citation keys are in ``replace_ids`` are removed first
|
|
125
|
+
(so a re-released version replaces its prior entry); all other existing
|
|
126
|
+
content is preserved byte-for-byte. ``new_entry`` is appended at the end.
|
|
127
|
+
"""
|
|
128
|
+
text = existing_text
|
|
129
|
+
# Remove replaced entries from the raw text, last-to-first so earlier spans
|
|
130
|
+
# stay valid as we splice. De-duplicate spans in case a key matched more
|
|
131
|
+
# than once.
|
|
132
|
+
spans = set()
|
|
133
|
+
for entry_id in replace_ids or []:
|
|
134
|
+
span = _find_bibtex_entry_span(text, entry_id)
|
|
135
|
+
if span is not None:
|
|
136
|
+
spans.add(span)
|
|
137
|
+
for start, end in sorted(spans, reverse=True):
|
|
138
|
+
# Consume any trailing newlines so we don't leave a gap behind
|
|
139
|
+
while end < len(text) and text[end] in "\r\n":
|
|
140
|
+
end += 1
|
|
141
|
+
text = text[:start] + text[end:]
|
|
142
|
+
# Append the new entry, leaving the existing content untouched and adding
|
|
143
|
+
# only the separator newlines needed for exactly one blank line between the
|
|
144
|
+
# last existing entry and the new one
|
|
145
|
+
if not text.strip():
|
|
146
|
+
return new_entry.strip() + "\n"
|
|
147
|
+
if text.endswith("\n\n"):
|
|
148
|
+
separator = ""
|
|
149
|
+
elif text.endswith("\n"):
|
|
150
|
+
separator = "\n"
|
|
151
|
+
else:
|
|
152
|
+
separator = "\n\n"
|
|
153
|
+
return text + separator + new_entry.strip() + "\n"
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def add_doi_badge_to_readme(
|
|
157
|
+
readme_text: str, badge: str, title: str | None
|
|
158
|
+
) -> str:
|
|
159
|
+
"""Insert (or refresh) a DOI badge near the top of a README.
|
|
160
|
+
|
|
161
|
+
The badge is placed directly beneath the title with exactly one blank line
|
|
162
|
+
on either side. Any pre-existing DOI badge line is replaced rather than
|
|
163
|
+
duplicated, the title is never duplicated, and all other existing content
|
|
164
|
+
is preserved.
|
|
165
|
+
"""
|
|
166
|
+
# Drop any existing DOI badge line(s); the current badge is re-added below
|
|
167
|
+
lines = [
|
|
168
|
+
line
|
|
169
|
+
for line in (readme_text.split("\n") if readme_text else [])
|
|
170
|
+
if not line.startswith("[![DOI")
|
|
171
|
+
]
|
|
172
|
+
# The first non-blank line is treated as the title; synthesize one from the
|
|
173
|
+
# project title if the README has no content yet
|
|
174
|
+
while lines and not lines[0].strip():
|
|
175
|
+
lines.pop(0)
|
|
176
|
+
if lines:
|
|
177
|
+
title_line = lines[0]
|
|
178
|
+
rest = lines[1:]
|
|
179
|
+
else:
|
|
180
|
+
title_line = f"# {title}"
|
|
181
|
+
rest = []
|
|
182
|
+
# Drop leading blank lines from the remaining content so the spacing around
|
|
183
|
+
# the badge is controlled entirely here
|
|
184
|
+
while rest and not rest[0].strip():
|
|
185
|
+
rest.pop(0)
|
|
186
|
+
new_lines = [title_line, "", badge]
|
|
187
|
+
if rest:
|
|
188
|
+
new_lines += [""] + rest
|
|
189
|
+
return "\n".join(new_lines)
|
|
190
|
+
|
|
191
|
+
|
|
89
192
|
CITATION_CFF_TEMPLATE = """
|
|
90
193
|
cff-version: 1.2.0
|
|
91
194
|
message: If you use this software, please cite it using these metadata.
|
|
@@ -296,3 +296,56 @@ def test_isolated_subproject_external_dep(tmp_dir):
|
|
|
296
296
|
check_environments=False, compile_to_dvc=False
|
|
297
297
|
)
|
|
298
298
|
assert not status.is_stale
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def test_targeted_status_uses_full_subproject_status_for_stage_targets(
|
|
302
|
+
tmp_dir, monkeypatch
|
|
303
|
+
):
|
|
304
|
+
os.makedirs("solver/.dvc", exist_ok=True)
|
|
305
|
+
root_ck = {
|
|
306
|
+
"subprojects": [{"path": "solver"}],
|
|
307
|
+
"pipeline": {
|
|
308
|
+
"stages": {
|
|
309
|
+
"post-process": {
|
|
310
|
+
"outputs": [{"path": "final.txt", "storage": "git"}]
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
}
|
|
315
|
+
solver_ck = {
|
|
316
|
+
"pipeline": {
|
|
317
|
+
"stages": {
|
|
318
|
+
"package-paper": {
|
|
319
|
+
"outputs": [{"path": "paper.txt", "storage": "git"}]
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
class FakeRepo:
|
|
326
|
+
def status(self, targets=None):
|
|
327
|
+
if targets is None:
|
|
328
|
+
return {
|
|
329
|
+
"solver/dvc.yaml:package-paper": ["always changed"],
|
|
330
|
+
"post-process": [
|
|
331
|
+
{"changed deps": {"solver/docs/figs": "modified"}}
|
|
332
|
+
],
|
|
333
|
+
}
|
|
334
|
+
return {}
|
|
335
|
+
|
|
336
|
+
monkeypatch.setattr(
|
|
337
|
+
calkit,
|
|
338
|
+
"load_calkit_info",
|
|
339
|
+
lambda wdir=None: solver_ck if wdir == "solver" else root_ck,
|
|
340
|
+
)
|
|
341
|
+
monkeypatch.setattr(calkit.dvc, "get_dvc_repo", lambda *args: FakeRepo())
|
|
342
|
+
monkeypatch.setattr(calkit.dvc, "status_as_posix", lambda status: status)
|
|
343
|
+
status = calkit.pipeline.get_status(
|
|
344
|
+
ck_info=root_ck,
|
|
345
|
+
targets=["post-process"],
|
|
346
|
+
check_environments=False,
|
|
347
|
+
clean_notebooks=False,
|
|
348
|
+
compile_to_dvc=False,
|
|
349
|
+
)
|
|
350
|
+
assert status.is_stale
|
|
351
|
+
assert status.stale_stage_names == ["post-process"]
|