pytest-dev/pytest · error · UsageError

: config option ' ' expects a string, got

Error message

{self.inipath}: config option '{name}' expects a string, got {value_type}: {value!r}

What it means

Raised by pytest's TOML config parser when a 'string'-type ini option receives a non-string value. TOML mode requires the value to be a str; numbers, bools, dates, arrays, and tables are all rejected without coercion.

Solutions

  1. Quote the value: change `minversion = 8.0` to `minversion = "8.0"`
  2. If multiple values are needed, check whether the option supports a list type; if not, keep a single string
  3. Validate the parsed value is str using tomllib

Example fix

// pyproject.toml — before
[tool.pytest.ini_options]
minversion = 8.0

// after
[tool.pytest.ini_options]
minversion = "8.0"
Defensive patterns

Strategy: validation

Validate before calling

import tomllib
with open('pyproject.toml', 'rb') as f:
    data = tomllib.load(f)
opts = data.get('tool', {}).get('pytest', {}).get('ini_options', {})
for key, val in opts.items():
    if not isinstance(val, str) and not isinstance(val, list) and not isinstance(val, bool):
        print(f"{key}: got {type(val).__name__} {val!r}, string type expects a quoted string")

Type guard

def is_toml_string(value: object) -> bool:
    return isinstance(value, str)

Prevention

When it happens

Trigger: An addini(type='string') option (the default type when type=None) is set to a non-string in pyproject.toml. Example: python_files = 42 or minversion = 8.0 instead of minversion = "8.0". The `isinstance(value, str)` check fails.

Common situations: Omitting quotes around a value that looks like a number or version string. Using a TOML array for a single-string option. Providing a TOML date or datetime where a string is expected.

Related errors


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

Appendix: source

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

            # Expect an integer (but not bool, which is a subclass of int).
            if not isinstance(value, int) or isinstance(value, bool):
                raise TypeError(
                    f"{self.inipath}: config option '{name}' expects an int, "
                    f"got {value_type}: {value!r}"
                )
            return value
        elif type == "float":
            # Expect a float or integer only.
            if not isinstance(value, (float, int)) or isinstance(value, bool):
                raise TypeError(
                    f"{self.inipath}: config option '{name}' expects a float, "
                    f"got {value_type}: {value!r}"
                )
            return value
        elif type == "string":
            # Expect a string.
            if not isinstance(value, str):
                raise TypeError(
                    f"{self.inipath}: config option '{name}' expects a string, "
                    f"got {value_type}: {value!r}"
                )
            return value
        else:
            return self._getini_unknown_type(name, type, value)

    def _getconftest_pathlist(
        self, name: str, path: pathlib.Path
    ) -> list[pathlib.Path] | None:
        try:
            mod, relroots = self.pluginmanager._rget_with_confmod(name, path)
        except KeyError:
            return None
        assert mod.__file__ is not None
        modpath = pathlib.Path(mod.__file__).parent
        values: list[pathlib.Path] = []
        for relroot in relroots:

View on GitHub (pinned to 0d6fbdeffa)