配置和 API

Noxfile

Nox 默认在名为 noxfile.py 的文件中寻找配置。你可以在运行 nox 时使用 --noxfile 参数指定一个不同的文件。

定义会话

session(func: Optional[nox.registry.F] = None, python: Optional[Union[str, Sequence[str], bool]] = None, py: Optional[Union[str, Sequence[str], bool]] = None, reuse_venv: Optional[bool] = None, name: Optional[str] = None, venv_backend: Optional[Any] = None, venv_params: Optional[Any] = None) Union[nox.registry.F, Callable[[nox.registry.F], nox.registry.F]]

将装饰的函数指定为一个会话。

Nox 会话是通过标准的 Python 函数配置的,这些函数用 @nox.session 装饰。例如:

import nox

@nox.session
def tests(session):
    session.run('pytest')

你也可以按照 配置会话的 virtualenv 中的描述,配置会话以针对多个 Python 版本运行,并按照 参数化会话 中的描述,配置会话的参数。

会话描述

你可以使用 docstring 为你的会话添加描述。第一行将在列出会话时显示。比如说:

import nox

@nox.session
def tests(session):
    """Run the test suite."""
    session.run('pytest')

nox --list 命令将显示:

$ nox --list
Available sessions:
* tests -> Run the test suite.

会话名字

默认情况下,Nox 使用装饰后的函数名称作为会话名称。这对绝大多数项目来说是很好的,但是,如果你需要,你可以通过使用 @nox.sessionname 参数来定制会话的名称。比如说:

import nox

@nox.session(name="custom-name")
def a_very_long_function_name(session):
    print("Hello!")

nox --list 命令将显示:

$ nox --list
Available sessions:
* custom-name

你可以告诉 nox 使用自定义的名字来运行会话:

$ nox --session "custom-name"
Hello!

配置会话的 virtualenv

默认情况下,Nox 将为每个会话创建新的 virtualenv,使用 Nox 使用的相同解释器。如果你使用 Python 3.6 安装 Nox,Nox 将默认为你的所有会话使用 Python 3.6。

你可以通过向 @nox.session 指定 python 参数(或其别名 py),告诉 Nox 使用不同的 Python 解释器/版本:

@nox.session(python='2.7')
def tests(session):
    pass

备注

Windows 上的 Python 二进制文件是通过 Python Launcher for Windows (py) 找到的。例如,Python 3.9 可以通过确定哪个可执行文件被 py -3.9 调用来找到。如果某个测试需要使用某个 Python 的 32 位版本,那么应该使用 X.Y-32 作为版本。

你也可以告诉 Nox 针对多个 Python 解释器运行你的会话。Nox 将创建一个单独的 virtualenv,为你指定的每个解释器运行会话。例如,这个会话将运行两次–一次用于 Python 2.7,另一次用于 Python 3.6:

@nox.session(python=['2.7', '3.6'])
def tests(session):
    pass

当你提供版本号时,Nox 会自动在前面加上 python 来确定可执行文件的名称。然而,Nox 也接受完整的可执行文件名。如果你想用 pypy 来测试,例如:

@nox.session(python=['2.7', '3.6', 'pypy-6.0'])
def tests(session):
    pass

当收集你的会话时,Nox 将为每个解释器创建一个单独的会话。你可以在运行 nox --list 时看到这些会话。 例如这个 Noxfile:

@nox.session(python=['2.7', '3.6', '3.7', '3.8', '3.9'])
def tests(session):
    pass

将制作这些会话:

* tests-2.7
* tests-3.6
* tests-3.7
* tests-3.8
* tests-3.9

注意,这种扩展发生在参数化之前,所以你仍然可以用多个解释器对会话进行参数化。

如果你想完全禁用 virtualenv 的创建,你可以把 python 设置为 False,或者把 venv_backend 设置为 "none",两者都是等同的。注意,这也可以通过 –no-venv 命令行标志临时完成。

@nox.session(python=False)
def tests(session):
    pass

使用 session.install() 在没有 virtualenv 的情况下是被弃用的,因为它修改了全局 Python 环境。如果这是你真正想要的,使用 session.run() 和 pip 来代替。

@nox.session(python=False)
def tests(session):
    session.run("pip", "install", "nox")

你也可以指定 virtualenv 应该一直被重用,而不是每次都重新创建:

@nox.session(
    python=['2.7', '3.6'],
    reuse_venv=True)
def tests(session):
    pass

你并不局限于 virtualenv,你可以选择 venv、conda、mamba 或 virtualenv(默认)等后端:

@nox.session(venv_backend='venv')
def tests(session):
    pass

最后,支持自定义后端参数:

@nox.session(venv_params=['--no-download'])
def tests(session):
    pass

将参数传入会话

通常情况下,向你的测试会话传递参数是很有用的。这里有一个快速的例子,演示了如何使用参数来运行针对特定文件的测试:

@nox.session
def test(session):
    session.install('pytest')

    if session.posargs:
        test_files = session.posargs
    else:
        test_files = ['test_a.py', 'test_b.py']

    session.run('pytest', *test_files)

现在你如果运行:

nox

然后 nox 将运行:

pytest test_a.py test_b.py

但如果你运行:

nox -- test_c.py

然后 nox 将运行:

pytest test_c.py

参数化会话

会话实参可以用 nox.parametrize() 装饰器来参数化。下面是一个典型的参数化 Django 安装版本的例子:

@nox.session
@nox.parametrize('django', ['1.9', '2.0'])
def tests(session, django):
    session.install(f'django=={django}')
    session.run('pytest')

当你运行 nox 时,它将创建两个不同的会话:

$ nox
nox > Running session tests(django='1.9')
nox > python -m pip install django==1.9
...
nox > Running session tests(django='2.0')
nox > python -m pip install django==2.0

nox.parametrize() 的接口和用法有意类似于 pytest 的 parametrize.

parametrize(arg_names: Union[str, List[str], Tuple[str]], arg_values_list: Union[Iterable[Union[nox._parametrize.Param, Iterable[Any]]], nox._parametrize.Param, Iterable[Any]], ids: Optional[Iterable[Optional[str]]] = None) Callable[[Any], Any]

参数化会话

使用给定的 arg_namesarg_values_list 列表,向底层会话函数添加新的调用。参数化是在会话发现期间进行的,每个调用都作为一个单独的会话出现在 nox 中。

参数
  • arg_names (Sequence[str]) – 参数名称的列表。

  • arg_values_list (Sequence[Union[Any, Tuple]]) – 参数值列表决定了一个会话在不同参数值下被调用的频率。如果只指定了一个参数名,那么这就是一个简单的值列表,例如 [1, 2, 3]。如果指定了 N 个参数名,这必须是一个 N 个元组的列表,其中每个元组元素为其各自的参数名指定一个值,例如 [(1, 'a'), (2, 'b')]

  • ids (Sequence[str]) – 可选的测试 ID 序列,用于参数化的实参。

你也可以将装饰器堆叠起来,产生的会话是参数的组合,例如:

@nox.session
@nox.parametrize('django', ['1.9', '2.0'])
@nox.parametrize('database', ['postgres', 'mysql'])
def tests(session, django, database):
    ...

如果你运行 nox --list,你会看到这产生了以下一组会话:

* tests(database='postgres', django='1.9')
* tests(database='mysql', django='1.9')
* tests(database='postgres', django='2.0')
* tests(database='mysql', django='2.0')

如果你只想运行其中一个参数化的会话,请参阅 指定参数化的会话

给予参数化的会话以友好的名称

The automatically generated names for parametrized sessions, such as tests(django='1.9', database='postgres'), can be long and unwieldy to work with even with using keyword filtering. You can give parametrized sessions custom IDs to help in this scenario. These two examples are equivalent:

@nox.session
@nox.parametrize('django',
    ['1.9', '2.0'],
    ids=['old', 'new'])
def tests(session, django):
    ...
@nox.session
@nox.parametrize('django', [
    nox.param('1.9', id='old'),
    nox.param('2.0', id='new'),
])
def tests(session, django):
    ...

When running nox --list you’ll see their new IDs:

* tests(old)
* tests(new)

And you can run them with nox --sessions "tests(old)" and so on.

This works with stacked parameterizations as well. The IDs are combined during the combination. For example:

@nox.session
@nox.parametrize(
    'django',
    ['1.9', '2.0'],
    ids=["old", "new"])
@nox.parametrize(
    'database',
    ['postgres', 'mysql'],
    ids=["psql", "mysql"])
def tests(session, django, database):
    ...

Produces these sessions when running nox --list:

* tests(psql, old)
* tests(mysql, old)
* tests(psql, new)
* tests(mysql, new)

Parametrizing the session Python

You can use parametrization to select the Python interpreter for a session. These two examples are equivalent:

@nox.session
@nox.parametrize("python", ["3.6", "3.7", "3.8"])
def tests(session):
    ...

@nox.session(python=["3.6", "3.7", "3.8"])
def tests(session):
    ...

The first form can be useful if you need to exclude some combinations of Python versions with other parameters. For example, you may want to test against multiple versions of a dependency, but the latest version doesn’t run on older Pythons:

@nox.session
@nox.parametrize(
    "python,dependency",
    [
        (python, dependency)
        for python in ("3.6", "3.7", "3.8")
        for dependency in ("1.0", "2.0")
        if (python, dependency) != ("3.6", "2.0")
    ],
)
def tests(session, dependency):
    ...

The session object

Nox will call your session functions with a an instance of the Session class.

class Session(runner: nox.sessions.SessionRunner)

The Session object is passed into each user-defined session function.

This is your primary means for installing package and running commands in your Nox session.

property bin: str

The first bin directory for the virtualenv.

property bin_paths: Optional[List[str]]

The bin directories for the virtualenv.

property cache_dir: pathlib.Path

Create and return a ‘shared cache’ directory to be used across sessions.

cd(dir: Union[str, os.PathLike]) nox.sessions._WorkingDirContext

An alias for chdir().

chdir(dir: Union[str, os.PathLike]) nox.sessions._WorkingDirContext

Change the current working directory.

Can be used as a context manager to automatically restore the working directory:

with session.chdir("somewhere/deep/in/monorepo"):
    # Runs in "/somewhere/deep/in/monorepo"
    session.run("pytest")

# Runs in original working directory
session.run("flake8")
conda_install(*args: str, auto_offline: bool = True, channel: Union[str, Sequence[str]] = '', **kwargs: Any) None

Install invokes conda install to install packages inside of the session’s environment.

To install packages directly:

session.conda_install('pandas')
session.conda_install('numpy', 'scipy')
session.conda_install('dask==2.1.0', channel='conda-forge')

To install packages from a requirements.txt file:

session.conda_install('--file', 'requirements.txt')
session.conda_install('--file', 'requirements-dev.txt')

By default this method will detect when internet connection is not available and will add the –offline flag automatically in that case. To disable this behaviour, set auto_offline=False.

To install the current package without clobbering conda-installed dependencies:

session.install('.', '--no-deps')
# Install in editable mode.
session.install('-e', '.', '--no-deps')

You can specify a conda channel using channel=; a falsy value will not change the current channels. You can specify a list of channels if needed.

Additional keyword args are the same as for run().

create_tmp() str

Create, and return, a temporary directory.

debug(*args: Any, **kwargs: Any) None

Outputs a debug-level message during the session.

property env: dict

A dictionary of environment variables to pass into all commands.

error(*args: Any) NoReturn

Immediately aborts the session and optionally logs an error.

install(*args: str, **kwargs: Any) None

Install invokes pip to install packages inside of the session’s virtualenv.

To install packages directly:

session.install('pytest')
session.install('requests', 'mock')
session.install('requests[security]==2.9.1')

To install packages from a requirements.txt file:

session.install('-r', 'requirements.txt')
session.install('-r', 'requirements-dev.txt')

To install the current package:

session.install('.')
# Install in editable mode.
session.install('-e', '.')

Additional keyword args are the same as for run().

property interactive: bool

Returns True if Nox is being run in an interactive session or False otherwise.

property invoked_from: str

The directory that Nox was originally invoked from.

Since you can use the --noxfile / -f command-line argument to run a Noxfile in a location different from your shell’s current working directory, Nox automatically changes the working directory to the Noxfile’s directory before running any sessions. This gives you the original working directory that Nox was invoked form.

log(*args: Any, **kwargs: Any) None

Outputs a log during the session.

property name: str

The name of this session.

notify(target: Union[str, nox.sessions.SessionRunner], posargs: Optional[Iterable[str]] = None) None

Place the given session at the end of the queue.

This method is idempotent; multiple notifications to the same session have no effect.

A common use case is to notify a code coverage analysis session from a test session:

@nox.session
def test(session):
    session.run("pytest")
    session.notify("coverage")

@nox.session
def coverage(session):
    session.run("coverage")

Now if you run nox -s test, the coverage session will run afterwards.

参数
  • target (Union[str, Callable]) – The session to be notified. This may be specified as the appropriate string (same as used for nox -s) or using the function object.

  • posargs (Optional[Iterable[str]]) – If given, sets the positional arguments only for the queued session. Otherwise, the standard globally available positional arguments will be used instead.

property posargs: List[str]

Any extra arguments from the nox commandline or Session.notify.

property python: Optional[Union[str, Sequence[str], bool]]

The python version passed into @nox.session.

run(*args: str, env: Optional[Mapping[str, str]] = None, **kwargs: Any) Optional[Any]

Run a command.

Commands must be specified as a list of strings, for example:

session.run('pytest', '-k', 'fast', 'tests/')
session.run('flake8', '--import-order-style=google')

You can not just pass everything as one string. For example, this will not work:

session.run('pytest -k fast tests/')

You can set environment variables for the command using env:

session.run(
    'bash', '-c', 'echo $SOME_ENV',
    env={'SOME_ENV': 'Hello'})

You can also tell nox to treat non-zero exit codes as success using success_codes. For example, if you wanted to treat the pytest “tests discovered, but none selected” error as success:

session.run(
    'pytest', '-k', 'not slow',
    success_codes=[0, 5])

On Windows, builtin commands like del cannot be directly invoked, but you can use cmd /c to invoke them:

session.run('cmd', '/c', 'del', 'docs/modules.rst')

If session.run fails, it will stop the session and will not run the next steps. Basically, this will raise a Python exception. Taking this in count, you can use a try...finally block for cleanup runs, that will run even if the other runs fail:

try:
    session.run("coverage", "run", "-m", "pytest")
finally:
    # Display coverage report even when tests fail.
    session.run("coverage", "report")
参数
  • env (dict or None) – A dictionary of environment variables to expose to the command. By default, all environment variables are passed.

  • silent (bool) – Silence command output, unless the command fails. False by default.

  • success_codes (list, tuple, or None) – A list of return codes that are considered successful. By default, only 0 is considered success.

  • external (bool) – If False (the default) then programs not in the virtualenv path will cause a warning. If True, no warning will be emitted. These warnings can be turned into errors using --error-on-external-run. This has no effect for sessions that do not have a virtualenv.

run_always(*args: str, env: Optional[Mapping[str, str]] = None, **kwargs: Any) Optional[Any]

Run a command always.

This is a variant of run() that runs even in the presence of --install-only. This method returns early if --no-install is specified and the virtualenv is being reused.

Here are some cases where this method is useful:

  • You need to install packages using a command other than pip install or conda install.

  • You need to run a command as a prerequisite of package installation, such as building a package or compiling a binary extension.

参数
  • env (dict or None) – A dictionary of environment variables to expose to the command. By default, all environment variables are passed.

  • silent (bool) – Silence command output, unless the command fails. False by default.

  • success_codes (list, tuple, or None) – A list of return codes that are considered successful. By default, only 0 is considered success.

  • external (bool) – If False (the default) then programs not in the virtualenv path will cause a warning. If True, no warning will be emitted. These warnings can be turned into errors using --error-on-external-run. This has no effect for sessions that do not have a virtualenv.

skip(*args: Any) NoReturn

Immediately skips the session and optionally logs a warning.

property virtualenv: nox.virtualenv.ProcessEnv

The virtualenv that all commands are run in.

warn(*args: Any, **kwargs: Any) None

Outputs a warning during the session.

Modifying Nox’s behavior in the Noxfile

Nox has various command line arguments that can be used to modify its behavior. Some of these can also be specified in the Noxfile using nox.options. For example, if you wanted to store Nox’s virtualenvs in a different directory without needing to pass it into nox every time:

import nox

nox.options.envdir = ".cache"

@nox.session
def tests(session):
    ...

Or, if you wanted to provide a set of sessions that are run by default:

import nox

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

...

The following options can be specified in the Noxfile:

  • nox.options.envdir is equivalent to specifying –envdir.

  • nox.options.sessions is equivalent to specifying -s or –sessions. If set to an empty list, no sessions will be run if no sessions were given on the command line, and the list of available sessions will be shown instead.

  • nox.options.pythons is equivalent to specifying -p or –pythons.

  • nox.options.keywords is equivalent to specifying -k or –keywords.

  • nox.options.default_venv_backend is equivalent to specifying -db or –default-venv-backend.

  • nox.options.force_venv_backend is equivalent to specifying -fb or –force-venv-backend.

  • nox.options.reuse_existing_virtualenvs is equivalent to specifying –reuse-existing-virtualenvs. You can force this off by specifying --no-reuse-existing-virtualenvs during invocation.

  • nox.options.stop_on_first_error is equivalent to specifying –stop-on-first-error. You can force this off by specifying --no-stop-on-first-error during invocation.

  • nox.options.error_on_missing_interpreters is equivalent to specifying –error-on-missing-interpreters. You can force this off by specifying --no-error-on-missing-interpreters during invocation.

  • nox.options.error_on_external_run is equivalent to specifying –error-on-external-run. You can force this off by specifying --no-error-on-external-run during invocation.

  • nox.options.report is equivalent to specifying –report.

When invoking nox, any options specified on the command line take precedence over the options specified in the Noxfile. If either --sessions or --keywords is specified on the command line, both options specified in the Noxfile will be ignored.

Nox version requirements

Nox version requirements can be specified in your Noxfile by setting nox.needs_version. If the Nox version does not satisfy the requirements, Nox exits with a friendly error message. For example:

import nox

nox.needs_version = ">=2019.5.30"

@nox.session(name="test")  # name argument was added in 2019.5.30
def pytest(session):
    session.run("pytest")

Any of the version specifiers defined in PEP 440 can be used.

警告

Version requirements must be specified as a string literal, using a simple assignment to nox.needs_version at the module level. This allows Nox to check the version without importing the Noxfile.