{"record":{"id":"4bf43087638a136e","repo":"pytest-dev/pytest","slug":"pytest-markeval-namespace-needs-to-return-a-dict","errorCode":null,"errorMessage":"pytest_markeval_namespace() needs to return a dict, got {dictionary!r}","messagePattern":"pytest_markeval_namespace\\(\\) needs to return a dict, got (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/_pytest/skipping.py","lineNumber":110,"sourceCode":"    If an old-style string condition is given, it is eval()'d, otherwise the\n    condition is bool()'d. If this fails, an appropriately formatted pytest.fail\n    is raised.\n\n    Returns (result, reason). The reason is only relevant if the result is True.\n    \"\"\"\n    # String condition.\n    if isinstance(condition, str):\n        globals_ = {\n            \"os\": os,\n            \"sys\": sys,\n            \"platform\": platform,\n            \"config\": item.config,\n        }\n        for dictionary in reversed(\n            item.ihook.pytest_markeval_namespace(config=item.config)\n        ):\n            if not isinstance(dictionary, Mapping):\n                raise ValueError(\n                    f\"pytest_markeval_namespace() needs to return a dict, got {dictionary!r}\"\n                )\n            globals_.update(dictionary)\n        if hasattr(item, \"obj\"):\n            globals_.update(item.obj.__globals__)\n        try:\n            filename = f\"<{mark.name} condition>\"\n            condition_code = compile(condition, filename, \"eval\")\n            result = eval(condition_code, globals_)\n        except SyntaxError as exc:\n            msglines = [\n                f\"Error evaluating {mark.name!r} condition\",\n                \"    \" + condition,\n                \"    \" + \" \" * (exc.offset or 0) + \"^\",\n                \"SyntaxError: invalid syntax\",\n            ]\n            fail(\"\\n\".join(msglines), pytrace=False)\n        except Exception as exc:","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/pytest-dev/pytest/blob/0d6fbdeffa57c796123f62f81f7dd370d9b7ecdc/src/_pytest/skipping.py#L92-L128","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Make the hookimpl return a dict: `return {\"myvar\": value}` — never None.","If you have nothing to add, return an empty dict `{}` rather than None.","Type-check at the boundary in your hook: `result = build_namespace(); return result if isinstance(result, Mapping) else {}`."],"exampleFix":"# before\n@pytest.hookimpl\ndef pytest_markeval_namespace(config):\n    return MyNamespace()  # not a Mapping\n\n# after\n@pytest.hookimpl\ndef pytest_markeval_namespace(config):\n    return {\"myvar\": 42}","handlingStrategy":"type-guard","validationCode":"from collections.abc import Mapping\n\n@pytest.hookimpl\ndef pytest_markeval_namespace(config):\n    ns = _build_namespace(config)\n    # Validate at the source so pytest never sees a non-Mapping.\n    assert isinstance(ns, Mapping), f\"namespace must be a Mapping, got {type(ns)!r}\"\n    return ns","typeGuard":"from collections.abc import Mapping\n\ndef is_markeval_namespace(value) -> bool:\n    \"\"\"True when value is acceptable as a pytest_markeval_namespace return.\"\"\"\n    return isinstance(value, Mapping)","tryCatchPattern":"# In a wrapper plugin that aggregates other plugins' namespaces:\ntry:\n    ns = hook.pytest_markeval_namespace(config=config)\nexcept Exception:\n    ns = {}\nif not isinstance(ns, Mapping):\n    ns = {}","preventionTips":["Always return a dict from pytest_markeval_namespace — never None and never a custom class.","Return {} when you have nothing to contribute; the hook contract is Mapping, not Optional[Mapping].","Add a unit test that asserts isinstance(result, Mapping) for your hookimpl."],"tags":["markers","hooks","plugins","markeval","skipif","xfail"],"backgroundTag":null,"analyzedSha":"0d6fbdeffa57c796123f62f81f7dd370d9b7ecdc","analyzedAt":"2026-08-11T20:52:36.969Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}