diff --git a/CLAUDE.md b/CLAUDE.md index 3db0e1ec..8ee90c2c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,13 +16,12 @@ ShapePipe is not a stand-alone library: it needs system tools (Source Extractor, PSFEx, WeightWatcher), MPI, and a specific scientific-Python stack. The supported way to get all of that is the container. -- **Dependencies** are declared in `pyproject.toml`, **pinned to exact versions** - and kept current by **Renovate** (see `renovate.json`). ShapePipe ships as a - container, not a published library, so there are no downstream installers to - keep flexible — pinning is what lets Renovate honor a supply-chain - release-age delay before a new release is adopted (it can't hold `uv` back to - an aged version through a floor range). `uv.lock` is the reproducible manifest - generated by `uv` (never hand-edited). System-level tools live in the +- **Dependencies** are declared in `pyproject.toml` as **floor ranges**; exact + versions live in `uv.lock`, the reproducible manifest generated by `uv` + (never hand-edited). **Renovate** keeps the lock current (see + `renovate.json`): uv's resolver owns version selection, so cross-package + constraints resolve jointly instead of failing update PRs. Bump a floor only + when the code actually requires the newer API. System-level tools live in the `Dockerfile`. - **Images** publish to `ghcr.io/cosmostat/shapepipe` on every push to an integration branch, in two targets: diff --git a/pyproject.toml b/pyproject.toml index 5d51b9a1..f5d5190d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,39 +11,38 @@ authors = [ license = "MIT" license-files = ["LICENSE"] requires-python = ">=3.12" -# Dependencies are PINNED to exact versions and kept current by Renovate (see -# renovate.json). shapepipe ships as a container, not a published library, so -# there are no downstream installers to keep flexible — pinning is what lets -# Renovate honor a supply-chain release-age delay before adopting a new release -# (it can't hold uv back to an aged version through a floor range; -# renovatebot/renovate#41624). `uv.lock` is the reproducible manifest generated -# by uv (never hand-edited). +# Dependencies use FLOOR RANGES; exact versions live in `uv.lock`, the +# reproducible manifest generated by uv (never hand-edited). Renovate updates +# the lock (rangeStrategy=update-lockfile, see renovate.json), so uv's resolver +# owns version selection — cross-package constraints (e.g. numba capping numpy) +# are resolved jointly instead of failing update PRs. Bump a floor only when +# the code actually requires the newer API. dependencies = [ - "astropy==7.2.0", # major 6 → 7 + "astropy>=7.0", # major 6 → 7 "astroquery", "canfar", "cs_util>=0.2.1", - "galsim==2.8.4", + "galsim>=2.8", "h5py", - "joblib==1.5.3", - "matplotlib==3.10.9", - "mccd==1.2.4", - "modopt==1.7.2", - "mpi4py==4.1.2", - "numba==0.65.1", # numpy 2 support - "numpy==2.4.6", # major 1 → 2 - "pandas==3.0.3", # major 2 → 3 + "joblib>=1.4", + "matplotlib>=3.10", + "mccd>=1.2.4", + "modopt>=1.6", + "mpi4py>=4.1.2", + "numba>=0.59", # numpy 2 support + "numpy>=2.4.6", # major 1 → 2 + "pandas>=3.0.3", # major 2 → 3 "python-dateutil", - "python-pysap==0.3.0", + "python-pysap>=0.3", "PyQt5", "pyqtgraph", - "reproject==0.19.0", - "sf_tools==2.0.4", - "skaha==1.7.0", - "sqlitedict==2.1.0", + "reproject>=0.19", + "sf_tools>=2.0.4", + "skaha>=1.7", + "sqlitedict>=2.0", "termcolor", - "tqdm==4.68.1", - "vos==3.6.4", + "tqdm>=4.68.1", + "vos>=3.6", "ngmix>=2.4", ] @@ -57,9 +56,9 @@ doc = [ "sphinx-book-theme" ] jupyter = [ - "ipython==9.14.1", - "jupyterlab==4.5.9", - "snakemake==9.22.0", + "ipython>=9.14.1", + "jupyterlab>=4.5.9", + "snakemake>=9.22.0", ] lint = [ "ruff", @@ -69,9 +68,9 @@ release = [ "twine", ] test = [ - "hypothesis==6.155.2", - "pytest==9.1.1", - "pytest-cov==7.1.0", + "hypothesis>=6.155.2", + "pytest>=8.3", + "pytest-cov>=5.0", ] fitsio = ["fitsio"] dev = ["shapepipe[doc,jupyter,lint,release,test,fitsio]"] @@ -99,7 +98,7 @@ cs_util = { git = "https://github.com/CosmoStat/cs_util", branch = "develop" } # Declaring a build backend makes shapepipe an installed package (not a uv # "virtual" project), so `uv sync` / `pip install -e .` put it on the path # rather than only its dependencies. -requires = ["setuptools==82.0.1"] +requires = ["setuptools>=61"] build-backend = "setuptools.build_meta" [tool.setuptools] diff --git a/renovate.json b/renovate.json index 3bcf1346..87098708 100644 --- a/renovate.json +++ b/renovate.json @@ -1,23 +1,14 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "description": [ - "Low-noise dependency updates with mild supply-chain hygiene, layered on the Mend org config.", - "Pins: ShapePipe ships as a container, not a PyPI library, so per the Renovate dependency-pinning doc we pin app dependencies exactly. The pins are written directly in pyproject.toml (== versions), so nothing depends on rangeStrategy resolution against the org config; the packageRule (scoped to PyPI, leaving git-sourced deps like ngmix/cs_util alone) just keeps future deps pinned. Exact pins are also what lets minimumReleaseAge work with uv (renovatebot/renovate#41624).", - "Python base-image bumps (3.12 -> 3.14 rides as 'minor' in Renovate's python versioning) never automerge — a Python upgrade gets human eyes.", - "Noise: all non-major updates grouped into one weekly PR that automerges when CI (full suite in the container) is green; majors surface individually for review; lockFileMaintenance refreshes transitive deps.", - "minimumReleaseAge=7 days: supply-chain detection window, sized per the Renovate docs' automerge guidance and Astral's uv guide." + "Low-noise dependency updates; uv owns version resolution. pyproject keeps floor ranges and Renovate uses the org default rangeStrategy=update-lockfile, so `uv lock` picks versions jointly — cross-package conflicts (e.g. numba capping numpy) resolve silently instead of failing the batch, and un-stick themselves when upstream catches up. No minimumReleaseAge: it is unenforceable with uv resolution (renovatebot/renovate#41624 forced exact pins, which forced manual conflict caps); CI running the full suite in the container is the automerge gate.", + "Noise: all non-major updates grouped into one weekly PR that automerges when CI is green; majors surface individually; lockFileMaintenance refreshes transitive deps weekly.", + "python is guarded: Renovate classes base-image bumps like 3.12 -> 3.14 as 'minor', so it gets its own PR and never automerges — a Python upgrade gets human eyes without holding the weekly batch hostage." ], "extends": ["config:recommended", "group:allNonMajor", "schedule:weekly"], - "minimumReleaseAge": "7 days", "lockFileMaintenance": { "enabled": true }, "packageRules": [ - { "matchDatasources": ["pypi"], "rangeStrategy": "pin" }, { "matchUpdateTypes": ["minor", "patch", "pin", "digest"], "automerge": true }, - { "matchPackageNames": ["python"], "automerge": false }, - { - "matchPackageNames": ["numpy"], - "allowedVersions": "<2.5", - "description": "numba 0.65.1 caps numpy<2.5; a grouped pin bump to 2.5.0 makes uv lock unsatisfiable and fails the whole batch. Drop this rule when numba supports numpy 2.5." - } + { "matchPackageNames": ["python"], "groupName": "python", "automerge": false } ] } diff --git a/uv.lock b/uv.lock index c5de4cdd..a1639e7b 100644 --- a/uv.lock +++ b/uv.lock @@ -3411,48 +3411,48 @@ test = [ [package.metadata] requires-dist = [ - { name = "astropy", specifier = "==7.2.0" }, + { name = "astropy", specifier = ">=7.0" }, { name = "astroquery" }, { name = "build", marker = "extra == 'release'" }, { name = "canfar" }, { name = "cs-util", git = "https://github.com/CosmoStat/cs_util?branch=develop" }, { name = "fitsio", marker = "extra == 'fitsio'" }, - { name = "galsim", specifier = "==2.8.4" }, + { name = "galsim", specifier = ">=2.8" }, { name = "h5py" }, - { name = "hypothesis", marker = "extra == 'test'", specifier = "==6.155.2" }, - { name = "ipython", marker = "extra == 'jupyter'", specifier = "==9.14.1" }, - { name = "joblib", specifier = "==1.5.3" }, - { name = "jupyterlab", marker = "extra == 'jupyter'", specifier = "==4.5.9" }, - { name = "matplotlib", specifier = "==3.10.9" }, - { name = "mccd", specifier = "==1.2.4" }, - { name = "modopt", specifier = "==1.7.2" }, - { name = "mpi4py", specifier = "==4.1.2" }, + { name = "hypothesis", marker = "extra == 'test'", specifier = ">=6.155.2" }, + { name = "ipython", marker = "extra == 'jupyter'", specifier = ">=9.14.1" }, + { name = "joblib", specifier = ">=1.4" }, + { name = "jupyterlab", marker = "extra == 'jupyter'", specifier = ">=4.5.9" }, + { name = "matplotlib", specifier = ">=3.10" }, + { name = "mccd", specifier = ">=1.2.4" }, + { name = "modopt", specifier = ">=1.6" }, + { name = "mpi4py", specifier = ">=4.1.2" }, { name = "myst-parser", marker = "extra == 'doc'" }, { name = "ngmix", git = "https://github.com/esheldon/ngmix?tag=v2.4.0" }, - { name = "numba", specifier = "==0.65.1" }, - { name = "numpy", specifier = "==2.4.6" }, + { name = "numba", specifier = ">=0.59" }, + { name = "numpy", specifier = ">=2.4.6" }, { name = "numpydoc", marker = "extra == 'doc'" }, - { name = "pandas", specifier = "==3.0.3" }, + { name = "pandas", specifier = ">=3.0.3" }, { name = "pyqt5" }, { name = "pyqtgraph" }, - { name = "pytest", marker = "extra == 'test'", specifier = "==9.1.1" }, - { name = "pytest-cov", marker = "extra == 'test'", specifier = "==7.1.0" }, + { name = "pytest", marker = "extra == 'test'", specifier = ">=8.3" }, + { name = "pytest-cov", marker = "extra == 'test'", specifier = ">=5.0" }, { name = "python-dateutil" }, - { name = "python-pysap", specifier = "==0.3.0" }, - { name = "reproject", specifier = "==0.19.0" }, + { name = "python-pysap", specifier = ">=0.3" }, + { name = "reproject", specifier = ">=0.19" }, { name = "ruff", marker = "extra == 'lint'" }, - { name = "sf-tools", specifier = "==2.0.4" }, + { name = "sf-tools", specifier = ">=2.0.4" }, { name = "shapepipe", extras = ["doc", "jupyter", "lint", "release", "test", "fitsio"], marker = "extra == 'dev'" }, - { name = "skaha", specifier = "==1.7.0" }, - { name = "snakemake", marker = "extra == 'jupyter'", specifier = "==9.22.0" }, + { name = "skaha", specifier = ">=1.7" }, + { name = "snakemake", marker = "extra == 'jupyter'", specifier = ">=9.22.0" }, { name = "sphinx", marker = "extra == 'doc'" }, { name = "sphinx-book-theme", marker = "extra == 'doc'" }, { name = "sphinxcontrib-bibtex", marker = "extra == 'doc'" }, - { name = "sqlitedict", specifier = "==2.1.0" }, + { name = "sqlitedict", specifier = ">=2.0" }, { name = "termcolor" }, - { name = "tqdm", specifier = "==4.68.1" }, + { name = "tqdm", specifier = ">=4.68.1" }, { name = "twine", marker = "extra == 'release'" }, - { name = "vos", specifier = "==3.6.4" }, + { name = "vos", specifier = ">=3.6" }, ] provides-extras = ["doc", "jupyter", "lint", "release", "test", "fitsio", "dev"]