{"record":{"id":"776c874b06638238","repo":"python/cpython","slug":"obj-r-annotations-is-neither-a-dict-nor-none","errorCode":null,"errorMessage":"{obj!r}.__annotations__ is neither a dict nor None","messagePattern":"(.+?)\\.__annotations__ is neither a dict nor None","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Lib/annotationlib.py","lineNumber":1162,"sourceCode":"\n    Does not return a fresh dictionary.\n    \"\"\"\n    # This special case is needed to support types defined under\n    # from __future__ import annotations, where accessing the __annotations__\n    # attribute directly might return annotations for the wrong class.\n    if isinstance(obj, type):\n        try:\n            ann = _BASE_GET_ANNOTATIONS(obj)\n        except AttributeError:\n            # For static types, the descriptor raises AttributeError.\n            return None\n    else:\n        ann = getattr(obj, \"__annotations__\", None)\n        if ann is None:\n            return None\n\n    if not isinstance(ann, dict):\n        raise ValueError(f\"{obj!r}.__annotations__ is neither a dict nor None\")\n    return ann\n\n\nclass _ExtraNameFixer(ast.NodeTransformer):\n    \"\"\"Fixer for __extra_names__ items in ForwardRef __repr__ and string evaluation\"\"\"\n    def __init__(self, extra_names):\n        self.extra_names = extra_names\n\n    def visit_Name(self, node: ast.Name):\n        if (new_name := self.extra_names.get(node.id, _sentinel)) is not _sentinel:\n            node = ast.Name(id=type_repr(new_name))\n        return node\n","sourceCodeStart":1144,"sourceCodeEnd":1175,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/annotationlib.py#L1144-L1175","documentation":"_get_dunder_annotations() validates that obj.__annotations__, when present, is either a dict or None; any other type raises ValueError. This catches objects (or classes) whose __annotations__ was overwritten with, say, a list, a string, or a property, which would otherwise silently corrupt downstream consumers.","triggerScenarios":"instance.__annotations__ = 'x: int'; a class body assigning __annotations__ = [] ; a mock or dataclass-like tool setting __annotations__ to a non-dict; a property named __annotations__ returning a tuple.","commonSituations":"Dynamic annotation injection by ORMs/serializers that writes the wrong type; patching in tests; pickling/copying artifacts that restore __annotations__ as another structure; typos assigning a tuple of keys.","solutions":["Set __annotations__ to a plain dict: obj.__annotations__ = {'x': int}.","Delete the bogus attribute (del obj.__annotations__) so the lookup returns None and get_annotations falls back to __annotate__ or {}. ","Validate with isinstance(obj.__annotations__, (dict, type(None))) before calling annotation APIs."],"exampleFix":"# before\nclass C: pass\nC.__annotations__ = ['x', 'y']  # later: ValueError: ...__annotations__ is neither a dict nor None\n\n# after\nC.__annotations__ = {'x': int, 'y': str}","handlingStrategy":"validation","validationCode":"def annotations_ok(obj) -> bool:\n    ann = getattr(obj, '__annotations__', None)\n    return ann is None or isinstance(ann, dict)","typeGuard":"def has_dict_annotations(obj) -> bool:\n    return isinstance(getattr(obj, '__annotations__', None), dict)","tryCatchPattern":"try:\n    ann = get_annotations(obj)\nexcept ValueError as e:\n    if 'neither a dict nor None' in str(e):\n        object.__setattr__(obj, '__annotations__', {})  # repair then retry\n        ann = get_annotations(obj)\n    else:\n        raise","preventionTips":["Only ever assign dicts (or None) to __annotations__","Validate dynamically injected __annotations__ with isinstance checks","After unpickling/serialization, re-normalize __annotations__ before introspection"],"tags":["python","annotations","typeerror","validation","introspection"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}