{"record":{"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/0d6fbdeffa57c796123f62f81f7dd370d9b7ecdc/src/_pytest/python.py#L1535-L1571","documentation":"Thrown by Metafunc._validate_ids() when the `ids` argument is neither callable nor iterable. After ruling out callable (handled earlier) and failing iter(), pytest rejects the value outright because it cannot be consumed as a source of ids.","triggerScenarios":"Passing ids=42 (an int), ids=True, or ids=SomeObject to @pytest.mark.parametrize; passing a non-iterable, non-callable value by mistake.","commonSituations":"Refactoring where ids used to be a list and became a single value; boolean flags accidentally passed as ids; copy-paste errors.","solutions":["Pass ids as a list/tuple of strings or a callable.","If you meant a single id for all params, use pytest.param(..., id=...) per parameter set instead.","Remove the ids argument to let pytest auto-generate ids."],"exampleFix":"// before\n@pytest.mark.parametrize(\"x\", [1,2,3], ids=42)\n// after\n@pytest.mark.parametrize(\"x\", [1,2,3], ids=[\"one\",\"two\",\"three\"])","handlingStrategy":"type-guard","validationCode":"if not callable(ids) and not isinstance(ids, (list, tuple)):\n    try:\n        iter(ids)\n    except TypeError:\n        raise TypeError('ids must be callable or iterable')\n@pytest.mark.parametrize('x', values, ids=ids)","typeGuard":"def is_valid_ids(ids: object) -> bool:\n    return callable(ids) or isinstance(ids, (list, tuple)) or hasattr(ids, '__iter__')","tryCatchPattern":null,"preventionTips":["Pass ids as a list/tuple of strings or a callable.","Use pytest.param(..., id=...) for per-set ids.","Omit ids to use auto-generated ones when unsure."],"tags":["parametrize","ids","type-error","validation"],"backgroundTag":null,"analyzedSha":"0d6fbdeffa57c796123f62f81f7dd370d9b7ecdc","analyzedAt":"2026-08-11T20:52:36.969Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}