generated from tweag/project
-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use uv instead of pip to manage nox virtualenvs #432
Draft
jherland
wants to merge
5
commits into
main
Choose a base branch
from
jherland/uv-replaces-pip
base: main
Could not load branches
Branch not found: {{ refName }}
Could not load tags
Nothing to show
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
jherland
force-pushed
the
jherland/ruff-format-instead-of-black
branch
from
May 13, 2024 11:16
a2c964c
to
fc666fd
Compare
Upgrade Nox to (at least) 2024.03.02 (which is the first version with support for managing virtualenvs with uv. Add uv as an indirect dependency by depending on "nox[uv]" instead of "nox" (exception: inside the "lint" dependency group, we only depend on "nox" in order for Mypy to access Nox' type annotations, uv is not needed here). Also we cannot use/depend on uv when using Python 3.7, since uv requires Python >=v3.8. I'm not actually sure _why_ uv requires >=v3.8, as it is apparently able to create venvs for Python v3.7 (see e.g. https://github.com/astral-sh/uv?tab=readme-ov-file#python-discovery), still astral-sh/uv#1239 prevents uv from being installed on <=v3.7). Finally, in noxfile.py, use uv as our default venv_backend instead of the default (pip), but only when it is in fact available. A final complication on Nix(OS) happens when we install requirements for the current session; we do this in two steps, and then we make sure that whatever we installed was patched appropriately: session.install("-r", str(requirments_txt)) if include_self: session.install("-e", ".") if not session.virtualenv._reused: # noqa: SLF001 patch_binaries_if_needed(session, session.virtualenv.location) However, with uv in the mix, we have to consider that session.install() itself _runs_ uv at the same time as the first session.install() may also _install_ uv itself into the virtualenv. The second session.install() can then end up _running_ a uv that was _installed_ by the first session.install(), and this will break on Nix(OS) unless the uv binary has been patched in the meantime. We therefore need to insert a call to patch_binaries_if_needed() _between_ the two session.install() calls. Since the second session.install() only installs FawltyDeps itself (which does not introduce any binaries to be patched), we can get away with simply reordering the second session.install() and the call to patch_binaries_if_needed(): session.install("-r", str(requirments_txt)) if not session.virtualenv._reused: # noqa: SLF001 patch_binaries_if_needed(session, session.virtualenv.location) if include_self: session.install("-e", ".")
Our sample_projects and real_projects tests use CachedExperimentVenv in tests/project_helpers.py to prepare virtualenvs containing the dependencies for each of these tests. Establishing these virtualenvs is costly, which is why we also _cache_ these virtualenvs between test runs (using the pytest cache). Using `uv` instead of `pip` can considerably speed up the creation of these virtualenvs. The speedup is largely due to two factors: 1. `uv` is simply faster than `pip`, even when they essentially perform the same tasks. 2. `uv` also implements its own cache of downloaded packages and will install a package into a virtualenv by _hardlinking_ the package files from its own cache. Here are some measurements before and after this commit. We run `time nox -Rs integration_tests-3.12 -- -k Python:all_reqs_installed` which times the execution of _one_ real_projects test with a fairly large set of dependencies: "The Algorithms - Python:all_reqs_installed". Each scenario is run 3 times: Before this commit (i.e. using `pip`): - Cold pytest cache (after running `rm -rf ~/.cache/pytest/*`): - 1m34.633s - 1m28.204s - 1m37.618s - Warm pytest cache: - 7.138s - 6.732s - 7.406s After this commit (i.e. using `uv` instead of `pip`): - Cold `uv` cache + cold pytest cache (after running `rm -rf ~/.cache/uv ~/.cache/pytest/*`): - 1m28.220s - 1m34.373s - 1m34.682s - Cold pytest cache (after running `rm -rf ~/.cache/pytest/*`): - 9.602s - 9.077s - 9.918s - Warm pytest cache: - 7.575s - 6.600s - 6.780s When both the `uv` cache and our own pytest-based cache are empty, `pip` and `uv` essentially have to perform the same work and the run time is dominated by the time it takes to download and unpack the required packages. In the warm cache case we reuse an existing virtualenv from the pytest cache and `pip`/`uv` is not involved at all. But in the case where we cannot reuse our pytest cache (e.g. because some detail of the experiment has changed), then `uv` will take advantage of its own cache to created the required virtualenvs almost instantaneously. In essence, with `uv` downloaded packages will be cached across test runs whether or not we implement our own caching. In the future - if we can _mandate_ `uv` instead of `pip` - we can consider removing our pytest-based cache with little impact on our test run times.
The previous commit explains why the uv cache has the potential to speed up the execution of our sample_projects and real_projects test cases. However, in order for CI to benefit from the same potential speedup, we need to actually preserve the uv cache across test runs.
jherland
force-pushed
the
jherland/uv-replaces-pip
branch
from
May 22, 2024 15:56
e6f968f
to
ac3e5bd
Compare
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Upgrade Nox to (at least) 2024.03.02 (which is the first version with
support for managing virtualenvs with
uv
.Add
uv
as an indirect dependency by depending on"nox[uv]"
instead of"nox"
(exception: inside the"lint"
dependency group, we only depend on"nox"
in order for Mypy to access Nox' type annotations,uv
is notneeded here).
Also we cannot use/depend on
uv
when using Python 3.7, sinceuv
requiresPython >=v3.8. I'm not actually sure why
uv
requires >=v3.8, as it isapparently able to create venvs for Python v3.7 (see e.g.
https://github.com/astral-sh/uv?tab=readme-ov-file#python-discovery),
still astral-sh/uv#1239 prevents
uv
from beinginstalled on <=v3.7).
Finally, in
noxfile.py
, useuv
as our defaultvenv_backend
instead ofthe default (
pip
), but only when it is in fact available.A final complication on Nix(OS) happens when we install requirements for
the current session; we do this in two steps, and then we make sure that
whatever we installed was patched appropriately:
However, with
uv
in the mix, we have to consider thatsession.install()
itself runs
uv
at the same time as the firstsession.install()
mayalso install
uv
itself into the virtualenv. The secondsession.install()
can then end up running auv
that was installedby the first
session.install()
, and this will break on Nix(OS) unlessthe
uv
binary has been patched in the meantime.We therefore need to insert a call to
patch_binaries_if_needed()
between the two
session.install()
calls. Since the secondsession.install()
only installs FawltyDeps itself (which does notintroduce any binaries to be patched), we can get away with simply
reordering the second
session.install()
and the call topatch_binaries_if_needed()
: