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
- Indent/move every reported top-level option under a [pytest] table header in that file.
- If the file is pyproject.toml, move the options under [tool.pytest.ini_options] instead of leaving them at the top level.
- 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
- Always nest pytest options under [pytest] (in pytest.toml) or [tool.pytest.ini_options] (in pyproject.toml).
- Review config after renaming files between pytest.ini/pytest.toml/pyproject.toml.
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
- {path}: Cannot use both [pytest] and [tool.pytest]/[tool.pyt
- {path}: {exc}
- {path}: Cannot use both [tool.pytest] (native TOML types) an
- {self.inipath}: config option '{name}' expects one of {_ini_
- {self.inipath}: config option '{name}' expects a string, got
AI-assisted analysis of pytest-dev/pytest@0d6fbdeffa (2026-08-11).
Data as JSON: /api/errors/e308d6775589ef78.
Report an issue: GitHub.