配置和 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.session
的 name
参数来定制会话的名称。比如说:
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_names
的arg_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.
- 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 orSession.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 thepytest
“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 usecmd /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 atry...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
orconda 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.