Nox: Python自動化タスクランナーの徹底解説

Noxとは何か? 🤔

Noxは、Pythonプロジェクトにおけるテスト、リンティング、ビルドなどの様々なタスクを自動化するためのコマンドラインツールです。特に、複数のPythonバージョンや環境で一貫したテストを実行したい場合に強力な味方となります。

似たようなツールとしてToxがありますが、Noxの最大の特徴は、設定ファイルをPythonスクリプト(noxfile.py)で記述する点です。これにより、Toxの設定ファイル(tox.ini)よりも柔軟で強力な設定が可能になります。Pythonの標準ライブラリやサードパーティライブラリを自由に利用して、複雑なタスク定義や動的な設定を行うことができます。

Noxは、Google Cloud PlatformのPythonクライアントライブラリや、pip, pipx, cibuildwheel, Scikit-HEP, Urllib3など、多くの著名なPythonプロジェクトで採用されています。

🚀 Noxのインストール

Noxは通常、プロジェクト固有の仮想環境ではなく、グローバルな環境にインストールすることが推奨されます。これにより、どのプロジェクトディレクトリからでもnoxコマンドを実行できるようになります。

pipxを使うのが最も推奨される方法です。pipxはPython製のCLIツールを独立した環境にインストール・管理するためのツールです。


pipx install nox
      

もしpipxがインストールされていない場合は、まずpipxをインストールしてください。


python3 -m pip install --user pipx
python3 -m pipx ensurepath
      

あるいは、通常のpipを使ってグローバルなPython環境にインストールすることも可能です(ただし、システムのPython環境を汚染する可能性があるため注意が必要です)。


python3 -m pip install --user nox
      

インストール後、以下のコマンドでNoxが正しくインストールされたか確認できます。


nox --version
      

または


pip show nox
      

これにより、インストールされているNoxのバージョン情報が表示されます。

🔧 基本的な使い方: `noxfile.py` の作成と実行

Noxの設定は、プロジェクトのルートディレクトリにnoxfile.pyという名前のPythonファイルを作成して行います。このファイル内で、「セッション」と呼ばれるタスクの単位を定義します。

セッションは、特定のPython環境とその環境で実行される一連のコマンドを定義したものです。これはToxにおける「環境(environment)」に相当します。

簡単な`noxfile.py`の例

以下は、`flake8`によるリンティングと`pytest`によるテストを実行する簡単なnoxfile.pyの例です。


import nox

# @nox.sessionデコレータを使ってセッションを定義
@nox.session
def lint(session):
    """コードの静的解析を実行するセッション"""
    # セッション内で使用するライブラリをインストール
    session.install("flake8")
    # インストールしたコマンドを実行
    session.run("flake8", ".", "--count", "--select=E9,F63,F7,F82", "--show-source", "--statistics")

@nox.session(python=["3.9", "3.10", "3.11", "3.12"]) # 複数のPythonバージョンを指定
def tests(session):
    """pytestを使ってテストを実行するセッション"""
    # 現在のプロジェクトとテスト用依存ライブラリをインストール
    # requirements.txt や pyproject.toml (Poetry, PDMなど) があればそれらも利用可能
    session.install(".[test]") # もし `.[test]` のようなextraがあれば
    # session.install("pytest", "pytest-cov") # 個別に指定も可能
    # pytestを実行。 session.posargs を使うと、noxコマンド経由で引数を渡せる
    session.run("pytest", *session.posargs)
      

セッションの実行

noxfile.pyを作成したら、ターミナルで以下のコマンドを実行します。

利用可能なセッションの一覧表示:


nox -l
# または
nox --list
      

これにより、noxfile.pyで定義されているセッションの一覧が表示されます。上記の例だと、`lint`と`tests-3.9`, `tests-3.10`, `tests-3.11`, `tests-3.12` が表示されるでしょう。

すべてのセッションを実行:


nox
      

引数なしでnoxを実行すると、デフォルトで定義されているすべてのセッションが順番に実行されます。Noxは各セッションごとに独立した仮想環境(デフォルトでは`.nox`ディレクトリ以下に作成)を自動で作成し、指定された依存関係をインストールし、コマンドを実行します。

特定のセッションを実行:


nox -s lint
# または
nox --session lint
      

-s (または --session) オプションに続けてセッション名を指定すると、そのセッションのみを実行できます。

複数のセッションを個別に指定して実行:


nox -s lint tests-3.10
      

特定のPythonバージョンを指定して実行:

Noxでは、Pythonバージョンもセッション選択のキーとして使えます。


nox -p 3.11
# または
nox --python 3.11
      

これにより、Python 3.11を使用するように指定されているすべてのセッション(この例では`tests-3.11`)が実行されます。これはToxの-e py311とは異なり、Pythonバージョン自体をセレクタとして扱える点がNoxの便利な特徴です。

Positional Arguments (コマンドライン引数の引き渡し):

session.posargsを使うと、noxコマンドに渡された追加の引数をセッション内のコマンド(例: `pytest`)に渡すことができます。


# pytestに `-k "特定のテスト名"` と `-v` オプションを渡す
nox -s tests -- -k "特定のテスト名" -v
      

-- の後に続く引数が session.posargs にリストとして格納されます。

📝 セッションの詳細設定

@nox.sessionデコレータや、セッション関数に渡されるsessionオブジェクトを通じて、様々な設定を行うことができます。

`@nox.session` デコレータの引数

引数 説明
python セッションで使用するPythonインタプリタのバージョンを指定します。文字列またはリストで指定できます。Falseを指定すると仮想環境を作成しません(システムPythonを使用)。 python="3.11"
python=["3.9", "3.10"]
python=False
venv_backend 仮想環境を作成するバックエンドを指定します。"virtualenv" (デフォルト), "conda", "mamba", "uv" などが選択可能です。Noneを指定すると仮想環境を作成しません。 venv_backend="conda"
venv_backend="uv"
venv_params 仮想環境作成時にバックエンドに渡す追加の引数をリストで指定します。 venv_params=["--system-site-packages"]
reuse_venv Trueを指定すると、既存の仮想環境を再利用します。デフォルトはFalseで、毎回クリーンな環境を作成します。 reuse_venv=True
name セッションのデフォルト名(関数名)を上書きします。 name="docs-build"
tags セッションにタグを付けます。タグを使ってセッションをグループ化し、-tオプションで実行できます。 tags=["test", "core"]

`session` オブジェクトの主なメソッドと属性

メソッド/属性 説明
session.install(*args) 指定されたパッケージをセッションの仮想環境にインストールします。pip installと同じ引数を指定できます。 session.install("pytest", "requests>=2.0")
session.install("-r", "requirements.txt")
session.install(".[dev]")
session.run(*cmd, **kwargs) 指定されたコマンドをセッションの仮想環境内で実行します。コマンドとその引数を個別の文字列として渡します。external=Trueを指定すると、仮想環境外のコマンドも実行できます。 session.run("pytest", "-v", "tests/")
session.run("bash", "-c", "echo 'Hello'", external=True)
session.log(message) コンソールにメッセージを出力します。 session.log("テストを開始します...")
session.error(*message) エラーメッセージを出力し、セッションを失敗させます。 session.error("必要なファイルが見つかりません")
session.skip(*message) メッセージを出力し、セッションをスキップします。 session.skip("この環境では実行不要です")
session.posargs noxコマンドラインで--以降に渡された引数のリスト。 session.run("pytest", *session.posargs)
session.python セッションで使用されているPythonインタプリタのバージョン文字列。 if session.python == "3.11": ...
session.invoked_from Noxが実行されたディレクトリのパス。 print(session.invoked_from)
session.create_tmp() 一時ディレクトリを作成し、そのパスを返します。セッション終了時に自動でクリーンアップされます。 tmpdir = session.create_tmp()
session.cd(path) セッション内でのカレントディレクトリを変更します。コンテキストマネージャとしても使用可能です。 with session.cd("docs"): session.run("make", "html")

これらの機能を組み合わせることで、非常に柔軟なタスク自動化を実現できます。

✨ 高度な機能

パラメータ化セッション (`@nox.parametrize`)

pytestのパラメータ化と同様に、Noxでもセッションをパラメータ化できます。これにより、同じ処理を異なるパラメータで繰り返し実行するセッションを簡潔に記述できます。


import nox

@nox.session
@nox.parametrize(
    "django_version",
    ["3.2", "4.0", "4.1"]
)
def tests(session, django_version):
    session.install(f"django=={django_version}", "pytest", ".")
    session.run("pytest")

# 実行されるセッション:
# tests(django_version='3.2')
# tests(django_version='4.0')
# tests(django_version='4.1')
      

複数のパラメータを組み合わせることも可能です。


import nox

@nox.session(python=["3.10", "3.11"])
@nox.parametrize(
    "db_backend",
    ["sqlite", "postgres"]
)
def tests(session, db_backend):
    session.install(f".[{db_backend}]", "pytest")
    # 環境変数を使ってDBバックエンドを指定する例
    env = {"DATABASE_URL": f"{db_backend}://..."}
    session.run("pytest", env=env)

# 実行されるセッション例:
# tests-3.10(db_backend='sqlite')
# tests-3.10(db_backend='postgres')
# tests-3.11(db_backend='sqlite')
# tests-3.11(db_backend='postgres')
      

タグ付けとタグによる実行

@nox.sessionデコレータのtags引数を使って、セッションにタグを付けることができます。これにより、関連するセッションをグループ化し、-t (または --tags) オプションでまとめて実行できます。


import nox

@nox.session(tags=["test"])
def unit_tests(session):
    session.install(".[test]", "pytest")
    session.run("pytest", "tests/unit")

@nox.session(tags=["test", "integration"])
def integration_tests(session):
    session.install(".[test]", "pytest", "docker")
    # Dockerコンテナ起動などの前処理
    session.run("pytest", "tests/integration")

@nox.session(tags=["lint"])
def linting(session):
    session.install("flake8", "black", "isort")
    session.run("flake8", ".")
    session.run("black", "--check", ".")
    session.run("isort", "--check-only", ".")

@nox.session(tags=["docs"])
def build_docs(session):
    session.install(".[docs]", "sphinx")
    session.run("sphinx-build", "docs/", "docs/_build/html")
      

実行例:

  • すべてのテストを実行: nox -t test
  • リンティングのみ実行: nox -t lint
  • テストとドキュメントビルドを実行: nox -t test docs

再利用可能な関数と設定

noxfile.py は通常のPythonファイルなので、ヘルパー関数を定義したり、設定を共通化したりすることが容易です。


import nox

# 共通の設定
nox.options.sessions = ["lint", "tests"] # noxコマンドでデフォルト実行されるセッションを指定
nox.options.stop_on_first_error = True # 最初のセッション失敗で停止

# Pythonバージョンのリストを共通化
SUPPORTED_PYTHONS = ["3.9", "3.10", "3.11", "3.12"]

# 共通のインストール処理を関数化
def install_dev_deps(session):
    session.install("-r", "requirements-dev.txt")
    session.install(".")

@nox.session(python=SUPPORTED_PYTHONS)
def tests(session):
    install_dev_deps(session)
    session.run("pytest", *session.posargs)

@nox.session
def lint(session):
    install_dev_deps(session)
    session.run("flake8", ".")
    session.run("black", "--check", ".")

@nox.session
def format(session):
    """コードフォーマットを実行する (デフォルトでは実行されない)"""
    install_dev_deps(session)
    session.run("black", ".")
    session.run("isort", ".")
      

外部ツールとの統合 (Poetry, PDM, uv)

Noxは他のパッケージ管理ツールや高速化ツールとも連携できます。

Poetry/PDM: これらのツールを使っている場合でも、Noxはうまく連携できます。session.install(".")pyproject.toml を認識し、プロジェクトとその依存関係をインストールします。


# PoetryやPDMプロジェクトでのテストセッション例
import nox

@nox.session(python=["3.10", "3.11"])
def tests(session):
    # Poetry/PDMが管理する依存関係を含めてインストール
    session.install(".[test]") # Poetryの extras や PDM の dev-dependencies グループなど
    session.run("pytest")

@nox.session
def lint(session):
    # Poetry/PDMでdev依存として管理されているリンターをインストール
    session.install(".[lint]") # 例: [tool.poetry.group.lint.dependencies] や [tool.pdm.dev-dependencies]
    session.run("flake8")
    session.run("mypy")
      

uv: 高速なPythonパッケージインストーラーである uv をNoxの仮想環境バックエンドとして使用できます。uvがシステムにインストールされていれば、noxfile.py の先頭に以下を追加するだけで利用できます。


import nox

# uv が利用可能であればデフォルトのバックエンドとして設定 (Nox 2024.3.2以降が必要)
nox.needs_version = ">=2024.3.2"
nox.options.default_venv_backend = "uv|virtualenv" # uvがあればuvを、なければvirtualenvを使う

@nox.session
def tests(session):
    # uvを使って高速にインストールされる
    session.install("pytest", ".")
    session.run("pytest")
      

🛠️ 実践的なユースケース

Noxは様々な開発タスクの自動化に活用できます。

  • 複数Pythonバージョンでのテスト: 最も一般的なユースケースです。@nox.session(python=["3.9", "3.10", ...]) のように指定するだけで、各バージョンで独立したテスト環境を構築・実行できます。
  • リンティングとフォーマット: Flake8, Black, isort, MyPyなどを使ったコード品質チェックと自動修正タスクを定義できます。フォーマット用のセッションとチェック用のセッションを分けることも一般的です。
  • ドキュメント生成: SphinxやMkDocsを使ったドキュメントのビルド、ローカルでのプレビューサーバーの起動などを自動化できます。
  • パッケージのビルドと公開: buildライブラリを使ったホイールやsdistの作成、twineを使ったPyPIへのアップロードなどをセッションとして定義できます。リリースプロセスの一貫性を保つのに役立ちます。
  • カバレッジレポート生成: pytest-covcoverage.py を使ってテストカバレッジを測定し、レポート(HTMLやXML)を生成するセッションを作成できます。
  • 依存関係の更新チェック: pip-tools などを利用して、依存関係のピン留めファイルを更新したり、最新バージョンでのテストを実行したりするセッションを作成できます。

import nox
import tempfile
import os

# デフォルトセッションの設定
nox.options.sessions = ["lint", "mypy", "tests"]
# 使用するPythonバージョン
PYTHON_VERSIONS = ["3.9", "3.10", "3.11", "3.12"]


def install_with_constraints(session, *args, **kwargs):
    # Poetryを使用している場合、exportして制約ファイルとして使う例
    # requirements.txt がある場合はそれを使うなど、プロジェクトに合わせて調整
    with tempfile.NamedTemporaryFile(delete=False) as requirements:
        session.run(
            "poetry",
            "export",
            "--dev",
            "--format=requirements.txt",
            f"--output={requirements.name}",
            external=True, # poetryは通常仮想環境外にあるため
        )
        session.install(f"--constraint={requirements.name}", *args, **kwargs)
    os.unlink(requirements.name)


@nox.session(python=PYTHON_VERSIONS)
def tests(session):
    """Pytestでテストを実行し、カバレッジレポートを生成する"""
    args = session.posargs or ["--cov=src", "--cov-report=term-missing", "--cov-report=xml"]
    session.run("poetry", "install", "--no-dev", external=True) # プロジェクト本体をインストール
    install_with_constraints(session, "pytest", "pytest-cov")
    session.run("pytest", *args)


@nox.session(python=PYTHON_VERSIONS[-1]) # 最新のPythonでLintを実行
def lint(session):
    """Flake8, Black, isortでリンティングを実行する"""
    args = session.posargs or ["src", "tests", "noxfile.py"]
    install_with_constraints(
        session,
        "flake8",
        "flake8-black",
        "flake8-isort",
        "flake8-bugbear",
        "flake8-bandit",
        "flake8-docstrings",
    )
    session.run("flake8", *args)


@nox.session(python=PYTHON_VERSIONS[-1])
def black(session):
    """Blackでコードフォーマットをチェックする"""
    args = session.posargs or ["--check", "."]
    install_with_constraints(session, "black")
    session.run("black", *args)


@nox.session(python=PYTHON_VERSIONS[-1])
def isort(session):
    """isortでimport順をチェックする"""
    args = session.posargs or ["--check-only", "."]
    install_with_constraints(session, "isort")
    session.run("isort", *args)


@nox.session(python=PYTHON_VERSIONS)
def mypy(session):
    """MyPyで型チェックを実行する"""
    args = session.posargs or ["src"]
    install_with_constraints(session, "mypy")
    session.run("mypy", *args)


@nox.session(python=PYTHON_VERSIONS[-1])
def docs(session):
    """Sphinxでドキュメントをビルドする"""
    session.run("poetry", "install", "--only=docs", external=True)
    install_with_constraints(session, "sphinx", "sphinx-rtd-theme")
    session.run("sphinx-build", "docs", "docs/_build/html")


@nox.session(python=PYTHON_VERSIONS[-1])
def build(session):
    """ホイールとsdistをビルドする"""
    install_with_constraints(session, "build")
    session.run("python", "-m", "build")

       

🆚 Nox vs Tox: 簡単な比較

NoxとToxは目的は似ていますが、アプローチが異なります。

特徴 Nox Tox
設定ファイル noxfile.py (Pythonスクリプト) tox.ini または pyproject.toml ([tool.tox]セクション) (INI形式ベースのDSL)
柔軟性 非常に高い。Pythonの全機能を利用可能。動的な設定、複雑なロジックの実装が容易。 比較的低い。INI形式と独自のDSLに制約される。複雑な処理はシェルスクリプトなどに頼る必要がある場合がある。
学習コスト Pythonの知識があれば比較的容易。Nox特有のAPIを覚える必要はある。 Tox独自のDSLとINI形式を学ぶ必要がある。
用語 セッション (Session) 環境 (Environment / Env)
Pythonバージョン選択 第一級のセレクタとして扱える (nox -p 3.10) 環境名のプレフィックスとして扱う (tox -e py310)
並列実行 標準ではサポートされていない(将来的に追加される可能性あり) -p オプションで並列実行が可能
エコシステム/歴史 比較的新しいが、多くの主要プロジェクトで採用が進んでいる。活発に開発されている。 長年の実績があり、Pythonテスト自動化のデファクトスタンダード。豊富なプラグインが存在する。

どちらを選ぶべきかは、プロジェクトの要件やチームの好みによります。

  • INI形式の設定に慣れており、シンプルなテスト自動化で十分な場合はToxが適しているかもしれません。
  • より複雑なタスクの自動化、動的な設定、Pythonスクリプトによる柔軟性を求める場合はNoxが強力な選択肢となります。

(参考: 2023年1月 Hynek Schlawack氏のブログ記事 “Why I Like Nox”)

🎉 まとめ

Noxは、Pythonプロジェクトにおける様々な開発タスクを自動化するための強力で柔軟なツールです。noxfile.pyというPythonスクリプトで設定を記述することにより、単純なテスト実行から複雑なビルドパイプラインまで、幅広いニーズに対応できます。

Noxの主なメリット:

  • ✅ 設定ファイルがPythonスクリプトであり、高い柔軟性と表現力を持つ。
  • ✅ 複数Pythonバージョンでのテスト実行が容易。
  • ✅ 仮想環境の管理を自動化。
  • ✅ パラメータ化やタグ付けにより、セッション管理がしやすい。
  • ✅ PoetryやPDM、uvなどのモダンなツールとの連携も可能。
  • ✅ 多くの主要プロジェクトで採用されており、活発に開発されている。

もしあなたがPythonプロジェクトで繰り返し行うタスクに手間を感じているなら、Noxの導入を検討してみてはいかがでしょうか。きっと開発ワークフローの改善に貢献してくれるはずです! ✨