pytest-dev/pytest · error · ValueError

invalid type for ini option {name!r}: Literal choices must b

Error message

invalid type for ini option {name!r}: Literal choices must be strings, got {choices!r}

What it means

Raised by Parser.addini() when the type= argument is a typing.Literal whose choices include non-string values. pytest's ini subsystem only supports Literal enumerations of strings because ini-style config files are fundamentally string-based.

Source

Thrown at src/_pytest/config/argparsing.py:87

def _ini_type_to_tag(name: str, type_: object) -> _IniTypeTag:
    """Normalize one member of an `addini(type=...)` argument to a string tag."""
    try:
        return _INI_TYPES[type_]
    except (KeyError, TypeError):  # TypeError: unhashable type_
        raise ValueError(
            f"invalid type for ini option {name!r}: {type_!r} (expected one of "
            f"{', '.join(repr(tag) for tag in get_args(_IniTypeTag))}, one of "
            "the types str, bool, int, float, a union of these types such as "
            "`int | str`, or a `Literal` of strings)"
        ) from None


def _ini_type_to_member(name: str, type_: object) -> _IniTypeTag | _IniLiteral:
    """Normalize one member of an `addini(type=...)` argument."""
    if get_origin(type_) is Literal:
        choices = get_args(type_)
        if not all(isinstance(choice, str) for choice in choices):
            raise ValueError(
                f"invalid type for ini option {name!r}: Literal choices "
                f"must be strings, got {choices!r}"
            )
        return _IniLiteral(choices)
    return _ini_type_to_tag(name, type_)


def _ini_type_repr(type: IniType) -> str:
    """Render an ini option type for --help output and error messages."""
    if isinstance(type, _IniLiteral):
        return " | ".join(repr(choice) for choice in type.choices)
    if isinstance(type, tuple):
        return " | ".join(_ini_type_repr(member) for member in type)
    return type


def _get_argparse_dest(opts: Sequence[str]) -> str:
    long_opts = [opt for opt in opts if opt.startswith("--")]

View on GitHub (pinned to 98b357f69e)

Solutions

  1. Restrict Literal choices to strings only: Literal['1', '2', '3'].
  2. Use type=int | str with manual validation if you need numeric acceptance.
  3. Keep choices as strings and convert to the desired type when reading via config.getini().

Example fix

# before
parser.addini('level', type=Literal[1, 2, 3], help='verbosity')

# after
parser.addini('level', type=Literal['1', '2', '3'], help='verbosity')
Defensive patterns

Strategy: type-guard

Validate before calling

from typing import get_origin, get_args, Literal
def validate_literal_choices(type_: object) -> None:
    if get_origin(type_) is Literal:
        choices = get_args(type_)
        bad = [c for c in choices if not isinstance(c, str)]
        if bad:
            raise TypeError(f'Literal choices must be strings, got {bad!r}')

Type guard

from typing import get_origin, get_args, Literal
def is_string_literal(type_: object) -> bool:
    return get_origin(type_) is Literal and all(isinstance(c, str) for c in get_args(type_))

Prevention

When it happens

Trigger: Calling parser.addini('mode', type=Literal['fast', 'slow']) is fine, but parser.addini('mode', type=Literal['fast', 2]) or type=Literal[1, 2, 3] raises this because not every choice isinstance(str).

Common situations: Plugin authors wanting an int/bool enum via Literal; mixing string and numeric literal values expecting automatic coercion.

Related errors


AI-assisted analysis of pytest-dev/pytest@98b357f69e (2026-08-04). Data as JSON: /data/errors/04a60174c1336e08.json. Report an issue: GitHub.