{"id":"29a707b4bf4fb156","repo":"pytest-dev/pytest","slug":"expected-id-to-be-a-string-or-a-pytest-hidden-par","errorCode":null,"errorMessage":"Expected id to be a string or a `pytest.HIDDEN_PARAM` sentinel, got {type(id)}: {id!r}","messagePattern":"Expected id to be a string or a `pytest\\.HIDDEN_PARAM` sentinel, got (.+?): (.+?)","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"src/_pytest/mark/structures.py","lineNumber":134,"sourceCode":"    def param(\n        cls,\n        *values: object,\n        marks: MarkDecorator | Collection[MarkDecorator | Mark] = (),\n        id: str | _HiddenParam | None = None,\n    ) -> ParameterSet:\n        if isinstance(marks, MarkDecorator):\n            marks = (marks,)\n        else:\n            assert isinstance(marks, collections.abc.Collection)\n        if any(i.name == \"usefixtures\" for i in marks):\n            raise ValueError(\n                \"pytest.param cannot add pytest.mark.usefixtures; see \"\n                \"https://docs.pytest.org/en/stable/reference/reference.html#pytest-param\"\n            )\n\n        if id is not None:\n            if not isinstance(id, str) and id is not HIDDEN_PARAM:\n                raise TypeError(\n                    \"Expected id to be a string or a `pytest.HIDDEN_PARAM` sentinel, \"\n                    f\"got {type(id)}: {id!r}\",\n                )\n        return cls(values, marks, id)\n\n    @classmethod\n    def extract_from(\n        cls,\n        parameterset: ParameterSet | Sequence[object] | object,\n        force_tuple: bool = False,\n    ) -> ParameterSet:\n        \"\"\"Extract from an object or objects.\n\n        :param parameterset:\n            A legacy style parameterset that may or may not be a tuple,\n            and may or may not be wrapped into a mess of mark objects.\n\n        :param force_tuple:","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/pytest-dev/pytest/blob/98b357f69e380da908740a212288d73b2ee06687/src/_pytest/mark/structures.py#L116-L152","documentation":"`ParameterSet.param(..., id=...)` validates that `id` is either a `str`, `None`, or the internal `pytest.HIDDEN_PARAM` sentinel. Anything else (int, bool, float, list) raises TypeError. The id becomes the test node id suffix and must be a string.","triggerScenarios":"`pytest.param(1, id=5)`, `pytest.param(1, id=True)`, or passing an enum/object as id.","commonSituations":"Using the loop counter or a database id (int) as the param id; forgetting to stringify.","solutions":["Convert the id to `str`: `pytest.param(v, id=str(my_id))`.","Pass `id=None` to let pytest auto-generate the id from the value.","Use the `ids=` callable form of parametrize to map values to string ids centrally."],"exampleFix":"# before\npytest.param(user.id, id=user.id)  # user.id is int\n# after\npytest.param(user.id, id=str(user.id))","handlingStrategy":"type-guard","validationCode":"def coerce_id(id):\n    return id if isinstance(id, str) or id is None else str(id)","typeGuard":"def is_valid_param_id(id: object) -> bool:\n    return id is None or isinstance(id, str)","tryCatchPattern":null,"preventionTips":["Always stringify non-string ids.","Prefer the `ids=` callable to centralize id generation."],"tags":["pytest","parametrize","type-error","id"],"analyzedSha":"98b357f69e380da908740a212288d73b2ee06687","analyzedAt":"2026-08-04T20:26:34.442Z","schemaVersion":2}