pytest-dev/pytest · error · UsageError

: config option ' ' expects a string, got

Error message

{self.inipath}: config option '{name}' expects a string, got {builtins.type(value).__name__}: {value!r}

What it means

Raised by _getini_value() at config/__init__.py:1864-1867 when an ini option registered with a Literal[...] type (normalized to _IniLiteral, argparsing.py:47) receives a non-string value. The check at capture.py:1860-1867 enforces that Literal choices are matched against a plain string in both ini and toml modes; passing a TOML int/bool/list triggers TypeError, which is then converted to UsageError by the outer handler at config/__init__.py:1847-1848.

Solutions

  1. Quote the value so it is parsed as a TOML string: mode = "a"
  2. Pick one of the documented Literal choices
  3. If you control the plugin, broaden the type to Literal[...] | str or document the allowed strings clearly

Example fix

// before
# pyproject.toml
[tool.pytest.ini_options]
log_level = 1   # Literal['DEBUG','INFO',...]
// after
log_level = "DEBUG"
Defensive patterns

Strategy: validation

Validate before calling

# Ensure choice-typed ini values are TOML strings
import tomllib
cfg = tomllib.loads(open('pyproject.toml').read())
for k, v in cfg.get('tool',{}).get('pytest',{}).get('ini_options',{}).items():
    if not isinstance(v, str) and not isinstance(v, (list,)):
        print(f'{k} should be a string, got {type(v).__name__}')

Type guard

def is_str_or_list(v) -> bool:
    return isinstance(v, str) or (isinstance(v, list) and all(isinstance(x, str) for x in v))

Try / catch

from _pytest.config import UsageError
try:
    val = config.getini('mode')
except UsageError as e:
    print(f'Set mode to a quoted string in pyproject.toml: {e}')

Prevention

When it happens

Trigger: A plugin registers addini('mode', type=Literal['a','b','c']) and the user writes `mode = 1` or `mode = true` in pyproject.toml. The non-string check at capture.py:1863 raises TypeError.

Common situations: TOML-native values for choice options (where the user assumed the choice would accept any truthy value or numeric key); migrating ini configs to pyproject.toml and losing the implicit string quoting; plugin docs not making clear that the option is choice-constrained.

Related errors


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

Appendix: source

Thrown at src/_pytest/config/__init__.py:1864

            )
        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 registered
            # choices, without coercion, in both ini and toml modes.
            if not isinstance(value, str):
                raise TypeError(
                    f"{self.inipath}: config option '{name}' expects a string, "
                    f"got {builtins.type(value).__name__}: {value!r}"
                )
            if value not in type.choices:
                raise ValueError(
                    f"{self.inipath}: config option '{name}' expects one of "
                    f"{_ini_type_repr(type)}, got {value!r}"
                )
            return value
        if mode == "ini":
            # In ini mode, values are always str | list[str].
            assert isinstance(value, (str, list))
            return self._getini_ini(name, canonical_name, type, value, default)
        elif mode == "toml":
            return self._getini_toml(name, canonical_name, type, value, default)
        else:
            assert_never(mode)

View on GitHub (pinned to 0d6fbdeffa)