Python 任務自動化工具:nox 的配置與 API

語言: CN / TW / HK

Noxfile
Nox 預設在一個名為noxfile.py的檔案中查詢配置。在執行 nox 時,你可以使用 --noxfile引數指定其它的檔案。

定義會話
格式:session(func=None, python=None, py=None, reuse_venv=None, name=None, venv_backend=None),將被裝飾的函式指定為一個會話。

Nox 會話是通過被@nox.session裝飾的標準 Python 函式來配置的。例如:

import nox

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

會話描述
你可以使用文件字串向會話中新增一個描述。第一行內容會在列出會話時顯示。例如:

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

你還可以告訴 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.5', '3.6', '3.7'])
def tests(session):
    pass

將產生這些會話:

* tests-2.7
* tests-3.5
* tests-3.6
* tests-3.7

注意,這個擴充套件發生在引數化之前,所以你仍然可以對多個直譯器的會話進行引數化。

如果你想完全禁止建立 virtualenv,你可以設定 python 引數為 False:

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

最後,你還可以指定每次都重用 virtualenv,而不是重新建立:

@nox.session(
    python=['2.7', '3.6'],
    reuse_venv=True)
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 > pip install django==1.9
...
nox > Running session tests(djano='2.0')
nox > pip install django==2.0

nox.parametrize() 的介面和用法故意跟pytest的引數化 相類似。

格式:parametrize(arg_names, arg_values_list, ids=None)

作用是引數化一個會話。

將 arg_values_list 列表賦給對應的 arg_names,為裝飾的會話函式新增新的呼叫。引數化在會話發現期間執行,每次呼叫都作為 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')

如果你只想執行一個引數化會話,請參閱"指定引數化會話"部分。

為引數化的會話起友好的名稱
自動生成的引數化會話的名稱,如tests(django='1.9', database='postgres'),即使用關鍵字過濾,也可能很長且很難處理。

在此場景中,可以為引數化會話提供輔助的自定義 id 。這兩個例子是等價的:

@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):
    ...

當執行nox --list時,你將看到它們的新 id:

* tests(old)
* tests(new)

你可以用nox --sessions "tests(old)",以此類推。

這也適用於堆疊引數化。id 是在組合期間組合的。例如:

@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):
    ...

執行nox --list時會產生這些會話:

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

會話物件
Nox 將使用 Session 類的一個例項來呼叫你的會話函式。

class Session(runner) ¶

會話物件被傳遞到使用者自定義的每個會話函式中。

這是在 Nox 會話中安裝軟體包和執行命令的主要途徑。

  • bin¶——virtualenv 的 bin 目錄
  • cd(dir)¶——chdir() 的一個別名
  • chdir(dir)¶——更改當前的工作目錄
  • conda_install(

    args,

    *kwargs)¶

呼叫conda install來在會話環境中的安裝軟體包。

直接安裝軟體包:

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

根據 requirements.txt 檔案來安裝軟體包:

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

不破壞 conda 已安裝的依賴而安裝軟體包:

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

剩下的關鍵字引數跟 run() 相同。

  • env¶——一個環境變數的字典,傳給所有的命令。
  • error(

    args,

    *kwargs)¶——立即中止會話並隨意地記錄一個錯誤。 * install(

    args,

    *kwargs)¶ ——呼叫 pip 在會話的 virtualenv 裡安裝包。

直接安裝包:

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

根據 requirements.txt 檔案來安裝軟體包:

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

安裝當前的包:

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

剩下的關鍵字引數跟 run() 相同。

  • interactive¶ ——如果 Nox 在互動式會話中執行,則返回 True,否則返回 False。
  • log(

    args,

    *kwargs)¶——在會話期間輸出一份日誌。 * notify(target)¶ ——將給定的會話放在佇列的末尾。

此方法是冪等的;對同一會話的多次通知無效。
引數:target (Union[str, Callable])——需要通知的會話。這可以指定適當的字串(與nox -s 的使用相同)或使用函式物件。

  • posargs¶ ——用於設定從命令列上傳給 nox 的額外引數。
  • python¶ ——傳給@nox.session的 Python 版本。
  • run(args, env=None, kwargs)¶ ——執行一個命令。

命令必須安裝字串列表指定,例如:

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

你不能把所有東西都當作一個字串傳遞。例如,不可以這樣:
session.run('pytest -k fast tests/')

你可以用env 為命令設定環境變數:

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

你還可以使用success_codes ,告訴 nox 將非零退出碼視為成功。例如,如果你想將 pytest 的“tests discovered, but none selected”錯誤視為成功:

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

在 Windows 上,像del這樣的內建命令不能直接呼叫,但是你可以使用cmd /c 來呼叫它們:

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

引數:

  • env (dict or None)——用於向命令公開的環境變數字典。預設情況下,傳遞所有環境變數。
  • silent (bool) ——靜默命令輸出,除非命令失敗。預設為 False。
  • success_codes (list, tuple, or None)——一系列被認為是成功的返回碼。預設情況下,只有 0 被認為是成功的。
  • external (bool) ——如果為 False(預設值),那麼不在 virtualenv 路徑中的程式將發出告警。如果為 True,則不會發出告警。這些告警可以使用--error-on-external-run將其轉換為錯誤。這對沒有 virtualenv 的會話沒有影響。
  • skip(

    args,

    *kwargs)¶ ——立即跳出會話,並隨意記錄一個告警。 * virtualenv¶ ——執行所有命令的 virtualenv。

修改 Noxfile 中的 Nox 行為
Nox 有各種命令列引數,可用於修改其行為。其中一些還可以在 Noxfile 中使用 nox.options 指定。例如,如果你想將 Nox 的 virtualenvs 儲存在不同的目錄中,而不需要每次都將它傳遞給 nox:

import nox

nox.options.envdir = ".cache"

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

或者,如果你想提供一組預設執行的會話:

import nox

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

...

以下的選項可以在 Noxfile 中指定:

  • nox.options.envdir 等同於指定 –envdir.
  • nox.options.sessions 等同於指定 -s or –sessions.
  • nox.options.keywords 等同於指定 -k or –keywords.
  • nox.options.reuse_existing_virtualenvs 等同於指定 –reuse-existing-virtualenvs 。通過在呼叫時指定 --no-reuse-existing-virtualenvs ,你可以強制取消它。
  • nox.options.stop_on_first_error 等同於指定 –stop-on-first-error. 通過在呼叫時指定 --no-stop-on-first-error,你可以強制取消它。
  • nox.options.error_on_missing_interpreters 等同於指定 –error-on-missing-interpreters 。通過在呼叫時指定 --no-error-on-missing-interpreters ,你可以強制取消它。
  • nox.options.error_on_external_run 等同於指定 –error-on-external-run. 通過在呼叫時指定 --no-error-on-external-run ,你可以強制取消它。
  • nox.options.report 等同於指定 –report。

在呼叫 nox 時,命令列上指定的任何選項都優先於 Noxfile 中指定的選項。如果在命令列上指定了--sessions或--keywords,那麼在 Noxfile 中指定的兩個選項都將被忽略。

以上就是本次分享的所有內容,想要了解更多 python 知識歡迎前往公眾號:Python 程式設計學習圈 ,傳送 “J” 即可免費獲取,每日干貨分享