pytest-dev/pytest · error · UsageError

{path}: pytest configuration must be under a [pytest] table

Error message

{path}: pytest configuration must be under a [pytest] table (found top-level options: {top_level_options})

What it means

When pytest encounters a [pytest] table in a TOML file, it expects all pytest settings to live inside that table. If, instead, top-level scalar keys sit beside (or instead of) the [pytest] table, pytest rejects them. This guards against the common mistake of writing 'addopts = ...' at the top of a pyproject-style file without a parent table.

Source

Thrown at src/_pytest/config/findpaths.py:146

) -> ConfigDict | None:
    """Return the configuration in the ``[pytest]`` table of a parsed TOML
    document, or None if it has none.

    Raise UsageError for options written outside of any table, which is the
    usual way of getting the table wrong.
    """
    if "pytest" in config:
        # TOML mode - preserve native TOML types.
        return {
            k: ConfigValue(v, origin="file", mode="toml")
            for k, v in config["pytest"].items()  # type: ignore[attr-defined]
        }

    top_level_options = [
        key for key, value in config.items() if not isinstance(value, dict)
    ]
    if top_level_options:
        raise UsageError(
            f"{path}: pytest configuration must be under a "
            f"[pytest] table (found top-level options: "
            f"{', '.join(top_level_options)})"
        )
    return None


def _config_from_tool_pytest(
    path: Path, config: dict[str, object]
) -> ConfigDict | None:
    """Return the configuration in the ``[tool.pytest]`` tables of a parsed
    TOML document, or None if it has none."""
    tool_pytest = config.get("tool", {}).get("pytest", {})  # type: ignore[attr-defined]

    # Check for toml mode config: [tool.pytest] with content outside of ini_options.
    toml_config = {k: v for k, v in tool_pytest.items() if k != "ini_options"}
    # Check for ini mode config: [tool.pytest.ini_options].
    ini_config = tool_pytest.get("ini_options", None)

View on GitHub (pinned to 0d6fbdeffa)

Solutions

  1. Indent/move every reported top-level option under a [pytest] table header in that file.
  2. If the file is pyproject.toml, move the options under [tool.pytest.ini_options] instead of leaving them at the top level.
  3. Remove keys that do not belong to pytest entirely.

Example fix

// before
addopts = ["-ra"]
testpaths = ["tests"]
// after
[pytest]
addopts = ["-ra"]
testpaths = ["tests"]
Defensive patterns

Strategy: validation

Validate before calling

import tomllib, pathlib

def validate_pytest_table(path: str) -> None:
    doc = tomllib.loads(pathlib.Path(path).read_text())
    top = [k for k, v in doc.items() if not isinstance(v, dict)]
    assert not top, f'top-level options must move under [pytest]: {top}'

Type guard

null

Try / catch

null

Prevention

When it happens

Trigger: A pytest.ini-style flat key (e.g. 'addopts = ...') placed at the document root of a .toml file; a [pytest] table present AND stray top-level option keys next to it.

Common situations: Renaming pytest.ini to pytest.toml without wrapping keys under [pytest]; mixing pytest.ini and pyproject.toml conventions in one file.

Related errors


AI-assisted analysis of pytest-dev/pytest@0d6fbdeffa (2026-08-11). Data as JSON: /api/errors/e308d6775589ef78. Report an issue: GitHub.