pytest-dev/pytest · error · TypeError

got {mark_obj!r} instead of Mark

Error message

got {mark_obj!r} instead of Mark

What it means

`normalize_mark_list` iterates items passed as marks and unwraps `MarkDecorator` via its `.mark` attribute; anything that is not ultimately a `Mark` instance raises TypeError. This guards internal mark normalization used by parametrize and `pytestmark`.

Source

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

        else:
            mark_list = [mark_attribute]
    return list(normalize_mark_list(mark_list))


def normalize_mark_list(
    mark_list: Iterable[Mark | MarkDecorator],
) -> Iterable[Mark]:
    """
    Normalize an iterable of Mark or MarkDecorator objects into a list of marks
    by retrieving the `mark` attribute on MarkDecorator instances.

    :param mark_list: marks to normalize
    :returns: A new list of the extracted Mark objects
    """
    for mark in mark_list:
        mark_obj = getattr(mark, "mark", mark)
        if not isinstance(mark_obj, Mark):
            raise TypeError(f"got {mark_obj!r} instead of Mark")
        yield mark_obj


def store_mark(obj, mark: Mark) -> None:
    """Store a Mark on an object.

    This is used to implement the Mark declarations/decorators correctly.
    """
    assert isinstance(mark, Mark), mark

    from ..fixtures import getfixturemarker

    if getfixturemarker(obj) is not None:
        fail(
            "Marks cannot be applied to fixtures.\n"
            "See docs: https://docs.pytest.org/en/stable/deprecations.html#applying-a-mark-to-a-fixture-function"
        )

View on GitHub (pinned to 98b357f69e)

Solutions

  1. Ensure each item in `marks=[...]` is a `Mark` or `MarkDecorator` (e.g. `pytest.mark.skip`, `pytest.mark.xfail(reason=...)`).
  2. Drop custom decorators that are not pytest marks from the marks list.
  3. If you constructed marks manually, use `pytest.Mark(name, args, kwargs)`.

Example fix

# before
@pytest.mark.parametrize('x', [pytest.param(1, marks=[my_custom_decorator)])
# after
@pytest.mark.parametrize('x', [pytest.param(1, marks=[pytest.mark.skip])])
Defensive patterns

Strategy: type-guard

Validate before calling

from _pytest.mark.structures import Mark, MarkDecorator
def all_real_marks(marks) -> bool:
    return all(isinstance(m, (Mark, MarkDecorator)) for m in marks)

Type guard

from _pytest.mark.structures import Mark, MarkDecorator
def is_mark_like(m: object) -> bool:
    return isinstance(m, (Mark, MarkDecorator))

Prevention

When it happens

Trigger: Passing a plain string, int, function, or a decorator that is not a `MarkDecorator` inside `marks=[...]` of `pytest.param`, or assigning non-mark objects to `pytestmark`.

Common situations: Mixing custom decorators with pytest marks; passing `pytest.mark.foo` incorrectly (e.g. `pytest.mark` itself, or an already-called decorator returning a non-mark); stale third-party helpers that return raw callables.

Related errors


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