{"record":{"id":"532deee1e038b43d","repo":"python/cpython","slug":"cannot-stringify-annotation-containing-string-form","errorCode":null,"errorMessage":"Cannot stringify annotation containing string formatting","messagePattern":"Cannot stringify annotation containing string formatting","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"Lib/annotationlib.py","lineNumber":497,"sourceCode":"            ast_args.append(new_arg)\n        ast_kwargs = []\n        for key, value in kwargs.items():\n            new_value, new_extra_names = self.__convert_to_ast(value)\n            if new_extra_names is not None:\n                extra_names.update(new_extra_names)\n            ast_kwargs.append(ast.keyword(key, new_value))\n        return self.__make_new(ast.Call(self.__get_ast(), ast_args, ast_kwargs), extra_names)\n\n    def __iter__(self):\n        yield self.__make_new(ast.Starred(self.__get_ast()))\n\n    def __repr__(self):\n        if isinstance(self.__ast_node__, str):\n            return self.__ast_node__\n        return ast.unparse(self.__ast_node__)\n\n    def __format__(self, format_spec):\n        raise TypeError(\"Cannot stringify annotation containing string formatting\")\n\n    def _make_binop(op: ast.AST):\n        def binop(self, other):\n            rhs, extra_names = self.__convert_to_ast(other)\n            return self.__make_new(\n                ast.BinOp(self.__get_ast(), op, rhs), extra_names\n            )\n\n        return binop\n\n    __add__ = _make_binop(ast.Add())\n    __sub__ = _make_binop(ast.Sub())\n    __mul__ = _make_binop(ast.Mult())\n    __matmul__ = _make_binop(ast.MatMult())\n    __truediv__ = _make_binop(ast.Div())\n    __mod__ = _make_binop(ast.Mod())\n    __lshift__ = _make_binop(ast.LShift())\n    __rshift__ = _make_binop(ast.RShift())","sourceCodeStart":479,"sourceCodeEnd":515,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/annotationlib.py#L479-L515","documentation":"When annotations are retrieved in Format.STRING, every name is replaced by a _Stringifier object that records operations as AST. _Stringifier deliberately raises TypeError from __format__ because an f-string (or .format()/format()) inside an annotation cannot be faithfully represented as source text. Hitting it means an annotation uses string formatting, which annotationlib refuses to stringify.","triggerScenarios":"A module using `from __future__ import annotations` with an f-string inside an annotation, e.g. `x: f\"list[{T}]\"`; calling annotationlib.get_annotations(obj, format=Format.STRING) or annotations_to_string on such an object; inspect.get_annotations on a class whose annotate function formats strings.","commonSituations":"Trying to parametrize annotations dynamically with f-strings instead of Subscription; tooling (docs generators, serializers) that stringifies annotations of third-party code that uses f-strings; migration to PEP 649 lazy annotations where STRING format is now used.","solutions":["Remove string formatting from the annotation: use real subscripts like list[T] instead of f\"list[{T}]\".","If the value must be computed, compute it before the annotation and reference the result, or keep it a plain string literal.","Consume annotations in Format.VALUE or FORWARDREF instead of STRING if you cannot change the annotated code."],"exampleFix":"# before\nT = int\nx: f\"list[{T.__name__}]\" = []\nannotationlib.get_annotations(mod, format=Format.STRING)  # TypeError\n\n# after\nx: \"list[int]\" = []","handlingStrategy":"validation","validationCode":"import inspect, ast\n\ndef annotation_uses_formatting(source: str) -> bool:\n    try:\n        tree = ast.parse(source or '', mode='eval')\n    except SyntaxError:\n        return False\n    return any(isinstance(n, (ast.JoinedStr, ast.FormattedValue)) for n in ast.walk(tree))","typeGuard":"from annotationlib import Format\n\ndef safe_string_annotations(obj) -> dict | None:\n    try:\n        return annotationlib.get_annotations(obj, format=Format.STRING)\n    except TypeError:\n        return None","tryCatchPattern":"try:\n    ann = annotationlib.get_annotations(obj, format=Format.STRING)\nexcept TypeError as e:\n    if 'string formatting' in str(e):\n        ann = annotationlib.get_annotations(obj, format=Format.FORWARDREF)\n    else:\n        raise","preventionTips":["Never use f-strings or .format() inside annotations","Prefer Format.VALUE or FORWARDREF when consuming third-party annotations","Lint for JoinedStr nodes inside annotation positions in CI"],"tags":["python","annotations","pep649","stringification","f-string"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}