-
-
Notifications
You must be signed in to change notification settings - Fork 167
Description
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 otherlint
dependencies to be installed — we needruff
ONLY; mypy
does not needruff
— 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.