pytest-dev/pytest · error · TypeError

Expected id to be a string or a `pytest.HIDDEN_PARAM` sentin

Error message

Expected id to be a string or a `pytest.HIDDEN_PARAM` sentinel, got {type(id)}: {id!r}

What it means

`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.

Source

Thrown at src/_pytest/mark/structures.py:134

    def param(
        cls,
        *values: object,
        marks: MarkDecorator | Collection[MarkDecorator | Mark] = (),
        id: str | _HiddenParam | None = None,
    ) -> ParameterSet:
        if isinstance(marks, MarkDecorator):
            marks = (marks,)
        else:
            assert isinstance(marks, collections.abc.Collection)
        if any(i.name == "usefixtures" for i in marks):
            raise ValueError(
                "pytest.param cannot add pytest.mark.usefixtures; see "
                "https://docs.pytest.org/en/stable/reference/reference.html#pytest-param"
            )

        if id is not None:
            if not isinstance(id, str) and id is not HIDDEN_PARAM:
                raise TypeError(
                    "Expected id to be a string or a `pytest.HIDDEN_PARAM` sentinel, "
                    f"got {type(id)}: {id!r}",
                )
        return cls(values, marks, id)

    @classmethod
    def extract_from(
        cls,
        parameterset: ParameterSet | Sequence[object] | object,
        force_tuple: bool = False,
    ) -> ParameterSet:
        """Extract from an object or objects.

        :param parameterset:
            A legacy style parameterset that may or may not be a tuple,
            and may or may not be wrapped into a mess of mark objects.

        :param force_tuple:

View on GitHub (pinned to 98b357f69e)

Solutions

  1. Convert the id to `str`: `pytest.param(v, id=str(my_id))`.
  2. Pass `id=None` to let pytest auto-generate the id from the value.
  3. Use the `ids=` callable form of parametrize to map values to string ids centrally.

Example fix

# before
pytest.param(user.id, id=user.id)  # user.id is int
# after
pytest.param(user.id, id=str(user.id))
Defensive patterns

Strategy: type-guard

Validate before calling

def coerce_id(id):
    return id if isinstance(id, str) or id is None else str(id)

Type guard

def is_valid_param_id(id: object) -> bool:
    return id is None or isinstance(id, str)

Prevention

When it happens

Trigger: `pytest.param(1, id=5)`, `pytest.param(1, id=True)`, or passing an enum/object as id.

Common situations: Using the loop counter or a database id (int) as the param id; forgetting to stringify.

Related errors


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