pytest-dev/pytest · error · UsageError
: config option ' ' expects one of , got
Error message
{self.inipath}: config option '{name}' expects one of {_ini_type_repr(type)}, got {builtins.type(value).__name__}: {value!r} What it means
Raised by _getini() at config/__init__.py:1843-1846 when an ini option registered with a union type (e.g. int | str) receives a value that matches NONE of the union members. The loop at capture.py:1836-1842 tries each member's _getini_value; if all raise TypeError/ValueError, the trailing raise at config/__init__.py:1843 fires. The outer try/except at capture.py:1847-1848 re-raises as UsageError so the user sees a clean message.
Solutions
- Provide a value matching one of the union members (typically a string or integer)
- Quote the value if you want it treated as a string in toml mode
- If you control the plugin, register the union more permissively (e.g. int | str | bool)
- Move the option to a CLI flag where argparse can do the type coercion
Example fix
// before # pyproject.toml [tool.pytest.ini_options] foo = true # addini type is int | str // after foo = 42 # or foo = "true"
Defensive patterns
Strategy: validation
Validate before calling
# Before writing pyproject.toml programmatically, validate against the union
import tomllib
cfg = tomllib.loads(open('pyproject.toml').read())
opts = cfg.get('tool',{}).get('pytest',{}).get('ini_options',{})
for k, v in opts.items():
# plugin must expose its accepted types; check docs/source
pass Type guard
def matches_union(value, members) -> bool:
for m in members:
if m == 'int' and isinstance(value, int): return True
if m == 'str' and isinstance(value, str): return True
if m == 'bool' and isinstance(value, bool): return True
if m == 'float' and isinstance(value, (int, float)): return True
return False Try / catch
from _pytest.config import UsageError
try:
val = config.getini('opt')
except UsageError as e:
print(f'Bad value: {e}') Prevention
- Quote TOML values you want treated as strings
- Document each union member your plugin accepts
- Test pyproject.toml configs in CI with the actual installed plugin versions
When it happens
Trigger: A plugin registers addini('foo', type=int | str) and the user provides a value in pyproject.toml that is neither an int nor parseable as an int string (e.g. a list or a bool). The union loop fails all members and capture.py:1843 raises.
Common situations: pyproject.toml config providing a TOML list/bool where the option expects int or str; ini option type widened to a union but old configs still supplying incompatible values; toml-mode parsing surfacing native types that ini mode would have treated as strings.
Related errors
- : config option ' ' expects a string, got
- ' ' is not recognized as a logging level name for ' '…
- : config option ' ' expects one of , got
- tmp_path_retention_count must be >= 0. Current input
- Expected a float string for option
AI-assisted analysis of pytest-dev/pytest@0d6fbdeffa (2026-08-11).
Data as JSON: /api/errors/9d8eca5238391071.
Report an issue: GitHub.
Appendix: source
Thrown at src/_pytest/config/__init__.py:1843
mode = selected.mode
# An invalid value is a user error, raised as UsageError so that it is
# reported as a short message rather than an internal error traceback.
try:
if not isinstance(type, tuple):
return self._getini_value(
mode, name, canonical_name, type, value, default
)
# Union: try each member; the first one that accepts the value wins.
for member in type:
try:
return self._getini_value(
mode, name, canonical_name, member, value, default
)
except (TypeError, ValueError):
pass
raise TypeError(
f"{self.inipath}: config option '{name}' expects one of "
f"{_ini_type_repr(type)}, got {builtins.type(value).__name__}: {value!r}"
)
except (TypeError, ValueError) as e:
raise UsageError(str(e)) from e
def _getini_value(
self,
mode: Literal["ini", "toml"],
name: str,
canonical_name: str,
type: str | _IniLiteral,
value: object,
default: Any,
):
"""Convert a config value, read in the given mode, to the option's type."""
if isinstance(type, _IniLiteral):
# A Literal value is a plain string checked against the registeredView on GitHub (pinned to 0d6fbdeffa)