pytest-dev/pytest · error · TypeError

ids must be a callable or an iterable

Error message

ids must be a callable or an iterable

What it means

Raised by Metafunc._validate_ids when the ids argument passed to @pytest.mark.parametrize is neither callable nor iterable. pytest first tries callable(ids); if not, it tries iter(ids); if that also fails it raises TypeError. ids must be a callable (id function), or an iterable of strings/None matching the number of parameter sets.

Source

Thrown at src/_pytest/python.py:1553

            idfn,
            ids_,
            self.config,
            nodeid=nodeid,
        )
        return id_maker.make_unique_parameterset_ids()

    def _validate_ids(
        self,
        ids: Iterable[object | None],
        parametersets: Sequence[ParameterSet],
    ) -> list[object | None]:
        try:
            num_ids = len(ids)  # type: ignore[arg-type]
        except TypeError:
            try:
                iter(ids)
            except TypeError as e:
                raise TypeError("ids must be a callable or an iterable") from e
            num_ids = len(parametersets)

        # num_ids == 0 is a special case: https://github.com/pytest-dev/pytest/issues/1849
        if num_ids != len(parametersets) and num_ids != 0:
            nodeid = self.definition.nodeid
            fail(
                f"In {nodeid}: {len(parametersets)} parameter sets specified, with different number of ids: {num_ids}",
                pytrace=False,
            )

        return list(itertools.islice(ids, num_ids))

    def _validate_if_using_arg_names(
        self,
        argnames: Sequence[str],
        indirect: bool | Sequence[str],
    ) -> None:
        """Check if all argnames are being used, by default values, or directly/indirectly.

View on GitHub (pinned to 98b357f69e)

Solutions

  1. Pass ids as a list/tuple of strings (one per parameter set) or a callable.
  2. Ensure the variable holding ids is actually a list at runtime (guard with isinstance).
  3. Use pytest.param(value, id='name') to attach IDs inline instead of a separate ids list.

Example fix

// before
@pytest.mark.parametrize("x", [1,2,3], ids=3)
def test_it(x): ...
// after
@pytest.mark.parametrize("x", [1,2,3], ids=["one","two","three"])
def test_it(x): ...
Defensive patterns

Strategy: type-guard

Validate before calling

from collections.abc import Iterable, Callable

def valid_ids(ids):
    return ids is None or callable(ids) or isinstance(ids, Iterable)

if not valid_ids(ids):
    raise TypeError("ids must be callable or iterable")

Type guard

from collections.abc import Iterable, Callable
from typing import Any

def is_valid_ids(v: Any) -> bool:
    return v is None or callable(v) or isinstance(v, Iterable)

Prevention

When it happens

Trigger: Passing ids=42, ids=None-in-a-non-iterable-wrapper, ids=some_object to parametrize where the object is neither callable nor iterable.

Common situations: Passing an integer or float as ids by mistake; passing a single string when a list was intended (a string IS iterable, so this case usually passes through but yields per-char ids); a variable that was expected to be a list but resolved to a scalar.

Related errors


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