Skip to content

Conversation

@tsbinns
Copy link
Contributor

@tsbinns tsbinns commented Oct 17, 2025

Reference issue (if any)

Fixes #13449

What does this implement/fix?

Adds a script that updates version specifiers for selected dependencies according to SPEC0 compliance.

Also adds a workflow that runs this script on PRs and just pushes the changes. End goal would be for the workflow to run on schedule and create a PR with any changes.

Additional information

Right now is only handling dependencies, not Python versions.

Am using tomlkit rather than the built-in tomllib due to requiring writing capabilities.

The specifier parsing using the packaging tools works really nicely, but unfortunately spaces are not preserved, e.g., numpy >= 1.26 becomes numpy>=1.26. For consistency, the script is applying this formatting to all pinned dependencies, even if they are not being changed. I'm open to changing/refining this.

Had to do some roundabout way to find the target branch to push to, as the zizmor pre-commit check was warning of code injection via template expansion with TARGET_BRANCH="${{ github.head_ref }}".

Also had to update the yamllint rules for the pre-commit check to pass on my Windows machine (had the same issue in MNE-Connectivity before: mne-tools/mne-connectivity#198).

@tsbinns
Copy link
Contributor Author

tsbinns commented Oct 17, 2025

Workflow is failing because the permissions aren't right to push the changes to this PR. I've been reading up on this, but can't seem to figure a solution out without adding a new PAT secret to the repo.

@drammock
Copy link
Member

xref to a couple things that (1) this might break (?) and (2) might have some code you could steal:

pre-commit hooks that parse pyproject.toml (and copy some things from it into other places)

# dependencies
- repo: local
hooks:
- id: update-env-file
name: Copy dependency changes from pyproject.toml to environment.yml
language: python
entry: ./tools/hooks/update_environment_file.py
files: '^(pyproject.toml|tools/hooks/update_environment_file.py)$'
- repo: local
hooks:
- id: dependency-sync
name: Copy core dependencies from pyproject.toml to README.rst
language: python
entry: ./tools/hooks/sync_dependencies.py
files: '^(pyproject.toml|tools/hooks/sync_dependencies.py)$'
additional_dependencies: ["mne==1.10.0"]

In particular, for our README we re-write the pins to look pretty, this could be adapted maybe:

def _prettify_pin(pin):
if pin is None:
return ""
pins = pin.split(",")
replacements = {
"<=": " ≤ ",
">=": " ≥ ",
"<": " < ",
">": " > ",
}
for old, new in replacements.items():
pins = [p.replace(old, new) for p in pins]
pins = reversed(pins)
return ",".join(pins)

@tsbinns
Copy link
Contributor Author

tsbinns commented Oct 20, 2025

@drammock Have pushed some changes to have the formatting better reflect what there is currently.

For this messing with the pre-commit hooks and making changes after they have been run, is there a way that this action changing pyproject.toml could re-trigger those hooks?

@larsoner
Copy link
Member

For this messing with the pre-commit hooks and making changes after they have been run, is there a way that this action changing pyproject.toml could re-trigger those hooks?

You can always pre-commit run --files pyproject.toml in the action. Does that do what you want?

Or really you can let the action push to a branch and make a PR, and as soon as that happens pre-commit.ci will run and push a commit as well

Copy link
Member

@drammock drammock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a few things I noticed when skimming the script

@tsbinns
Copy link
Contributor Author

tsbinns commented Oct 22, 2025

@larsoner Have pushed changes to run on workflow_dispatch and create a PR.

One last thing on my mind: should release date comments like this be removed now that we're automating?

"matplotlib >= 3.8", # released: 2023/09/15
"numpy >= 1.26,<3", # released: 2023/09/16

There are also comments for packages we're not automating (e.g., for pandas in full-no-qt deps). Should we keep those or drop as well?

@larsoner
Copy link
Member

larsoner commented Oct 22, 2025

One last thing on my mind: should release date comments like this be removed now that we're automating?

Either way is fine by me. I actually think it would be cool to add comments like this on lines that your action updates. It should be easy enough with your code, right?

There are also comments for packages we're not automating (e.g., for pandas in full-no-qt deps). Should we keep those or drop as well?

I think we should add pandas to the list of packages whose min version we update on the same schedule as matplotlib, numpy, etc. Are there any others?

@larsoner
Copy link
Member

larsoner commented Oct 22, 2025

... I looked at pyproject.toml and I think the others worth adding are pandas, openmeeg, and scikit-learn. I don't think we need to do openmeeg so you can remove the released: comment there so that the only ones with the comment are the ones maintained by this action

also you can push with [skip ci] in the commit comment to save some CI cycles since they don't need to run on your commits anymore

@tsbinns
Copy link
Contributor Author

tsbinns commented Oct 22, 2025

Either way is fine by me. I actually think it would be cool to add comments like this on lines that your action updates. It should be easy enough with your code, right?

Yeah, I can work on this. Not to make things too complex, but would a drop date also be worthwhile?

I think we should add pandas to the list of packages whose min version we update on the same schedule as matplotlib, numpy, etc. Are there any others?

Sorry I was mistaken, pandas is already included, so have just added scikit-learn.

@larsoner
Copy link
Member

larsoner commented Oct 22, 2025

but would a drop date also be worthwhile

So like something like this to indicate the next point and its release date?

 "numpy >= 1.26,<3",  # released 2023/09/16, 2.0 on 2024/06/16

That seems informative, yeah 👍

@larsoner
Copy link
Member

@tsbinns just let me know when you push the above change and the changelog entry. I'll test running the script locally, merge, and then workflow_dispatch and we can see if it opens a PR 🤞

Sound good?

@tsbinns
Copy link
Contributor Author

tsbinns commented Oct 22, 2025

@larsoner Sounds good! Have pushed a changelog now. Will ping you once the auto comment is working and once I've changed the schedule.

@tsbinns
Copy link
Contributor Author

tsbinns commented Oct 22, 2025

It's a good job I went back to update the comments, because I realised the logic for finding minimum versions was flawed. I was taking the earliest minor version within those 2 years, not the latest version at 2 years prior. That is now fixed.

The only thing that changed was where before pyvista was being set to >= 0.43, it is now >= 0.42.1. The micro ver reflects the fact that 0.42.0 was being explicitly excluded in the specifier, but 0.43 wasn't released until Dec 2023. At least we know the logic is robust for these cases.

I also made it so the comments are automatically updated. They look like this:

dependencies = [
  "decorator",
  "jinja2",
  "lazy_loader >= 0.3",
  "matplotlib >= 3.8",  # released 2023-09-15, will become 3.9 on 2026-05-15
  "numpy >= 1.26, < 3",  # released 2023-09-16, will become 2.0 on 2026-06-16
  "packaging",
  "pooch >= 1.5",
  "scipy >= 1.11",  # released 2023-06-28, will become 1.12 on 2026-01-19
  "tqdm",
]

and in cases with no new version available:

"pyvistaqt >= 0.11",  # released 2023-06-30, no newer version available

Now in cases where the unpatched version was yanked (e.g., scipy 1.11.0), the major.minor version is still being pinned, but I'm using the release date from the earliest unyanked patch (e.g., scipy 1.11.1).

Comment on lines +152 to +154
dependencies._value[idx] = _add_date_comment(
dependencies._value[idx], min_ver_release, next_ver, next_ver_release
)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not ideal that I have to manipulate private attrs of the toml data to add the comments, but there doesn't seem to be a way to do so otherwise. Even with tomlkit-extras this isn't supported.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tomlkit docs seem to suggest that this should work

Suggested change
dependencies._value[idx] = _add_date_comment(
dependencies._value[idx], min_ver_release, next_ver, next_ver_release
)
dependencies.value[idx].comment(code_to_generate_comment_string_here)

did you try that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, so dependencies.value[idx].comment("some comment") updates the comment properties of the item in that index, but this doesn't propagate to dependencies._value, which is what is used to write back to the file. Could be a bug? Skimming the issues/PRs for tomlkit I haven't seen this raised.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems buggish, yeah, but I'd have to dig into their code/API to be sure. I guess leave it for now, unless you find it deeply unsatisfying and can't sleep without knowing the answer 😆

Copy link
Member

@drammock drammock Oct 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not really a bug. xref: python-poetry/tomlkit#340 (comment)

TL;DR: private methods are the way to go, I can find any substantive improvement over what you've already got here.

@tsbinns
Copy link
Contributor Author

tsbinns commented Oct 22, 2025

@larsoner I think this is ready now!

Comment on lines +152 to +154
dependencies._value[idx] = _add_date_comment(
dependencies._value[idx], min_ver_release, next_ver, next_ver_release
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tomlkit docs seem to suggest that this should work

Suggested change
dependencies._value[idx] = _add_date_comment(
dependencies._value[idx], min_ver_release, next_ver, next_ver_release
)
dependencies.value[idx].comment(code_to_generate_comment_string_here)

did you try that?

@larsoner
Copy link
Member

Okay let's try it! 🤞

@larsoner larsoner merged commit 54644a5 into mne-tools:main Oct 23, 2025
4 checks passed
larsoner added a commit to larsoner/mne-python that referenced this pull request Oct 23, 2025
* upstream/main:
  ENH: Add encoding parameter to Nihon Kohden reader (mne-tools#13458)
  [MAINT] Automatic SPEC0 dependency version management (mne-tools#13451)
@tsbinns tsbinns deleted the spec0-automation branch October 24, 2025 16:55
larsoner added a commit to larsoner/mne-python that referenced this pull request Oct 27, 2025
* upstream/main: (23 commits)
  ENH: Add on_missing for combine_channels (mne-tools#13463)
  Bump the actions group with 2 updates (mne-tools#13464)
  Move development dependencies into a dependency group (no more extra) (mne-tools#13452)
  ENH: add on_missing for rename_channels (mne-tools#13456)
  add advisory board to website (mne-tools#13462)
  ENH: Support Nihon Kohden EEG-1200A V01.00 (mne-tools#13448)
  MAINT: Update dependency specifiers (mne-tools#13459)
  ENH: Add encoding parameter to Nihon Kohden reader (mne-tools#13458)
  [MAINT] Automatic SPEC0 dependency version management (mne-tools#13451)
  FIX: Read Nihon Kohden annotation file accurately (mne-tools#13251)
  MAINT: Restore edfio git install (mne-tools#13421)
  Support preload=False for the new EEGLAB single .set format (mne-tools#13096)
  [pre-commit.ci] pre-commit autoupdate (mne-tools#13453)
  MAINT: Restore PySide6 6.10.0 testing (mne-tools#13446)
  MAINT: Auth [skip azp] [skip actions]
  MAINT: Deploy [circle deploy] [skip azp] [skip actions]
  Bump github/codeql-action from 3 to 4 in the actions group (mne-tools#13442)
  ENH: Dont constrain fiducial clicks to mesh vertices (mne-tools#13445)
  Use timezone-aware ISO 8601 for website timestamp (mne-tools#13347)
  [pre-commit.ci] pre-commit autoupdate (mne-tools#13443)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Automate SPEC0 adherence for dependency version management

3 participants