From 6f73104f7a576336ed3d96deb77c49f536bc8c79 Mon Sep 17 00:00:00 2001 From: Johannes Mahl Date: Mon, 6 Jul 2026 15:28:31 -0700 Subject: [PATCH] pin tooling and add verified dependency floors Add a skill to have future version bumping be easy and reliable. Make CI reproducible and harden the supply chain: - Pin [dev] extras to exact versions (ruff, mypy, pyright, deptry, pre-commit, pytest, build, etc.) so CI matches local and pre-commit. - SHA-pin all GitHub Actions to the current major's latest patch, each with a # vX.Y.Z comment; pypi-publish moves off the release/v1 branch. - Add "supports Python >=3.12" lower bounds to all runtime deps. - Add a min-versions CI job (parallel, 3.12-only) that verifies those floors: pinned uv computes the direct-dep floors, pip resolves the transitive graph, then the full suite runs. Floors validated against the full suite (997 passed); pandas floor is >=2.1.1 (2.1.0 ships no cp312 wheel). - Rename deptry's pep621_dev_dependency_groups to the new optional_dependencies_dev_groups (0.25.1 deprecation). --- .claude/skills/bump-versions/SKILL.md | 12 +++++ .github/workflows/ci.yaml | 42 ++++++++++++++- .github/workflows/release.yaml | 10 ++-- docs/ai/bump-versions.md | 73 +++++++++++++++++++++++++++ docs/ai/index.md | 1 + pyproject.toml | 53 ++++++++++--------- 6 files changed, 160 insertions(+), 31 deletions(-) create mode 100644 .claude/skills/bump-versions/SKILL.md create mode 100644 docs/ai/bump-versions.md diff --git a/.claude/skills/bump-versions/SKILL.md b/.claude/skills/bump-versions/SKILL.md new file mode 100644 index 0000000..3c6356c --- /dev/null +++ b/.claude/skills/bump-versions/SKILL.md @@ -0,0 +1,12 @@ +--- +name: bump-versions +description: Refresh dev-tool pins in pyproject.toml, the uv pin in ci.yaml, and GitHub Actions SHA pins. Manual stand-in for Dependabot. +disable-model-invocation: true +--- + +Use [../../../docs/ai/bump-versions.md](../../../docs/ai/bump-versions.md) as +the source of truth for this skill. + +This skill takes no arguments. + +When this wrapper and the shared doc differ, follow the shared doc. diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f2df3b0..52dc3cf 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -18,12 +18,12 @@ jobs: continue-on-error: ${{ matrix.python-version != '3.12' }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false fetch-depth: 1 - - uses: actions/setup-python@v6 + - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 with: python-version: ${{ matrix.python-version }} @@ -56,3 +56,41 @@ jobs: - name: Check package metadata run: .venv/bin/twine check dist/* + + # Verify the declared lower bounds in [project.dependencies] stay true instead + # of aspirational. Runs on the lowest supported Python (the floors are the + # first releases with cp312 wheels, so they neither install nor mean anything + # on 3.13/3.14 — that axis is covered by the `checks` matrix at latest deps). + min-versions: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + fetch-depth: 1 + + - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: "3.12" + + - name: Pin direct deps to their floors + # uv reads the floors straight from pyproject (single source of truth) + # and pins each DIRECT dep to its lowest version; --no-deps keeps + # transitive deps out of the constraints file so pip resolves those + # itself below — mirroring a user whose direct deps sit at our floors. + run: | + python -m pip install uv==0.11.27 # bump alongside the [dev] pins + uv pip compile --resolution lowest-direct --no-deps \ + -o min-constraints.txt pyproject.toml + cat min-constraints.txt + + - name: Install with pip (pip resolves the transitive graph) + run: | + python -m venv .venv + .venv/bin/python -m pip install --upgrade pip + .venv/bin/pip install -c min-constraints.txt -e ".[dev]" + + - name: Run tests + run: .venv/bin/pytest -q -m "" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index afc7217..b20b561 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -14,7 +14,7 @@ jobs: contents: read actions: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false fetch-depth: 0 @@ -26,7 +26,7 @@ jobs: echo "Ref: $GITHUB_REF Sha: $GITHUB_SHA" exit 1 fi - - uses: actions/setup-python@v6 + - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 with: python-version: "3.12" - name: Install package + dev tools @@ -50,7 +50,7 @@ jobs: run: .venv/bin/python -m build - name: Check package metadata run: .venv/bin/twine check dist/* - - uses: actions/upload-artifact@v6 + - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: dist path: dist/* @@ -66,10 +66,10 @@ jobs: actions: read id-token: write steps: - - uses: actions/download-artifact@v7 + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: dist path: dist/ - - uses: pypa/gh-action-pypi-publish@release/v1 + - uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 #with: # repository-url: https://test.pypi.org/legacy/ diff --git a/docs/ai/bump-versions.md b/docs/ai/bump-versions.md new file mode 100644 index 0000000..116d798 --- /dev/null +++ b/docs/ai/bump-versions.md @@ -0,0 +1,73 @@ +# Bump Versions + +Refresh the pinned dev tooling and GitHub Actions SHAs. Run every few months +or before a release. This skill does the work of Dependabot (ecosystems +`pip` + `github-actions`) manually, as one deliberate bump-everything session +instead of a stream of PRs. If the manual runs get tedious, consider migrating +to Dependabot — but note two things it will NOT handle: the `uv==` pin inside +a `run:` script line in `ci.yaml`, and config migrations that a tool bump +requires (it only surfaces those as red CI on its PRs). + +## What gets bumped + +1. **`[dev]` exact pins** in `pyproject.toml` (`==` versions). +2. **The `uv==` pin** in the `min-versions` job of `.github/workflows/ci.yaml` + — it lives in a shell line, cross-referenced by comments from the `[dev]` + section. Always bump it in the same pass. +3. **GitHub Actions SHA pins** in `.github/workflows/*.yaml` + (`uses: owner/repo@ # vX.Y.Z`). + +Do NOT touch the lower bounds in `[project.dependencies]`. Those are +"supports Python >=3.12" floors, validated by the `min-versions` CI job, and +are only raised when code starts relying on a newer API — never as part of a +routine bump. + +## Procedure + +### 1. Dev pins (PyPI) + +For each package in `[dev]` plus `uv`, look up the latest release: + +```bash +curl -s https://pypi.org/pypi//json | python3 -c \ + "import json,sys; print(json.load(sys.stdin)['info']['version'])" +``` + +Update the pins in `pyproject.toml` and the `uv==` line in `ci.yaml`. + +### 2. Action SHA pins (GitHub) + +For each `uses:` entry, find the latest tag and resolve it to the **peeled +commit SHA** — release tags are often annotated, and `git ls-remote` shows the +tag-object SHA on the bare ref. Pin the `^{}` (peeled) SHA when one is listed; +only when a tag has no `^{}` line is it lightweight and the bare SHA already +the commit: + +```bash +git ls-remote --tags https://github.com// | tail -20 # newest tags +git ls-remote https://github.com// \ + 'refs/tags/' 'refs/tags/^{}' +``` + +Update to `owner/repo@ # ` — keep the version comment +accurate, it is the only human-readable trace of what is pinned. Stay on the +same major unless release notes say the workflow inputs are unchanged. + +### 3. Verify and run the suite + +- Every changed pin must exist upstream: HTTP 200 from + `https://pypi.org/pypi///json`, and the commit SHA resolves via + `https://api.github.com/repos///commits/`. +- Reinstall and run the full check suite exactly as `ci.yaml` does: `pytest -q + -m ""`, `ruff check .` + `ruff format --check .`, `mypy --no-incremental`, + `pyright`, `deptry .`, `python -m build`, `twine check dist/*`. +- **Read the warnings, not just the exit codes.** Tool bumps can deprecate + config keys that still "work" (e.g. deptry 0.25 renamed + `pep621_dev_dependency_groups` → `optional_dependencies_dev_groups`). + Migrate `[tool.*]` config in the same pass so warnings never accumulate. + +### 4. Report + +Print a table of `package/action | old | new` plus any config migrations made, +and note anything held back (e.g. a major version skipped) with the reason. +Do not commit — leave the changes for the user to review. diff --git a/docs/ai/index.md b/docs/ai/index.md index 7a240b7..80c271e 100644 --- a/docs/ai/index.md +++ b/docs/ai/index.md @@ -8,6 +8,7 @@ working on the repo. add-function benchmark +bump-versions changelog check-docs check-example diff --git a/pyproject.toml b/pyproject.toml index 284bb9a..c27ad97 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,34 +33,39 @@ classifiers = [ "Programming Language :: Python :: 3.14", ] +# Lower bounds are "supports Python >=3.12" floors, validated by the +# min-versions CI job (pins these floors, lets pip resolve the transitive graph, +# then runs the suite). Raise a floor when code starts relying on a newer API. dependencies = [ - "lmfit", - "asteval", - "numdifftools", - "emcee", - "tqdm", - "corner", - "numpy", - "pandas", - "scipy", - "ruamel.yaml", - "h5py", - "ipython", - "matplotlib", + "lmfit>=1.2", + "asteval>=1.0", + "numdifftools>=0.9.41", + "emcee>=3.1", + "tqdm>=4.66", + "corner>=2.2", + "numpy>=1.26", + "pandas>=2.1.1", + "scipy>=1.11.2", + "ruamel.yaml>=0.18", + "h5py>=3.10", + "ipython>=8.18", + "matplotlib>=3.8", ] [project.optional-dependencies] +# NB: the min-versions CI job pins uv (see .github/workflows/ci.yaml) — bump it +# alongside these dev pins. dev = [ - "pre-commit", - "deptry", - "mypy", - "nbformat", # used by scripts/normalize_notebooks.py (pre-commit hook) - "nbstripout", - "pytest", - "pyright", - "ruff", - "build", - "twine", + "pre-commit==4.6.0", + "deptry==0.25.1", + "mypy==2.1.0", + "nbformat==5.10.4", # used by scripts/normalize_notebooks.py (pre-commit hook) + "nbstripout==0.9.1", + "pytest==9.1.1", + "pyright==1.1.411", + "ruff==0.15.20", + "build==1.5.0", + "twine==6.2.0", ] # Optional notebook/visualization support @@ -122,7 +127,7 @@ ignore = ["C901"] [tool.deptry] known_first_party = ["trspecfit"] ignore_notebooks = true -pep621_dev_dependency_groups = ["dev"] +optional_dependencies_dev_groups = ["dev"] # confidence interval estimation needs to work with standard install # keep these as runtime deps even though used indirectly via lmfit # py-spy is a CLI tool invoked via shell (see benchmark skill), not imported