tox

tox ist ein Automatisierungstool, das ähnlich wie ein CI-Tool funktioniert, aber sowohl lokal als auch in Verbindung mit anderen CI-Tools auf einem Server ausgeführt werden kann.

Im Folgenden richten wir uns tox für unsere Tasks-Anwendung so ein, dass es uns beim lokalen Testen hilft. Anschließend werden wir das Testen mithilfe von GitHub Actions einrichten.

Einführung in tox

tox ist ein Kommandozeilen-Tool, mit dem ihr eure komplette Testsuite in verschiedenen Umgebungen ausführen könnt. Wir werden tox verwenden, um das Tasks-Projekt in mehreren Python-Versionen zu testen. tox ist jedoch nicht nur auf Python-Versionen beschränkt. Ihr könnt es zum Testen mit verschiedenen Abhängigkeits-Konfigurationen und verschiedenen Konfigurationen für verschiedene Betriebssysteme verwenden. tox verwendet dabei Projektinformationen aus der setup.py- oder pyproject.toml-Datei für das zu testende Paket, um eine installierbare Distribution eures Pakets zu erstellen. Es sucht im [tool.tox]-Abschnitt der pyproject.toml-Datei nach einer Liste von Umgebungen, und führt dann jeweils folgende Schritte aus:

  1. erstellt eine virtuelle Umgebung

  2. installiert einige Abhängigkeiten mit pip

  3. baut euer Paket

  4. installiert euer Paket mit pip

  5. führt weitere Tests aus

Nachdem alle Umgebungen getestet wurden, gibt tox eine Zusammenfassung der Ergebnisse aus.

Um diesen Prozess mit uv zu beschleunigen, verwenden wir tox nicht direkt, sondern tox-uv.

Bemerkung

Obwohl tox von vielen Projekten verwendet wird, gibt es Alternativen, die ähnliche Funktionen erfüllen. Zwei Alternativen zu tox sind nox und invoke.

tox einrichten

Früher wurde tox üblicherweise in der tox.ini-Datei konfiguriert. Seit tox 4.44.0 ist deren Funktionalität jedoch eingefroren und zukünftige Konfigurationsparameter werden wohl nur noch in einer TOML-Datei bereitgestellt werden, z. B. in der pyproject.toml-Datei. Werfen wir einen Blick auf eine einfache Konfiguration in der pyproject.toml-Datei:

[tool.tox]
env_list = ["py314"]

[tool.tox.env_run_base]
dependency_groups = [ "tests" ]
commands = [[ "pytest"]]

Im [tool.tox]-Abschnitt haben wir env_list = ["py314"] definiert. Dies ist eine Abkürzung, die tox anweist, unsere Tests mit Python Version 3.14 durchzuführen. Wir werden in Kürze weitere Python-Versionen hinzufügen, aber die Verwendung einer Version hilft uns zunächst, den Ablauf von tox besser zu verstehen.

Im [tool.tox.env_run_base]-Abschnitt wird in dependency_groups tests angegeben. Somit weiß tox, dass die entsprechenden Bibliotheken in dieser Umgebung installiert werden sollen. Mit commands wird tox schließlich angewiesen, pytest auszuführen.

tox ausführen

Bevor ihr tox ausführen könnt, müsst ihr sicherstellen, dass ihr tox-uv installiert habt:

$ uv add --group tests tox-uv
C:> uv add --group tests tox-uv

Um tox auszuführen, startet einfach tox:

$ uv run tox
.pkg: _optional_hooks> python /Users/veit/cusy/prj/cusy.tasks/.venv/lib/python3.14/site-packages/pyproject_api/_backend.py True hatchling.build
.pkg: get_requires_for_build_sdist> python /Users/veit/cusy/prj/cusy.tasks/.venv/lib/python3.14/site-packages/pyproject_api/_backend.py True hatchling.build
.pkg: build_sdist> python /Users/veit/cusy/prj/cusy.tasks/.venv/lib/python3.14/site-packages/pyproject_api/_backend.py True hatchling.build
py314: install_package> .venv/bin/uv pip install --reinstall --no-deps cusy.tasks@/Users/veit/cusy/prj/cusy.tasks/.tox/.tmp/package/18/cusy.tasks-26.2.0.tar.gz
py314: commands[0]> python --version --version
Python 3.14.0 (main, Oct  7 2024, 23:47:22) [Clang 18.1.8 ]
py314: commands[1]> coverage run -m pytest
============================= test session starts ==============================
platform darwin -- Python 3.14.0, pytest-9.0.2, pluggy-1.6.0
cachedir: .tox/py314/.pytest_cache
rootdir: /Users/veit/cusy/prj/cusy.tasks
configfile: pyproject.toml
testpaths: tests
plugins: Faker-40.1.0, cov-7.0.0
collected 83 items
tests/api/test_add.py ......                                             [  7%]
tests/api/test_config.py .                                               [  8%]
tests/api/test_count.py ...                                              [ 12%]
tests/api/test_delete.py ...                                             [ 15%]
tests/api/test_delete_all.py ..                                          [ 18%]
tests/api/test_exceptions.py ..                                          [ 20%]
tests/api/test_finish.py ....                                            [ 25%]
tests/api/test_task.py ...                                               [ 28%]
tests/api/test_task_id.py .                                              [ 30%]
tests/api/test_list.py .........                                         [ 40%]
tests/api/test_list_edge_cases.py ........                               [ 50%]
tests/api/test_start.py ....                                             [ 55%]
tests/api/test_update.py .....                                           [ 61%]
tests/api/test_version.py .                                              [ 62%]
tests/cli/test_add.py ..                                                 [ 65%]
tests/cli/test_config.py ..                                              [ 67%]
tests/cli/test_count.py .                                                [ 68%]
tests/cli/test_delete.py .                                               [ 69%]
tests/cli/test_errors.py .......                                         [ 78%]
tests/cli/test_finish.py .                                               [ 79%]
tests/cli/test_help.py .........                                         [ 90%]
tests/cli/test_list.py .....                                             [ 96%]
tests/cli/test_start.py .                                                [ 97%]
tests/cli/test_update.py .                                               [ 98%]
tests/cli/test_version.py .                                              [100%]

============================== 83 passed in 0.35s ==============================
.pkg: _exit> python /Users/veit/cusy/prj/cusy.tasks/.venv/lib/python3.14/site-packages/pyproject_api/_backend.py True hatchling.build
  py314: OK (1.19=setup[0.45]+cmd[0.01,0.72] seconds)
  congratulations :) (1.23 seconds)

Mehrere Python-Versionen testen

Hierfür erweitern wir envlist in der pyproject.toml-Datei um weitere Python-Versionen hinzuzufügen:

[tool.tox]
env_list = [
  "py3{10-14}",
  "py{13-14}t",
]
skip_missing_interpreters = true

Damit werden wir jetzt Python-Versionen von 3.10 bis 3.14 testen. Zusätzlich haben wir auch die Einstellung skip_missing_interpreters = true hinzugefügt, damit tox nicht fehlschlägt, wenn auf eurem System eine der aufgeführten Python-Versionen fehlt. Ist der Wert auf true gesetzt, führt tox die Tests mit jeder verfügbaren Python-Version durch, überspringt aber Versionen, die es nicht findet, ohne fehlzuschlagen. Die Ausgabe ist sehr ähnlich, wobei ich in der folgenden Darstellung lediglich die Unterschiede hervorhebe:

$ uv run tox
.pkg: _optional_hooks> python /Users/veit/cusy/prj/cusy.tasks/.venv/lib/python3.14/site-packages/pyproject_api/_backend.py True hatchling.build
.pkg: get_requires_for_build_sdist> python /Users/veit/cusy/prj/cusy.tasks/.venv/lib/python3.14/site-packages/pyproject_api/_backend.py True hatchling.build
.pkg: build_sdist> python /Users/veit/cusy/prj/cusy.tasks/.venv/lib/python3.14/site-packages/pyproject_api/_backend.py True hatchling.build
py310: install_package> .venv/bin/uv pip install --reinstall --no-deps cusy.tasks@/Users/veit/cusy/prj/cusy.tasks/.tox/.tmp/package/19/cusy.tasks-26.2.0.tar.gz
py310: commands[0]> python --version --version
Python 3.10.17 (main, Apr  9 2025, 03:47:39) [Clang 20.1.0 ]
py310: commands[1]> coverage run -m pytest
============================= test session starts ==============================
...
============================== 83 passed in 0.35s ==============================
py310: OK ✔ in 1.3 seconds
py311: install_package> .venv/bin/uv pip install --reinstall --no-deps cusy.tasks@/Users/veit/cusy/prj/cusy.tasks/.tox/.tmp/package/20/cusy.tasks-26.2.0.tar.gz
py311: commands[0]> python --version --version
Python 3.11.11 (main, Feb  5 2025, 18:58:27) [Clang 19.1.6 ]
py311: commands[1]> coverage run -m pytest
============================= test session starts ==============================
...
============================== 83 passed in 0.36s ==============================
py311: OK ✔ in 1.16 seconds
py312: install_package> .venv/bin/uv pip install --reinstall --no-deps cusy.tasks@/Users/veit/cusy/prj/cusy.tasks/.tox/.tmp/package/21/cusy.tasks-26.2.0.tar.gz
py312: commands[0]> python --version --version
Python 3.12.12 (main, Oct 14 2025, 21:38:21) [Clang 20.1.4 ]
py312: commands[1]> coverage run -m pytest
============================= test session starts ==============================
...
============================== 83 passed in 0.55s ==============================
py312: OK ✔ in 1.79 seconds
py313: install_package> .venv/bin/uv pip install --reinstall --no-deps cusy.tasks@/Users/veit/cusy/prj/cusy.tasks/.tox/.tmp/package/22/cusy.tasks-26.2.0.tar.gz
py313: commands[0]> python --version --version
Python 3.13.0 (main, Oct  7 2024, 23:47:22) [Clang 18.1.8 ]
py313: commands[1]> coverage run -m pytest
============================= test session starts ==============================
...
============================== 83 passed in 0.35s ==============================
py313: OK ✔ in 1.07 seconds
py314: install_package> .venv/bin/uv pip install --reinstall --no-deps cusy.tasks@/Users/veit/cusy/prj/cusy.tasks/.tox/.tmp/package/23/cusy.tasks-26.2.0.tar.gz
py314: commands[0]> python --version --version
Python 3.14.0 (main, Oct 14 2025, 21:10:22) [Clang 20.1.4 ]
py314: commands[1]> coverage run -m pytest
============================= test session starts ==============================
...
============================== 83 passed in 0.36s ==============================
py314: OK ✔ in 1.28 seconds
py313t: install_package> .venv/bin/uv pip install --reinstall --no-deps cusy.tasks@/Users/veit/cusy/prj/cusy.tasks/.tox/.tmp/package/24/cusy.tasks-26.2.0.tar.gz
py313t: commands[0]> python --version --version
Python 3.13.0 experimental free-threading build (main, Oct 16 2024, 08:24:33) [Clang 18.1.8 ]
py313t: commands[1]> coverage run -m pytest
============================= test session starts ==============================
...
============================== 83 passed in 0.49s ==============================
py313t: OK ✔ in 1.51 seconds
py314t: install_package> .venv/bin/uv pip install --reinstall --no-deps cusy.tasks@/Users/veit/cusy/prj/cusy.tasks/.tox/.tmp/package/25/cusy.tasks-26.2.0.tar.gz
py314t: commands[0]> python --version --version
Python 3.14.0b4 free-threading build (main, Jul  8 2025, 21:06:49) [Clang 20.1.4 ]
py314t: commands[1]> coverage run -m pytest
============================= test session starts ==============================
...
============================== 83 passed in 0.39s ==============================
.pkg: _exit> python /Users/veit/cusy/prj/cusy.tasks/.venv/lib/python3.14/site-packages/pyproject_api/_backend.py True hatchling.build
  py310: OK (1.30=setup[0.54]+cmd[0.01,0.75] seconds)
  py311: OK (1.16=setup[0.38]+cmd[0.01,0.76] seconds)
  py312: OK (1.79=setup[0.42]+cmd[0.01,1.36] seconds)
  py313: OK (1.07=setup[0.34]+cmd[0.01,0.71] seconds)
  py314: OK (1.28=setup[0.42]+cmd[0.01,0.85] seconds)
  py313t: OK (1.51=setup[0.44]+cmd[0.01,1.05] seconds)
  py314t: OK (1.34=setup[0.44]+cmd[0.01,0.89] seconds)
  congratulations :) (9.48 seconds)

Geändert in Version tox: 4.25.0 Vor tox 4.25.0 vom 27. März 2025 mussten die verschiedenen Python-Versionen einzeln angegeben werden:

[tool.tox]
envlist = ["py3{10,11,12,13,14,13t,14t}"]

Tox-Umgebungen parallel ausführen

Im vorherigen Beispiel wurden die verschiedenen Umgebungen nacheinander ausgeführt. Es ist auch möglich, sie mit der Option -p parallel laufen zu lassen:

$ uv run tox -p
py311: OK ✔ in 1.7 seconds
py310: OK ✔ in 1.8 seconds
py313: OK ✔ in 1.8 seconds
py314t: OK ✔ in 1.89 seconds
py314: OK ✔ in 1.91 seconds
py313t: OK ✔ in 2.24 seconds
  py310: OK (1.80=setup[0.62]+cmd[0.02,1.16] seconds)
  py311: OK (1.70=setup[0.54]+cmd[0.02,1.15] seconds)
  py312: OK (2.28=setup[0.58]+cmd[0.01,1.69] seconds)
  py313: OK (1.80=setup[0.60]+cmd[0.02,1.18] seconds)
  py314: OK (1.91=setup[0.62]+cmd[0.02,1.28] seconds)
  py313t: OK (2.24=setup[0.72]+cmd[0.02,1.51] seconds)
  py314t: OK (1.89=setup[0.61]+cmd[0.02,1.26] seconds)
  congratulations :) (2.33 seconds)

Bemerkung

Die Ausgabe ist nicht abgekürzt; dies ist die gesamte Ausgabe, die ihr seht, wenn alles funktioniert.

Coverage-Report in tox hinzufügen

Der pyproject.toml-Datei kann einfach die Konfiguration von Coverage Reports hinzugefügt werden. Dazu müssen wir pytest-cov in der tests-Abhängigkeitsgruppe hinzufügen, damit das pytest-cov-Plugin auch in den tox-Testumgebungen installiert wird. Das Einbinden von pytest-cov schließt auch alle weiteren Abhängigkeiten ein, wie z. B. Coverage. Wir fügen dann noch die env.coverage-report.OPTIONS hinzu und ändern env_run_base.commands:

[dependency-groups]
...
tests = [
  "faker",
  "pytest>=6",
  "pytest-cov",
]

[tool.tox]
requires = [ "tox>=4" ]
env_list = [
  "py3{10-14}",
  "py{13-14}t",
]
skip_missing_interpreters = true
env.coverage-report.description = "Report coverage over all test runs."
env.coverage-report.deps = [ "coverage[toml]" ]
env.coverage-report.depends = [ "py" ]
env.coverage-report.skip_install = true
env.coverage-report.commands = [
  [ "coverage combine" ],
  [ "coverage report" ],
]
env_run_base.dependency_groups = [ "tests" ]
env_run_base.deps = [ "coverage[toml]" ]
env_run_base.commands = [
  [ "coverage", "run", "-m", "pytest" ],
]

Bei der Verwendung von Coverage mit tox kann es manchmal sinnvoll sein, in der pyproject.toml-Datei einen Abschnitt einzurichten, der Coverage mitteilt, welche Quelltext-Pfade als identisch betrachtet werden sollen:

[tool.coverage.paths]
source = ["src", ".tox/py*/**/site-packages"]

Der Tasks-Quellcode befindet sich zunächst in src/cusy/tasks/, bevor von tox die virtuellen Umgebungen erstellt und cusy.tasks in der Umgebung installiert wird. Dann befindet es sich z. B. in .tox/py314/lib/python3.14/site-packages/cusy.tasks.

$ uv run tox
...
Name    Stmts   Miss Branch BrPart  Cover   Missing
---------------------------------------------------
TOTAL     540      0     32      0   100%

33 files skipped due to complete coverage.
  py310: OK (1.10=setup[0.44]+cmd[0.01,0.64] seconds)
  py311: OK (0.98=setup[0.31]+cmd[0.01,0.66] seconds)
  py312: OK (1.59=setup[0.34]+cmd[0.01,1.24] seconds)
  py313: OK (1.06=setup[0.34]+cmd[0.01,0.71] seconds)
  py314: OK (1.10=setup[0.35]+cmd[0.01,0.74] seconds)
  py313t: OK (1.36=setup[0.40]+cmd[0.01,0.95] seconds)
  py314t: OK (1.31=setup[0.44]+cmd[0.01,0.86] seconds)
  coverage-report: OK (1.55=setup[0.37]+cmd[1.08,0.10] seconds)
  congratulations :) (10.09 seconds)

Mindestabdeckungsgrad festlegen

Bei der Ausführung der Coverage durch tox ist auch sinnvoll, einen Mindestabdeckungsgrad festzulegen, um eventuelle Ausrutscher bei der Coverage zu erkennen. Dies wird mit der Option --cov-fail-under erreicht:

Name               Stmts   Miss Branch BrPart  Cover   Missing
--------------------------------------------------------------
src/cusy.tasks/api.py 68      1     12      1    98%   88
--------------------------------------------------------------
TOTAL                428      1     32      1    99%

26 files skipped due to complete coverage.
Coverage failure: total of 99 is less than fail-under=100

Dadurch wird der Ausgabe die hervorgehobene Zeile hinzugefügt.

pytest-Parameter an tox übergeben

Wir können auch einzelne Tests mit tox aufrufen, indem wir eine weitere Änderung vornehmen, damit Parameter an pytest übergeben werden können:

[tool.tox]
requires = [ "tox>=4" ]
env_list = [
  "pre-commit",
  "docs",
  "py3{10-14}",
  "py{13-14}t",
  "coverage-report",
]
skip_missing_interpreters = true
env_run_base.dependency_groups = [ "tests" ]
env_run_base.deps = [ "coverage[toml]" ]
env_run_base.commands = [
  [ "python", "--version", "--version" ],
  [ "coverage", "run", "-m", "pytest", "{posargs}" ],
]

Um Argumente an pytest zu übergeben, fügt sie zwischen den tox-Argumenten und den pytest-Argumenten ein. In diesem Fall wählen wir test_version-Tests mit der Schlüsselwort-Option -k aus. Wir verwenden auch --no-cov, um die Abdeckung zu deaktivieren:

$ uv run tox -e py314 -- -k test_version --no-cov
...
coverage-report: commands[0]> coverage combine
Combined data file .coverage.fay.local.19539.XpQXpsGx
coverage-report: commands[1]> coverage report
Name               Stmts   Miss Branch BrPart  Cover   Missing
--------------------------------------------------------------
src/cusy.tasks/api.py 68      1     12      1    98%   88
--------------------------------------------------------------
TOTAL                428      1     32      1    99%

26 files skipped due to complete coverage.
  py310: OK (2.12=setup[1.49]+cmd[0.63] seconds)
  py311: OK (1.41=setup[0.80]+cmd[0.62] seconds)
  py312: OK (1.43=setup[0.81]+cmd[0.62] seconds)
  py313: OK (1.46=setup[0.83]+cmd[0.62] seconds)
  py314: OK (1.46=setup[0.83]+cmd[0.62] seconds)
  coverage-report: OK (0.16=setup[0.00]+cmd[0.07,0.09] seconds)
  congratulations :) (10.26 seconds)

tox eignet sich nicht nur hervorragend für die lokale Automatisierung von Testprozessen, sondern hilft auch bei Server-basierter CI. Fahren wir fort mit der Ausführung von pytest und tox mithilfe von GitHub-Aktionen.

tox mit GitHub-Aktionen ausführen

Wenn euer Projekt auf GitHub gehostet ist, könnt ihr GitHub-Actions verwenden um automatisiert eure Tests in verschiedenen Umgebungen ausführen zu können. Dabei sind eine ganze Reihe von Umgebungen für die GitHub-Actions verfügbar: github.com/actions/runner-images.

  1. Um eine GitHub-Action in eurem Projekt zu erstellen, klickt auf Actions ‣ set up a workflow yourself. Dies erstellt üblicherweise eine Datei .github/workflows/main.yml.

  2. Gebt dieser Datei einen aussagekräftigeren Namen. Wir verwenden hierfür üblicherweise ci.yml.

  3. Die vorausgefüllte YAML-Datei ist für unsere Zwecke wenig hilfreich. Ihr könnt hier einen coverage-Abschnitt einfügen, z.B. mit:

    jobs:
      coverage:
        name: Ensure 100% test coverage
        runs-on: ubuntu-latest
        needs: tests
        if: always()
    
        steps:
          - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
            with:
              persist-credentials: false
          - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
            with:
              python-version-file: .python-version
          - uses: hynek/setup-cached-uv@4300ec2180bc77d705e626a34e381b81a4772c51 # v2.5.0
    
          - name: Download coverage data
            uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
            with:
              pattern: coverage-data-*
              merge-multiple: true
    
          - name: Combine coverage and fail if it’s <100%.
            run: |
              uv tool install coverage
    
              coverage combine
              coverage html --skip-covered --skip-empty
    
              # Report and write to summary.
              coverage report --format=markdown >> $GITHUB_STEP_SUMMARY
    
              # Report again and fail if under 100%.
              coverage report --fail-under=100
    
    name

    kann ein beliebiger Name sein. Er wird in der Benutzeroberfläche von GitHub Actions angezeigt.

    steps

    ist eine Liste von Schritten. Der Name eines jeden Schrittes kann beliebig sein und ist optional.

    uses: actions/checkout

    ist ein GitHub-Actions-Tool, das unser Repository auscheckt, damit der Rest des Workflows darauf zugreifen kann.

    uses: actions/setup-python

    ist ein GitHub-Actions-Tool, das Python konfiguriert und in einer Build-Umgebung installiert.

    with: python-version: ${{ matrix.python }}

    sagt, dass eine Umgebung für jede der in matrix.python aufgeführten Python-Versionen erstellt werden soll.

    uses: hynek/setup-cached-uv

    uses uv in GitHub Actions.

    Siehe auch

  4. Anschließend könnt ihr auf Start commit klicken. Da wir noch weitere Änderungen vornehmen wollen bevor die Tests automatisiert ausgeführt werden sollen, wählen wir Create a new branch for this commit and start a pull request und als Name für den neuen Branch github-actions. Schließlich könnt ihr auf Create pull request klicken.

  5. Um nun in den neuen Branch zu wechseln, gehen wir zu Code ‣ main ‣ github-actions.

Die Actions-Syntax ist gut dokumentiert. Ein guter Startpunkt in der GitHub-Actions-Dokumentation ist die Seite Building and Testing Python. Die Dokumentation zeigt euch auch, wie ihr pytest direkt ohne tox ausführen könnt und wie ihr die Matrix auf mehrere Betriebssysteme erweitern könnt. Sobald ihr eure *.yml-Datei eingerichtet und in euer GitHub-Repository hochgeladen habt, wird sie automatisch ausgeführt. Im Reiter Actions könnt ihr anschließend die Durchläufe sehen:

Screenshot der GitHub-Actions-Übersicht

Die verschiedenen Python-Umgebungen sind auf der linken Seite aufgelistet. Wenn ihr eine auswählt, werden die Ergebnisse für diese Umgebung angezeigt, wie im folgenden Screenshot dargestellt:

Screenshot eines GitHub-Actions-Run für eine Umgebung

Badge anzeigen

Nun könnt ihr in eurer README.rst-Datei noch ein Badge eures CI-Status hinzufügen, z.B. mit:

.. image:: https://github.com/YOU/YOUR_PROJECT/workflows/CI/badge.svg?branch=main
   :target: https://github.com/YOU/YOUR_PROJECT/actions?workflow=CI
   :alt: CI Status

Testabdeckung veröffentlichen

Die Testabdeckung könnt ihr auf GitHub veröffentlichen, s.a. Coverage GitHub-Actions.

tox erweitern

tox verwendet pluggy, um das Standardverhalten anzupassen. Pluggy findet ein Plugin, indem es nach einem Einstiegspunkt mit dem Namen tox sucht, z.B. in einer pyproject.toml-Datei:

[project.entry-points.tox]
my_plugin = "my_plugin.hooks"

Um das Plugin zu verwenden, muss es daher lediglich in der gleichen Umgebung installiert werden, in der auch tox läuft, und es wird über den definierten Einstiegspunkt gefunden.

Ein Plugin wird durch die Implementierung von Erweiterungspunkten in Form von Hooks erstellt. Der folgende Codeschnipsel würde zum Beispiel ein neues –my CLI definieren:

from tox.config.cli.parser import ToxParser
from tox.plugin import impl


@impl
def tox_add_option(parser: ToxParser) -> None:
    parser.add_argument("--my", action="store_true", help="my option")

tox-uv

tox-uv ist ein Tox-Plugin, das virtualenv und pip durch uv in euren Tox-Umgebungen ersetzt.

Ihr könnt tox und tox-uv installieren mit:

$ uv tool install tox --with tox-uv

uv.lock-Unterstützung

Wenn ihr für eine Tox-Umgebung uv sync mit einer uv.lock-Datei verwenden wollt, müsst ihr für diese Tox-Umgebung den Runner auf uv-venv-lock-runner ändern, zum Beispiel:

pyproject.toml
env.app.dependency_groups = [ "tests" ]
env.app.runner = "uv-venv-lock-runner"
commands = [[ "pytest"]]

Die app-Umgebung verwendet den uv-venv-lock-runner und nutzt uv sync --locked, um die Abhängigkeiten in den Versionen der uv.lock-Datei zu installieren.

Siehe auch