Skip to content

Commit 4051f31

Browse files
drammockCarinaFotsbinnsscott-hubertynordme
authored
use a lockfile for "old" CI job (#13490)
Co-authored-by: Carina Forster <carinaforster0611@gmail.com> Co-authored-by: Thomas S. Binns <t.s.binns@outlook.com> Co-authored-by: Scott Huberty <52462026+scott-huberty@users.noreply.github.com> Co-authored-by: Erica Peterson <nordme@uw.edu> Co-authored-by: Eric Larson <larson.eric.d@gmail.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent dcc7116 commit 4051f31

12 files changed

Lines changed: 993 additions & 120 deletions

.github/workflows/spec_zero.yml

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,24 +37,32 @@ jobs:
3737
fi
3838
- name: Setup Remote SSH Connection
3939
if: env.ENABLE_SSH == 'true'
40-
uses: mxschmitt/action-tmate@v3
40+
uses: Warpbuilds/action-debugger@v1.3
4141
timeout-minutes: 10
4242
with:
4343
detached: true
44-
- uses: actions/setup-python@v6
44+
- uses: astral-sh/setup-uv@v7
4545
with:
46-
python-version: '3.12'
47-
- run: pip install packaging requests tomlkit
48-
- run: python tools/dev/spec_zero_update_versions.py
49-
- run: |
46+
version: ">=0.9"
47+
activate-environment: true
48+
python-version: 3.12
49+
- run: uv pip install packaging requests tomlkit
50+
- run: python ./tools/dev/spec_zero_update_versions.py
51+
- name: Create lockfile for old CI
52+
# uv pip compile requires setting the python version explicitly in the command :(
53+
run: |
54+
uv pip compile pyproject.toml --python "3.10" --python-platform "x86_64-unknown-linux-gnu" --group test --extra ver-auto-bumped --resolution lowest-direct --format pylock.toml --output-file tools/pylock.ci-old.toml
55+
python tools/github_actions_check_old_lockfile.py
56+
- name: check if files changed
57+
run: |
5058
git diff && git status --porcelain
5159
if [[ $(git status --porcelain) ]]; then
5260
echo "dirty=true" >> $GITHUB_OUTPUT
5361
fi
5462
id: status
5563
- name: Run pre-commit hooks to update other files
5664
run: |
57-
pip install pre-commit
65+
uv pip install pre-commit
5866
pre-commit run --all || true
5967
if: steps.status.outputs.dirty == 'true'
6068
- name: Create PR

.github/workflows/tests.yml

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ jobs:
107107
fi
108108
- name: Setup Remote SSH Connection
109109
if: env.ENABLE_SSH == 'true'
110-
uses: mxschmitt/action-tmate@v3
110+
uses: Warpbuilds/action-debugger@v1.3
111111
timeout-minutes: 80
112112
with:
113113
detached: true
@@ -151,10 +151,19 @@ jobs:
151151
create-args: >-
152152
python=${{ env.PYTHON_VERSION }}
153153
-v
154-
if: ${{ !startswith(matrix.kind, 'pip') }}
154+
if: matrix.kind == 'conda' || matrix.kind == 'mamba'
155155
timeout-minutes: 20
156+
# Python (if old)
157+
- uses: astral-sh/setup-uv@v7
158+
with:
159+
version: ">=0.9"
160+
activate-environment: true
161+
cache-dependency-glob: |
162+
**/pylock.ci-old.toml
163+
python-version: ${{ matrix.python }}
164+
if: matrix.kind == 'old'
156165
- run: bash ./tools/github_actions_dependencies.sh
157-
- run: python ./tools/github_actions_check_old.py
166+
- run: python ./tools/github_actions_check_old_env.py
158167
if: matrix.kind == 'old'
159168
# Minimal commands on Linux (macOS stalls)
160169
- run: bash ./tools/get_minimal_commands.sh

environment.yml

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,32 @@ dependencies:
77
- antio >=0.5.0
88
- curryreader >=0.1.2
99
- darkdetect
10-
- decorator
10+
- decorator >=5.1
1111
- defusedxml
12-
- dipy
12+
- dipy >=0.8
1313
- edfio >=0.4.10
1414
- eeglabio
1515
- filelock >=3.18.0
1616
- h5io >=0.2.4
17-
- h5py
17+
- h5py >=2.4
1818
- imageio >=2.6.1
1919
- imageio-ffmpeg >=0.4.1
2020
- ipyevents
2121
- ipympl
22-
- ipython !=8.7.0
22+
- ipython >=2.0,!=8.7.0
2323
- ipywidgets
24-
- jinja2
25-
- joblib
24+
- jinja2 >=3.1
25+
- joblib >=0.8
2626
- jupyter
2727
- lazy_loader >=0.3
2828
- mamba
2929
- matplotlib >=3.8
3030
- mffpy >=0.5.7
3131
- mne-qt-browser
32-
- nibabel
32+
- nibabel >=2.0
3333
- nilearn
3434
- nomkl
35-
- numba
35+
- numba >=0.35
3636
- numpy >=1.26,<3
3737
- openmeeg >=2.5.7
3838
- packaging
@@ -45,7 +45,7 @@ dependencies:
4545
- pymatreader
4646
- PySide6 !=6.9.1
4747
- python-neo
48-
- python-picard
48+
- python-picard >=0.4
4949
- pyvista >=0.43
5050
- pyvistaqt >=0.11
5151
- qdarkstyle !=3.2.2
@@ -54,9 +54,9 @@ dependencies:
5454
- scipy >=1.12
5555
- sip
5656
- snirf
57-
- statsmodels
57+
- statsmodels >=0.6
5858
- threadpoolctl
59-
- tqdm
59+
- tqdm >=4.66
6060
- traitlets
6161
- trame
6262
- trame-vtk

pyproject.toml

Lines changed: 40 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,64 @@
11
[build-system]
22
build-backend = "hatchling.build"
3-
requires = ["hatch-vcs", "hatchling"]
3+
requires = ["hatch-vcs", "hatchling >= 1.27"]
44

55
[dependency-groups]
6-
dev = ["pip >= 25.1", "rcssmin", {include-group = "doc"}, {include-group = "test_extra"}]
6+
dev = ["pip >= 25.1", "rcssmin >= 1.1", {include-group = "doc"}, {include-group = "test_extra"}]
77
# Dependencies for building the documentation
88
doc = [
99
"graphviz",
1010
"intersphinx_registry >= 0.2405.27",
1111
"ipython != 8.7.0", # also in "full-no-qt" and "test"
12-
"memory_profiler",
12+
"memory_profiler >= 0.16",
1313
"mne-bids",
1414
"mne-connectivity",
1515
"mne-gui-addons",
1616
"neo",
17-
"numpydoc",
18-
"openneuro-py",
17+
"numpydoc >= 0.5",
18+
"openneuro-py >= 2020.1",
1919
"psutil",
2020
"pydata_sphinx_theme >= 0.15.2",
2121
"pygments >= 2.13",
2222
"pymef",
2323
"pytest",
24-
"pyvistaqt",
24+
"pyvistaqt >= 0.11", # released 2023-06-30, no newer version available
2525
"pyxdf",
2626
"pyzmq != 24.0.0",
27-
"scikit-learn",
28-
"seaborn != 0.11.2",
27+
"scikit-learn >= 1.4", # released 2024-01-18, will become 1.5 on 2026-05-21
28+
"seaborn >= 0.5, != 0.11.2",
2929
"selenium >= 4.27.1",
3030
"sphinx >= 6",
3131
"sphinx-design",
3232
"sphinx-gallery >= 0.16",
3333
"sphinx_copybutton",
3434
"sphinxcontrib-bibtex >= 2.5",
35-
"sphinxcontrib-towncrier >=0.5.0a0",
35+
"sphinxcontrib-towncrier >= 0.5.0a0",
3636
"sphinxcontrib-youtube",
3737
]
38+
# Dependencies in a separate group for uv lockfile generation, currently only used for
39+
# the 'old' environment CI test run
40+
lockfile_extras = [
41+
"pandas >= 2.2", # released 2024-01-20, will become 2.3 on 2027-06-05
42+
"scikit-learn >= 1.4", # released 2024-01-18, will become 1.5 on 2026-05-21
43+
]
3844
test = [
3945
"codespell",
40-
"ipython != 8.7.0", # for testing notebook backend; also in "full-no-qt" and "doc"
41-
"mypy",
42-
"numpydoc",
46+
"ipython >= 8.20", # for testing notebook backend; also in "full-no-qt" and "doc"
47+
"mypy >= 0.14",
48+
"numpydoc >= 1.6",
49+
"pillow >= 10.2",
4350
"pre-commit",
4451
"pytest >= 8.0",
45-
"pytest-cov",
46-
"pytest-qt",
52+
"pytest-cov >= 4.1",
53+
"pytest-qt >= 4.3",
4754
"pytest-rerunfailures",
48-
"pytest-timeout",
49-
"ruff",
55+
"pytest-timeout >= 2.2",
56+
"ruff >= 0.1",
5057
"toml-sort",
51-
"tomli; python_version<'3.11'",
58+
"tomli; python_version < '3.11'",
5259
"twine",
5360
"vulture",
54-
"wheel",
61+
"wheel >= 0.21",
5562
]
5663
# Dependencies for being able to run additional tests (rare/CIs/advanced devs)
5764
# Changes here should be reflected in the mne/utils/config.py dev dependencies section
@@ -65,7 +72,7 @@ test_extra = [
6572
"nbclient",
6673
"nbformat",
6774
"neo",
68-
"nitime",
75+
"nitime >= 0.7",
6976
"pybv",
7077
"pymef",
7178
"snirf",
@@ -92,15 +99,15 @@ classifiers = [
9299
"Topic :: Software Development",
93100
]
94101
dependencies = [
95-
"decorator",
96-
"jinja2",
102+
"decorator >= 5.1",
103+
"jinja2 >= 3.1",
97104
"lazy_loader >= 0.3",
98105
"matplotlib >= 3.8", # released 2023-09-15, will become 3.9 on 2026-05-15
99106
"numpy >= 1.26, < 3", # released 2023-09-16, will become 2.0 on 2026-06-16
100107
"packaging",
101108
"pooch >= 1.5",
102109
"scipy >= 1.12", # released 2024-01-20, will become 1.13 on 2026-04-02
103-
"tqdm",
110+
"tqdm >= 4.66",
104111
]
105112
description = "MNE-Python project for MEG and EEG data analysis."
106113
dynamic = ["version"]
@@ -119,6 +126,9 @@ maintainers = [{email = "dan@mccloy.info", name = "Dan McCloy"}]
119126
name = "mne"
120127
readme = {content-type = "text/x-rst", file = "README.rst"}
121128
requires-python = ">= 3.10"
129+
# ↑↑↑↑↑↑↑↑↑↑↑ when this changes, bump the `--python-version` in the `uv pip compile ...`
130+
# command in `.github/workflows/spec_zero.yaml` (astral-sh/uv/#16333), and also the
131+
# `python` version for the `kind: old` matrix entry in `.github/workflows/tests.yaml`
122132
scripts = {mne = "mne.commands.utils:main"}
123133

124134
[project.optional-dependencies]
@@ -135,43 +145,43 @@ full-no-qt = [
135145
"curryreader >= 0.1.2",
136146
"darkdetect",
137147
"defusedxml",
138-
"dipy",
148+
"dipy >= 0.8",
139149
"edfio >= 0.4.10",
140150
"eeglabio",
141151
"filelock >= 3.18.0",
142-
"h5py",
152+
"h5py >= 2.4",
143153
"imageio >= 2.6.1",
144154
"imageio-ffmpeg >= 0.4.1",
145155
"ipyevents",
146156
"ipympl",
147-
"ipython != 8.7.0", # for notebook backend; also in "doc" and "test"
157+
"ipython >= 2.0, != 8.7.0", # for notebook backend; also in "doc" and "test"
148158
"ipywidgets",
149-
"joblib",
159+
"joblib >= 0.8",
150160
"jupyter",
151161
"mffpy >= 0.5.7",
152162
"mne-qt-browser",
153163
"mne[hdf5]",
154164
"neo",
155165
"nest-asyncio2",
156-
"nibabel",
166+
"nibabel >= 2.0",
157167
"nilearn",
158-
"numba",
168+
"numba >= 0.35",
159169
"openmeeg >= 2.5.7",
160170
"pandas >= 2.2", # released 2024-01-20, will become 2.3 on 2027-06-05
161171
"pillow", # for `Brain.save_image` and `mne.Report`
162172
"pyarrow", # only needed to avoid a deprecation warning in pandas
163173
"pybv",
164174
"pymef",
165175
"pyobjc-framework-Cocoa >= 5.2.0; platform_system == 'Darwin'",
166-
"python-picard",
176+
"python-picard >= 0.4",
167177
"pyvista >= 0.43", # released 2023-12-07, will become 0.44 on 2026-07-07
168178
"pyvistaqt >= 0.11", # released 2023-06-30, no newer version available
169179
"qdarkstyle != 3.2.2",
170180
"qtpy",
171181
"scikit-learn >= 1.4", # released 2024-01-18, will become 1.5 on 2026-05-21
172182
"sip",
173183
"snirf",
174-
"statsmodels",
184+
"statsmodels >= 0.6",
175185
"threadpoolctl",
176186
"traitlets",
177187
"trame",

tools/check_pyproject_helpers.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""Checks for handling version pins for dependencies in `pyproject.toml`."""
2+
3+
# Authors: The MNE-Python contributors.
4+
# License: BSD-3-Clause
5+
# Copyright the MNE-Python contributors.
6+
7+
from pathlib import Path
8+
9+
from packaging.requirements import Requirement
10+
from tomlkit.toml_file import TOMLFile
11+
12+
project_root = Path(__file__).parent.parent
13+
14+
15+
def get_deps_to_check():
16+
"""Get the dependencies whose versions should be checked from `pyproject.toml`."""
17+
pyproject = TOMLFile(project_root / "pyproject.toml")
18+
pyproject_data = pyproject.read()
19+
check_deps = (
20+
[f"python {pyproject_data['project']['requires-python']}"]
21+
+ pyproject_data["project"]["dependencies"]
22+
+ pyproject_data["dependency-groups"]["lockfile_extras"]
23+
)
24+
n_want_deps = 12 # update when we add more core deps or auto-bumped pins!
25+
assert len(check_deps) == n_want_deps, (
26+
f"Number of dependencies being checked ({len(check_deps)=}) is not as "
27+
f"expected {n_want_deps=}"
28+
)
29+
30+
return check_deps
31+
32+
33+
def get_min_pinned_ver(req):
34+
"""Get the min pinned version from a dep specification in `pyproject.toml`."""
35+
req = Requirement(req)
36+
name = req.name
37+
spec = req.specifier
38+
if len(spec) == 0:
39+
return name, None # no min version specified
40+
ge_specs = [this_spec for this_spec in spec if this_spec.operator == ">="]
41+
assert len(ge_specs) == 1, (
42+
f"Expected exactly 1 '>=' specifier in `pyproject.toml` for module {name} with "
43+
f"version specifications, but found {len(ge_specs)}"
44+
f"{': ' + ', '.join([str(ge_spec) for ge_spec in ge_specs]) if len(ge_specs) > 0 else ''}." # noqa: E501
45+
) # can't use \ to break f-string statements until python 3.12
46+
47+
return name, ge_specs[0].version
48+
49+
50+
def get_bad_deps_message(bads, bads_reason):
51+
"""Format a message about bad deps (e.g., missing, wrong version), if any."""
52+
if len(bads) == 0:
53+
return ""
54+
return f"The following module(s) {bads_reason}:\n" + "\n".join(bads)
55+
56+
57+
def raise_bad_deps_messages(bad_messages):
58+
"""Raise a RuntimeError if there are any bad messages to report."""
59+
bad_messages = [message for message in bad_messages if message != ""]
60+
if len(bad_messages) > 0:
61+
raise RuntimeError("\n\n".join(bad_messages))

0 commit comments

Comments
 (0)