{"id":"035517879921843a","repo":"pytest-dev/pytest","slug":"ids-must-be-a-callable-or-an-iterable","errorCode":null,"errorMessage":"ids must be a callable or an iterable","messagePattern":"ids must be a callable or an iterable","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"src/_pytest/python.py","lineNumber":1553,"sourceCode":"            idfn,\n            ids_,\n            self.config,\n            nodeid=nodeid,\n        )\n        return id_maker.make_unique_parameterset_ids()\n\n    def _validate_ids(\n        self,\n        ids: Iterable[object | None],\n        parametersets: Sequence[ParameterSet],\n    ) -> list[object | None]:\n        try:\n            num_ids = len(ids)  # type: ignore[arg-type]\n        except TypeError:\n            try:\n                iter(ids)\n            except TypeError as e:\n                raise TypeError(\"ids must be a callable or an iterable\") from e\n            num_ids = len(parametersets)\n\n        # num_ids == 0 is a special case: https://github.com/pytest-dev/pytest/issues/1849\n        if num_ids != len(parametersets) and num_ids != 0:\n            nodeid = self.definition.nodeid\n            fail(\n                f\"In {nodeid}: {len(parametersets)} parameter sets specified, with different number of ids: {num_ids}\",\n                pytrace=False,\n            )\n\n        return list(itertools.islice(ids, num_ids))\n\n    def _validate_if_using_arg_names(\n        self,\n        argnames: Sequence[str],\n        indirect: bool | Sequence[str],\n    ) -> None:\n        \"\"\"Check if all argnames are being used, by default values, or directly/indirectly.","sourceCodeStart":1535,"sourceCodeEnd":1571,"githubUrl":"https://github.com/pytest-dev/pytest/blob/98b357f69e380da908740a212288d73b2ee06687/src/_pytest/python.py#L1535-L1571","documentation":"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.","triggerScenarios":"Passing ids=42, ids=None-in-a-non-iterable-wrapper, ids=some_object to parametrize where the object is neither callable nor iterable.","commonSituations":"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.","solutions":["Pass ids as a list/tuple of strings (one per parameter set) or a callable.","Ensure the variable holding ids is actually a list at runtime (guard with isinstance).","Use pytest.param(value, id='name') to attach IDs inline instead of a separate ids list."],"exampleFix":"// before\n@pytest.mark.parametrize(\"x\", [1,2,3], ids=3)\ndef test_it(x): ...\n// after\n@pytest.mark.parametrize(\"x\", [1,2,3], ids=[\"one\",\"two\",\"three\"])\ndef test_it(x): ...","handlingStrategy":"type-guard","validationCode":"from collections.abc import Iterable, Callable\n\ndef valid_ids(ids):\n    return ids is None or callable(ids) or isinstance(ids, Iterable)\n\nif not valid_ids(ids):\n    raise TypeError(\"ids must be callable or iterable\")","typeGuard":"from collections.abc import Iterable, Callable\nfrom typing import Any\n\ndef is_valid_ids(v: Any) -> bool:\n    return v is None or callable(v) or isinstance(v, Iterable)","tryCatchPattern":null,"preventionTips":["Pass a list of strings or a callable for ids.","Validate ids is a list at runtime if built dynamically.","Use pytest.param(value, id=...) for inline IDs."],"tags":["parametrize","ids","type-validation"],"analyzedSha":"98b357f69e380da908740a212288d73b2ee06687","analyzedAt":"2026-08-04T20:26:34.442Z","schemaVersion":2}