nox icon indicating copy to clipboard operation
nox copied to clipboard

Enable reading dependency specifiers from PEP 621 `pyproject.toml`

Open paduszyk opened this issue 1 year ago • 3 comments

How would this feature be useful?

Let us assume I develop a Python package. In my PEP 621 pyproject.toml I have:

[project]
# ...
dependencies = [
   "django >= 3.2, < 5.2",
   "typing-extensions >= 4.12.2, < 5",
]

# ...

[project.optional-dependencies]
# dev = [ ... ]
lint = [
  "ruff >= 0.6.3, < 1",
  "django-stubs[compatible-mypy] >= 5.0.4, < 6",
]
# test = [ ... ]

Then, I would like to have separate sessions for checking and formatting my codebase with ruff and type-checking it with mypy. It would go like this:

@nox.session(tags=["lint"])
@nox.parametrize(
    "command",
    [
        "check",
        "format",
    ],
)
def ruff(session: nox.Session, command: str) -> None:
    session.install("-e", ".[lint]")

    session.run("ruff", command)


@nox.session(tags=["lint"])
def mypy(session: nox.Session) -> None:
    session.install("-e", ".[lint]")
 
    session.run("mypy", ".")

It works, but:

  • actually, ruff session does not need the package as well as any other lint dependencies to be installed — we need ruff ONLY;
  • mypy does not need ruff — nevertheless, it's installed because it's in the same dependency group.

A nice feature would be to tell Nox which dependencies should be installed, with version specifiers automatically read from pyproject.toml.

Describe the solution you'd like

This is a draft for my noxfile.py implementing a potential solution:

from __future__ import annotations

__all__ = [
    "mypy",
    "ruff",
]

import re
from functools import partial
from pathlib import Path
from typing import cast

import nox

PYPROJECT_TOML_PATH = Path(__file__).resolve().parent / "pyproject.toml"

def get_dependency_specifiers(pattern: str, *, group: str | None = None) -> list[str]:
    project = nox.project.load_toml(PYPROJECT_TOML_PATH).get("project")

    if not project:
        msg = (
            f"{PYPROJECT_TOML_PATH} is not a valid PEP 621 metadata file as "
            f"it does not contain a [project] table"
        )

        raise LookupError(msg)

    if group is None:
        dependencies = project.get("dependencies", [])
    else:
        try:
            dependencies = project.get("optional-dependencies", {})[group]
        except KeyError as e:
            msg = f"{group!r} dependencies are not defined in {PYPROJECT_TOML_PATH}"

            raise LookupError(msg) from e

    dependencies = cast(list[str], dependencies)

    if not (dependencies := list(filter(partial(re.match, pattern), dependencies))):
        msg = (
            f"{PYPROJECT_TOML_PATH} does not define any dependencies "
            f"that match {pattern!r}"
        )

        raise LookupError(msg)

    return dependencies


@nox.session(tags=["lint"])
@nox.parametrize(
    "command",
    [
        "check",
        "format",
    ],
)
def ruff(session: nox.Session, command: str) -> None:
    session.install(
        *get_dependency_specifiers(r"^ruff(\[.*\])?", group="lint"),
    )

    session.run("ruff", command)


@nox.session(tags=["lint"])
def mypy(session: nox.Session) -> None:
    session.install("-e", ".")
    session.install(
        *get_dependency_specifiers(r"^django\-stubs\[compatible-mypy\]", group="lint"),
    )

    session.run("mypy", ".")

Now, each environment contains only the relevant dependencies with the versions retrieved directly from the project's metadata.

Describe alternatives you've considered

No response

Anything else?

The function get_dependency_specifiers could be a part of the nox.project module.

Of course, I am open to any changes in the design. If you're interested, I can open a PR assuming you will assist in developing tests.

paduszyk avatar Aug 30 '24 09:08 paduszyk

IMO, this should be on hold until the dependency group pep gets accepted/rejected, in case that needs to be part of the design.

henryiii avatar Oct 03 '24 06:10 henryiii

The PEP (735) was just accepted!

henryiii avatar Oct 11 '24 05:10 henryiii

Supporting PEP 735 dependency groups would be great. Some motivation on a similar issue for nox-poetry is listed here

  • https://github.com/cjolowicz/nox-poetry/issues/663

johnthagen avatar Oct 15 '24 12:10 johnthagen