目录

任务运行器

任务运行器,如 make(完全通用)、rake(Ruby 通用)、invoke(Python 通用)、hatch(Python 包)、tox(Python 包)或 nox(Python 半通用),是一种工具,允许您通过通用界面指定一组任务。过去,一些社区项目曾不鼓励使用它们,因为它们可能是一种拐杖,允许在自定义脚本后面使用糟糕的打包实践,并且它们可以隐藏实际发生的事情。

只要您不依赖它来隐藏打包问题,nox 对于许多包来说是一个不错的选择。Nox 具有两个强大的优势,有助于解决上述问题。首先,它非常明确,甚至在运行时打印它正在做什么。与旧的 tox 不同,它没有内置任何隐式假设。其次,它对虚拟环境和 Conda 环境都有非常优雅的内置支持。这可以极大地减少代码库中的新贡献者摩擦。

每天的开发人员不应该期望使用 nox 来执行简单的任务,例如运行测试或进行代码风格检查。您不应该依赖 nox 来执行应该简单标准化(例如构建包)的任务。您不需要为 CI 上的代码风格检查使用 nox,甚至通常不需要在 CI 上进行测试,即使这些任务是为用户提供的。Nox 比在自定义环境中直接运行慢几秒钟 - 但对于新用户来说,以及很少运行的任务,它比解释如何进行设置或手动处理虚拟环境快得多。它也是高度可重复的,每次都会创建和销毁临时环境。而且,如果您在重新运行时传递 -R,您可以跳过设置和安装步骤,使其几乎与直接运行命令一样快!

PY007应该使用任务运行器,以便新贡献者可以轻松简单地运行事物。您应该使用任务运行器来简化专门的开发人员任务。您应该使用任务运行器来避免为文档和其他很少运行的任务创建一次性虚拟环境。推荐使用 Nox,但 Tox 和 Hatch 也是可以接受的。

Nox 不太擅长处理二进制构建,因此对于编译项目,最好将它留给专门的任务。

Nox

安装

安装 nox 应该像处理任何其他 Python 应用程序一样。您应该使用一个好的包管理器,例如 macOS 上的 brew,或者使用 pipx;可以永久安装(pipx install nox)或者通过运行 pipx run nox 而不是 nox

在 GitHub Actions 或 Azure 上,pipx 默认情况下可用,因此您应该使用 pipx run nox。要让它访问所有 Python 版本,您可以使用此操作

- uses: wntrblm/nox@2024.10.09

您现在可以从 nox 访问所有当前版本的 Python。至少在 GitHub Actions 中,您应该在 nox 运行中添加 --forcecolor 以在日志中获取彩色输出,或者设置 env: FORCE_COLOR: 31。如果您想自定义为您准备的 Python 版本,请使用类似这样的输入

- uses: wntrblm/nox@2024.10.09
  with:
    python-versions: "3.9, 3.10, 3.11, 3.12, 3.13, pypy-3.10"

简介

Nox 是一个用于在临时虚拟环境中运行任务(称为“会话”)的工具。它通过 Python 配置,旨在类似于 pytest。它查找的文件默认情况下称为 noxfile.py。这是一个简单的 nox 文件的示例

import nox


@nox.session
def tests(session: nox.Session) -> None:
    """
    Run the unit and regular tests.
    """
    session.install(".[test]")
    session.run("pytest", *session.posargs)

这将创建一个名为 tests 的会话。该函数接收“会话”参数,该参数使您可以访问它创建的虚拟环境。您可以使用 .install() 在环境中安装,并使用 .run() 在环境中运行。我们还在使用 session.posargs 允许将额外的参数传递给 pytest。还有 更多有用的方法

您可以使用以下方法运行它

$ nox -s tests

您可以使用以下方法查看所有已定义的会话(以及文档字符串)

$ nox -l

最好在文件顶部附近设置 nox.options.sessions 来列出您默认情况下想要运行的会话

nox.options.sessions = ["lint", "tests"]

这将防止您默认情况下运行额外的项目,例如 docs

参数化

您可以参数化会话。可以对 Python 或任何其他项目进行参数化。

# Shortcut to parametrize Python
@nox.session(python=["3.9", "3.10", "3.11", "3.12", "3.13"])
def my_session(session: nox.Session) -> None: ...


# General parametrization
@nox.session
@nox.parametrize("letter", ["a", "b"], ids=["a", "b"])
def my_session(session: nox.Session, letter: str) -> None: ...

可选的 ids= 参数可以为参数化提供不错的名称,就像在 pytest 中一样。

如果用户没有安装特定版本的 Python,它将被跳过。您可以使用 Docker 容器在所有 Python(3.6+)都可用的环境中运行

$ docker run --rm -itv $PWD:/src -w /src quay.io/pypa/manylinux_2_28_x86_64:latest pipx run nox

您可以使用的另一个容器是 thekevjames/nox:latest;它预装了 nox(无需 pipx)以及 Python 2.7 和 3.5。

有用的会话

诸如升级版本之类的操作可以成为会话 - 由于 nox 为您处理环境,您可以使用任何喜欢的 Python 依赖项,而无需担心安装任何东西。以下是一些通常有用的会话,它们在不同的项目中可能看起来很相似

代码风格检查

理想情况下,所有开发人员都应该直接使用 pre-commit,但这对新用户有所帮助。

@nox.session
def lint(session: nox.Session) -> None:
    """
    Run the linter.
    """
    session.install("pre-commit")
    session.run(
        "pre-commit", "run", "--all-files", "--show-diff-on-failure", *session.posargs
    )

测试

@nox.session
def tests(session: nox.Session) -> None:
    """
    Run the unit and regular tests.
    """
    session.install(".[test]")
    session.run("pytest", *session.posargs)

文档

@nox.session(reuse_venv=True)
def docs(session: nox.Session) -> None:
    """
    Build the docs. Pass --non-interactive to avoid serving. First positional argument is the target directory.
    """

    parser = argparse.ArgumentParser()
    parser.add_argument(
        "-b", dest="builder", default="html", help="Build target (default: html)"
    )
    parser.add_argument("output", nargs="?", help="Output directory")
    args, posargs = parser.parse_known_args(session.posargs)
    serve = args.builder == "html" and session.interactive

    session.install("-e.[docs]", "sphinx-autobuild")

    shared_args = (
        "-n",  # nitpicky mode
        "-T",  # full tracebacks
        f"-b={args.builder}",
        "docs",
        args.output or f"docs/_build/{args.builder}",
        *posargs,
    )

    if serve:
        session.run("sphinx-autobuild", "--open-browser", *shared_args)
    else:
        session.run("sphinx-build", "--keep-going", *shared_args)

这也支持设置一个快速服务器,运行方式如下

$ nox -s docs -- --serve

构建(纯 Python)

对于纯 Python 包,这可能会有用

import shutil
from pathlib import Path

DIR = Path(__file__).parent.resolve()


@nox.session
def build(session: nox.Session) -> None:
    """
    Build an SDist and wheel.
    """

    build_path = DIR.joinpath("build")
    if build_path.exists():
        shutil.rmtree(build_path)

    session.install("build")
    session.run("python", "-m", "build")

(删除构建目录有助于 setuptools)

使用 uv 加速

uv 项目是对 pip、pip-tools 和 venv 的 Rust 重新实现,速度非常快。如果您的系统上安装了 uv,您可以告诉 nox 使用 uv,方法是在您的 noxfile.py 中添加以下内容

nox.needs_version = ">=2024.3.2"
nox.options.default_venv_backend = "uv|virtualenv"

您可以使用 pipxbrew 等安装 uv。如果您想在 GitHub Actions 中使用 uv,一种方法是使用以下方法

- uses: astral-sh/setup-uv@v3

检查您使用 uv 的作业;大多数内容不需要更改。主要区别是 uv 不会安装 pip,除非您要求它这样做。如果您想与 uv 交互,nox 可能会从其环境而不是系统环境获取 uv,因此,如果 shutil.which("uv") 返回 None,您可以安装 uv

示例

一个标准的 由 nox 提供支持 的纯 Python 包可以在 Scikit-HEP 的 Hist 项目中找到。

一个碰巧使用 PDM(类似于 Poetry 但更好)的包是 Scikit-HEP UHI,它 由 nox 提供支持。Nox 可以使用 ROOT 设置 conda 环境(速度慢,但只需要 nox 和 conda)。它还包括一个版本升级会话,并执行一些自定义逻辑。

为 Scientific Python Cookie 提供动力的复杂测试程序 由 nox 提供支持。它允许在本地运行生成项目并对其进行代码风格检查/测试/构建的复杂 CI 作业,而无需其他设置。

PyPA 的 cibuildwheel 也 由 nox 提供支持,在每个 Python 版本上运行 pip-tools 的编译以固定依赖项,以及提供用于更新 Python 和项目列表更新脚本的标准接口。那里的文档作业运行 mkdocs 而不是 Sphinx。其他使用 nox 的 PyPA 项目包括 pippipxmanylinuxpackagingpackaging.python.org.

  1. 许多颜色库只需要将 FORCE_COLOR 设置为任何值,但至少 一个 区分颜色深度,其中“3”->“256 位颜色”。对于许多用例,使用 FORCE_COLOR: 1 就足够了。