Repository: pytest-dev/pytest
File: src/_pytest/mark/structures.py (Python)

Code before:

class ParameterSet(NamedTuple):
    values: Sequence[object | NotSetType]
    marks: Collection[MarkDecorator | Mark]
    id: str | _HiddenParam | None

    @classmethod
    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(f"Expected id to be a string, 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:
            Enforce tuple wrapping so single argument tuple values
            don't get decomposed and break tests.
        """
        if isinstance(parameterset, cls):
            return parameterset
        if force_tuple:
            return cls.param(parameterset)
        else:
            # TODO: Refactor to fix this type-ignore. Currently the following
            # passes type-checking but crashes:

Review comment (suggestion):
The check has changed, and the message should reflect the new reality:
```suggestion
                raise TypeError(
                    "Expected id to be a string or a `pytest.HIDDEN_PARAM` sentinel, "
                    f"got {type(id)}: {id!r}",
                )
```

Code after:

class ParameterSet(NamedTuple):
    values: Sequence[object | NotSetType]
    marks: Collection[MarkDecorator | Mark]
    id: str | _HiddenParam | None

    @classmethod
    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:
            Enforce tuple wrapping so single argument tuple values
            don't get decomposed and break tests.
        """
        if isinstance(parameterset, cls):
            return parameterset
        if force_tuple:
            return cls.param(parameterset)