pytest-dev/pytest · error · ValueError

pytest_markeval_namespace() needs to return a dict, got

Error message

pytest_markeval_namespace() needs to return a dict, got {dictionary!r}

What it means

When a skipif/xfail marker carries a string condition, pytest eval()s it in a globals namespace partly built from the pytest_markeval_namespace hook. Every plugin implementing that hook must return a Mapping (typically a dict) of extra names. The loop at skipping.py:106 reverses the hookresults and type-checks each entry; a non-Mapping return (None, a list, a custom object) aborts evaluation with this ValueError before the condition is ever compiled.

Solutions

  1. Make the hookimpl return a dict: `return {"myvar": value}` — never None.
  2. If you have nothing to add, return an empty dict `{}` rather than None.
  3. Type-check at the boundary in your hook: `result = build_namespace(); return result if isinstance(result, Mapping) else {}`.

Example fix

# before
@pytest.hookimpl
def pytest_markeval_namespace(config):
    return MyNamespace()  # not a Mapping

# after
@pytest.hookimpl
def pytest_markeval_namespace(config):
    return {"myvar": 42}
Defensive patterns

Strategy: type-guard

Validate before calling

from collections.abc import Mapping

@pytest.hookimpl
def pytest_markeval_namespace(config):
    ns = _build_namespace(config)
    # Validate at the source so pytest never sees a non-Mapping.
    assert isinstance(ns, Mapping), f"namespace must be a Mapping, got {type(ns)!r}"
    return ns

Type guard

from collections.abc import Mapping

def is_markeval_namespace(value) -> bool:
    """True when value is acceptable as a pytest_markeval_namespace return."""
    return isinstance(value, Mapping)

Try / catch

# In a wrapper plugin that aggregates other plugins' namespaces:
try:
    ns = hook.pytest_markeval_namespace(config=config)
except Exception:
    ns = {}
if not isinstance(ns, Mapping):
    ns = {}

Prevention

When it happens

Trigger: A conftest.py or plugin registers @pytest.hookimpl(hookwrapper=False) for pytest_markeval_namespace and returns None (implicit), a list, a tuple, or a non-Mapping namespace object. The error fires the first time any test with a string skipif/xfail condition is collected and evaluated.

Common situations: Plugin author returns a custom Namespace class instead of a dict; a hookimpl that conditionally returns None; upgrading a plugin from an older pytest where the return contract was looser; copy-pasting a hookimpl that returns a list of items instead of a name->value mapping.

Related errors


AI-assisted analysis of pytest-dev/pytest@0d6fbdeffa (2026-08-11). Data as JSON: /api/errors/4bf43087638a136e. Report an issue: GitHub.

Appendix: source

Thrown at src/_pytest/skipping.py:110

    If an old-style string condition is given, it is eval()'d, otherwise the
    condition is bool()'d. If this fails, an appropriately formatted pytest.fail
    is raised.

    Returns (result, reason). The reason is only relevant if the result is True.
    """
    # String condition.
    if isinstance(condition, str):
        globals_ = {
            "os": os,
            "sys": sys,
            "platform": platform,
            "config": item.config,
        }
        for dictionary in reversed(
            item.ihook.pytest_markeval_namespace(config=item.config)
        ):
            if not isinstance(dictionary, Mapping):
                raise ValueError(
                    f"pytest_markeval_namespace() needs to return a dict, got {dictionary!r}"
                )
            globals_.update(dictionary)
        if hasattr(item, "obj"):
            globals_.update(item.obj.__globals__)
        try:
            filename = f"<{mark.name} condition>"
            condition_code = compile(condition, filename, "eval")
            result = eval(condition_code, globals_)
        except SyntaxError as exc:
            msglines = [
                f"Error evaluating {mark.name!r} condition",
                "    " + condition,
                "    " + " " * (exc.offset or 0) + "^",
                "SyntaxError: invalid syntax",
            ]
            fail("\n".join(msglines), pytrace=False)
        except Exception as exc:

View on GitHub (pinned to 0d6fbdeffa)