Visitar URL original
Pin requirements with `uv.lock` for use in CI and on Read the Docs by namurphy · Pull Request #2937 · PlasmaPy/PlasmaPy · GitHub
Skip to content

Pin requirements with uv.lock for use in CI and on Read the Docs - #2937

Merged
namurphy merged 105 commits into
PlasmaPy:mainfrom
namurphy:use-uv-lockfile
Feb 27, 2025
Merged

namurphy merged 105 commits into
PlasmaPy:mainfrom
namurphy:use-uv-lockfile

Conversation

@namurphy

@namurphy namurphy commented Jan 8, 2025 •

Copy link
Copy Markdown
Member

Background

For the last several months, we've been using uv pip compile to create separate pinned requirements files in ci_requirements/ for different versions of Python and for different operating systems. Having to deal with 18 separate files got unwieldy, and the Nox session to generate these files began to get tricky to maintain.

uv.lock holds information about different dependency groups for different operating systems and for different versions of Python. Pinning requirements in uv.lock would consolidate all of this information in a single file. 💾

Description of changes

This PR switches multiple Nox sessions defined in noxfile.py for testing and documentation builds to use requirements that are frozen in uv.lock. We can then use uv sync to install exact requirements.1

Context

Updating pinned requirements is still done via nox -s requirements, but can also be done with uv lock --upgrade. Keeping this as a Nox session ensures that we have a consistent/canonical way to update uv.lock (i.e., by making sure we are using the same version of uv with the same options) both locally and in CI. With a Nox session, we could add in additional steps too (e.g., logging or exporting a documentation build requirements file).

There are some Nox sessions that will not use uv.lock, including the tests that run against the lowest versions of direct dependencies. Consequently, breaking changes in new versions of indirect dependencies could cause spontaneous CI failures. If this happens more than ∼twice per year, we might be able to create a separate uv.lock or requirements file for the lowest direct dependencies.

This PR does not substantively change the GitHub workflow to update pinned requirements that is run weekly to quarantine new versions of dependencies and make sure that CI for other PRs does not get spontaneous failures due to breaking changes in upstream dependencies.

Closes #2852. Closes #2940.

Tasks

  • Make the Nox session for requirements generate uv.lock instead
  • Install dependencies from uv.lock using uv sync for the Nox sessions for:
    • Building documentation
    • Running tests
    • Running mypy
  • Update the PR message for the GitHub workflow that regenerate requirements
  • Remove _get_requirements_filepath and other unneeded functionality
  • Remove newly obsolete information from contributor guide and README files, if needed
  • Update the Nox session to validate_requirements
  • Remove ci_requirements/ files
  • Update the Read the Docs configuration to install dependencies using uv. We could probably adapt this section of the RTD docs to use uv sync instead of uv pip install so that the requirements from uv.lock are used.

Footnotes

  1. Whilst uv.lock is described as a "universal" lockfile, it is frankly planetary at best. 🪐 ↩

@namurphy namurphy added prototype 🏗️ Trying out an implementation on a trial basis requirements Related to updating requirements nox Related to the nox automation software labels Jan 8, 2025
@github-actions github-actions Bot added the maintenance General updates to package infrastructure label Jan 8, 2025
@codecov

codecov Bot commented Jan 8, 2025 •

Copy link
Copy Markdown

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 94.61%. Comparing base (422bded) to head (10773fe).
Report is 258 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2937      +/-   ##
==========================================
- Coverage   95.41%   94.61%   -0.80%     
==========================================
  Files         107      107              
  Lines        9662     9662              
  Branches     1462     1462              
==========================================
- Hits         9219     9142      -77     
- Misses        257      327      +70     
- Partials      186      193       +7     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

...instead of the pinned requirements files in `ci_requirements/` that we've been using up until now.
@github-actions github-actions Bot added testing CI Related to continuous integration python Pull requests that update Python code labels Jan 9, 2025
Since uv.lock is about 330 KB, it's probably worth excluding from source
distributions and wheels to keep sizes smaller.  It'll still be included
in Zenodo uploads.
@github-actions github-actions Bot added the packaging Related to packaging or distribution label Jan 9, 2025
@namurphy namurphy removed the prototype 🏗️ Trying out an implementation on a trial basis label Jan 9, 2025
@github-actions github-actions Bot added the GitHub Actions A continuous integration platform for automating tests and other tasks (see .github/ directory) label Jan 9, 2025
@namurphy
namurphy requested review from pheuer and rocco8773 and removed request for pheuer February 22, 2025 01:36
@namurphy

Copy link
Copy Markdown
Member Author

@sapols, might you be interested in reviewing this PR to check out some fun infrastructure improvements?

The purpose of this PR is to pin the dependencies used in PlasmaPy's CI and doc builds with uv lock. The uv.lock files is similar to the requirements.txt like files we have been using, but uv.lock stores requirements information for multiple versions of Python, multiple operating systems, and multiple dependency sets in a single file. Every Monday, an automated PR updates uv.lock to make sure that the tests still pass with new versions of the dependencies. This setup may be helpful for other packages in PyHC since changes to uv.lock help identify which upstream packages that may be responsible for newly failing tests.

Comment thread docs/contributing/coding_guide.rst Outdated
Comment on lines 664 to 665
- These files are updated periodically via pull requests created by a
GitHub workflow to `update pinned requirements`_.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Note: This link currently takes you to the GitHub Action page (https://github.com/PlasmaPy/PlasmaPy/actions/workflows/update-pinned-reqs.yml) instead of the actual workflow script (https://github.com/PlasmaPy/PlasmaPy/blob/main/.github/workflows/update-pinned-reqs.yml).

[Opinion] To me it makes more sense to link to the script and not the Action. In any case this will not affect this PR.

Comment thread pyproject.toml
"numpydoc >= 1.7.0",
"pillow >= 10.4.0",
"pygments >= 2.18.0",
"pygments == 2.18.0",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You made a comment in the the .github/content/update-requirements-pr-bocy.md that a PR should be opened to remove an upper bound on a dependency if one is add. Should this also be true if we freeze == a dependency?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Or maybe we only do this for install requirements (i.e. not tests, docs, etc.).

@namurphy namurphy Feb 27, 2025 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Should this also be true if we freeze == a dependency?

Yes! I'll need to update that.

  • address this ⬆️

Comment thread noxfile.py
Comment on lines 43 to 44
maxpython = max(supported_python_versions)
minpython = min(supported_python_versions)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Note: The supported_python_versions can easily get out of date with pyproject.toml. Are there breadcrumbs to ensure this gets updated when pyproject.toml gets updated? Or, is there a way to scrape pyproject.toml for this info?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I've been thinking the same thing! I did see an example noxfile.py somewhere that scraped pyproject.toml to get the supported versions of Python, but haven't had it be a high priority since I've been pretty thorough when dropping & adding versions of Python. I raised #2954 so we don't forget.

Comment thread noxfile.py

version : str
The supported version of Python.
uv_requirement = "uv >= 0.6.1"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

uv is not listed as a package dependency, should it be? If uv is included as a dependency, then this line can get out of sync easily.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I have been wondering the same thing! I've seen it recommended that uv be installed completely independently of Python, which makes sense since uv can be used to install and manage multiple versions of Python at once.

uv needs to get installed first because it's what is used to install the dependencies in the tests. I put the uv requirement in here explicitly as a safeguard so that running nox -s docs will work even if uv hasn't been installed separately.

I'm going to need to take a bit more time to think about it! 😅

Comment thread noxfile.py Outdated
Comment on lines +214 to +221
doc_build_dir = "$READTHEDOCS_OUTPUT" if running_on_rtd else "docs/build/html"

if running_on_rtd:
rtd_output_path = pathlib.Path(os.environ.get("READTHEDOCS_OUTPUT")) / "html"
rtd_output_path.mkdir(parents=True, exist_ok=True)
doc_build_dir = str(rtd_output_path)
else:
doc_build_dir = "docs/build/html"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Whenever we build the docs the build directory is docs/_build (with an underscore), not docs/build. That's how the make files are constructed.

This build directory is a bit arbitrary though. However, this would make the build directory different if you built with make versus nox.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Also, this block can be streamlined more...

Suggested change
doc_build_dir = "$READTHEDOCS_OUTPUT" if running_on_rtd else "docs/build/html"
if running_on_rtd:
rtd_output_path = pathlib.Path(os.environ.get("READTHEDOCS_OUTPUT")) / "html"
rtd_output_path.mkdir(parents=True, exist_ok=True)
doc_build_dir = str(rtd_output_path)
else:
doc_build_dir = "docs/build/html"
doc_build_dir = "docs/build/html"
if running_on_rtd:
rtd_output_path = pathlib.Path(os.environ.get("READTHEDOCS_OUTPUT")) / "html"
rtd_output_path.mkdir(parents=True, exist_ok=True)
doc_build_dir = str(rtd_output_path)

@rocco8773 rocco8773 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I left a few minor comments that highlight some potential (unrelated 🤷‍♂️) issues, but nothing I think really holds up this PR.

With my limited knowledge of how uv and nox works, this looks good to me. There might be some subtleties I'm missing, but if it works it works!

@namurphy

Copy link
Copy Markdown
Member Author

@rocco8773 — Thank you for the very helpful review!

@namurphy
namurphy enabled auto-merge (squash) February 27, 2025 17:39
@namurphy
namurphy merged commit 0aaf93b into PlasmaPy:main Feb 27, 2025
@namurphy
namurphy deleted the use-uv-lockfile branch April 1, 2025 20:10
@namurphy

namurphy commented Apr 1, 2025

Copy link
Copy Markdown
Member Author

Some exciting news for the pythoniverse is that PEP 751 was approved yesterday, which defines a standard for pylock.toml lock files. They will be kind of like a next gen requirements.txt file.

I'm inclined to stick with uv.lock as PlasmaPy's primary lockfile since:

  • uv.lock is more fully featured than pylock.toml
  • uv is being very actively developed and has a lot of really helpful tooling built around uv.lock
  • PlasmaPy uses the lockfile only internally for running tests and building docs, so the lockfile isn't used by end users.

It may be helpful to export pylock.toml from uv.lock, since that might be useful to have in PlasmaPy Zenodo uploads.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI Related to continuous integration contributor guide docs PlasmaPy Docs at http://docs.plasmapy.org documentation infrastructure GitHub Actions A continuous integration platform for automating tests and other tasks (see .github/ directory) maintenance General updates to package infrastructure nox Related to the nox automation software packaging Related to packaging or distribution python Pull requests that update Python code requirements Related to updating requirements testing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Validate that the requirements in pyproject.toml are consistent with uv.lock Use uv.lock file to pin dependencies in tests instead of files in ci_requirements

2 participants