{"record":{"id":"9802107c8755b001","repo":"python/cpython","slug":"unknown-thread-safety-level-level-r-for-name-r","errorCode":null,"errorMessage":"Unknown thread safety level {level!r} for {name!r}. Valid levels: {sorted(_VALID_THREADSAFETY_LEVELS)}","messagePattern":"Unknown thread safety level (.+?) for (.+?)\\. Valid levels: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Doc/tools/extensions/c_annotations.py","lineNumber":150,"sourceCode":"    \"atomic\",\n})\n\n\ndef read_threadsafety_data(\n    threadsafety_filename: Path,\n) -> dict[str, ThreadSafetyEntry]:\n    threadsafety_data = {}\n    for line in threadsafety_filename.read_text(encoding=\"utf8\").splitlines():\n        line = line.strip()\n        if not line or line.startswith(\"#\"):\n            continue\n        # Each line is of the form: function_name : level : [comment]\n        parts = line.split(\":\", 2)\n        if len(parts) < 2:\n            raise ValueError(f\"Wrong field count in {line!r}\")\n        name, level = parts[0].strip(), parts[1].strip()\n        if level not in _VALID_THREADSAFETY_LEVELS:\n            raise ValueError(\n                f\"Unknown thread safety level {level!r} for {name!r}. \"\n                f\"Valid levels: {sorted(_VALID_THREADSAFETY_LEVELS)}\"\n            )\n        threadsafety_data[name] = ThreadSafetyEntry(name=name, level=level)\n    return threadsafety_data\n\n\ndef add_annotations(app: Sphinx, doctree: nodes.document) -> None:\n    state = app.env.domaindata[\"c_annotations\"]\n    refcount_data = state[\"refcount_data\"]\n    stable_abi_data = state[\"stable_abi_data\"]\n    threadsafety_data = state[\"threadsafety_data\"]\n    for node in doctree.findall(addnodes.desc_content):\n        par = node.parent\n        if par[\"domain\"] != \"c\":\n            continue\n        if not par[0].get(\"ids\", None):\n            continue","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Doc/tools/extensions/c_annotations.py#L132-L168","documentation":"Runner.run() accepts exactly a coroutine; other awaitables (Futures, objects with __await__) are tolerated only by auto-wrapping, and everything else raises this TypeError. It is the same contract as loop.create_task()/asyncio.run(): you must pass something awaitable, not a plain function, coroutine function, or result value.","triggerScenarios":"Passing a coroutine function without calling it: runner.run(main) instead of runner.run(main()); passing a lambda/def result (e.g. run(get_value())) where get_value is sync; passing a Task or Future created elsewhere (older semantics) or a plain object like a string/None.","commonSituations":"Missing parentheses on the main coroutine — the single most common hit; passing an already-awaited coroutine's cached result; helpers that accept 'main or default_main' where the default is a function reference not an invocation; passing functools.partial of an async def without calling it (partial is not awaitable unless it yields a coroutine).","solutions":["Call the coroutine function: runner.run(main())","If building the awaitable dynamically, ensure the expression evaluates to a coroutine: runner.run(factory()) where factory is async def","For plain results, wrap them: async def _wrap(): return value, then run(_wrap())","Verify with inspect.iscoroutinefunction before passing callables through generic glue code"],"exampleFix":"# before\nrunner.run(main)      # main is a coroutine function -> TypeError\n# after\nrunner.run(main())    # invoke to obtain the coroutine","handlingStrategy":"type-guard","validationCode":"import inspect, asyncio\n\ndef run_entry(runner, main):\n    if inspect.iscoroutinefunction(main):\n        raise TypeError('call the coroutine function: pass main(), not main')\n    if not (asyncio.iscoroutine(main) or inspect.isawaitable(main)):\n        raise TypeError(f'not awaitable: {type(main).__name__}')\n    return runner.run(main)","typeGuard":"def is_runnable_coroutine(obj) -> bool:\n    return asyncio.iscoroutine(obj) or inspect.isawaitable(obj)","tryCatchPattern":"try:\n    runner.run(main)\nexcept TypeError as e:\n    if 'awaitable is required' in str(e) and inspect.iscoroutinefunction(main):\n        raise TypeError('forgot parentheses: use main()') from e\n    raise","preventionTips":["Always invoke: run(main()), never run(main)","Annotate entry helpers as (coro: Coroutine) so mypy flags misuse","Wrap non-coroutine results in an async def before run()","Beware 'x or default_main' patterns that can pass a function reference"],"tags":["asyncio","typeerror","coroutine","runner"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}