Skip to content

Support only re-creating session venv if poetry.lock has changed #1063

@johnthagen

Description

@johnthagen

nox-poetry currently by default always recreates the virtual environment on every invocation of the session. This has the advantage of ensuring that the environment is always up to date, but at the cost that environments will be recreated unnecessarily when there are no changes to the locked dependencies.

Consider this small example project:

[tool.poetry]
name = "poetry-test"
version = "0.1.0"
description = ""
authors = []

[tool.poetry.dependencies]
python = "^3.10, <3.12"

[tool.poetry.group.dev.dependencies]
nox-poetry = "*"

[tool.poetry.group.lint.dependencies]
flake8 = "*"
flake8-bugbear = "*"
flake8-broken-line = "*"
flake8-comprehensions = "*"
pep8-naming = "*"
flake8-pyproject = "*"

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
# noxfile.py
from nox_poetry import Session, session


@session
def lint(s: Session) -> None:
    s.install(
        "flake8",
        "flake8-bugbear",
        "flake8-broken-line",
        "flake8-comprehensions",
        "pep8-naming",
        "flake8-pyproject"
    )
    s.run("flake8")

Running a simple lint invocation recreates the environment every time. Even when the wheels are cached on the user's system, the recreation can take significant time and add delay into the developer's workflow:

$ time nox -s lint
nox > Running session lint
nox > Creating virtual environment (virtualenv) using python in .nox/lint
nox > poetry export --format=requirements.txt --dev --without-hashes
The `--dev` option is deprecated, use the `--with dev` notation instead.
nox > python -m pip install --constraint=.nox/lint/tmp/requirements.txt flake8 flake8-bugbear flake8-broken-line flake8-comprehensions pep8-naming flake8-pyproject
nox > flake8 
nox > Session lint was successful.
nox -s lint  16.02s user 2.58s system 90% cpu 20.528 total

On a Macbook Pro, it took 16 seconds to recreate this relatively small venv.

If we were to reuse it:

time nox --reuse-existing-virtualenvs -s lint
nox > Running session lint
nox > Re-using existing virtual environment at .nox/lint.
nox > python -m pip install --constraint=.nox/lint/tmp/requirements.txt flake8 flake8-bugbear flake8-broken-line flake8-comprehensions pep8-naming flake8-pyproject
nox > flake8 
nox > Session lint was successful.
nox --reuse-existing-virtualenvs -s lint  0.90s user 0.29s system 96% cpu 1.227 total

Only 0.9 seconds.

nox-poetry has an advantage over Nox in that we know that the user is using Poetry. Poetry includes a helpful hash of the entire lockfile and important Poetry-related metadata from pyproject.toml.

# poetry.lock
[metadata]
lock-version = "2.0"
python-versions = "^3.10, <3.12"
content-hash = "f8d0beddbcf539e1dcd2e67603593a3c1b19a31df9b295b066d498110aa03b51"

If the content-hash were read using tomlkit (which is already a dependency of nox-poetry) and stored with each venv, then nox-poetry could very quickly check if a virtual environment needed to be recreated or not. This would speed up local development using nox-poetry greatly.

One open question could be, do we want to also try to handle the case when there are changes to the session itself? For example if different packages were passed to Session.install()? Perhaps there would be a simple way to add that with the lockfile hash and then it would be possible to skip the entire install step.

This can be demonstrated with this edit to the session:

from nox_poetry import Session, session


@session(venv_backend="none")
def lint(s: Session) -> None:
    s.run("flake8")

Avoiding the install entirely speeds up the command even more (because we are skipping the pip install --contraint... step which still needs to validate that the venv has all of the needed requirements.

time nox -s lint
nox > Running session lint
nox > flake8 
nox > Session lint was successful.
nox -s lint  0.30s user 0.12s system 96% cpu 0.436 total

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions