python/cpython · error · ValueError

Unknown thread safety level {level!r} for {name!r}. Valid le

Error message

Unknown thread safety level {level!r} for {name!r}. Valid levels: {sorted(_VALID_THREADSAFETY_LEVELS)}

What it means

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.

Source

Thrown at Doc/tools/extensions/c_annotations.py:150

    "atomic",
})


def read_threadsafety_data(
    threadsafety_filename: Path,
) -> dict[str, ThreadSafetyEntry]:
    threadsafety_data = {}
    for line in threadsafety_filename.read_text(encoding="utf8").splitlines():
        line = line.strip()
        if not line or line.startswith("#"):
            continue
        # Each line is of the form: function_name : level : [comment]
        parts = line.split(":", 2)
        if len(parts) < 2:
            raise ValueError(f"Wrong field count in {line!r}")
        name, level = parts[0].strip(), parts[1].strip()
        if level not in _VALID_THREADSAFETY_LEVELS:
            raise ValueError(
                f"Unknown thread safety level {level!r} for {name!r}. "
                f"Valid levels: {sorted(_VALID_THREADSAFETY_LEVELS)}"
            )
        threadsafety_data[name] = ThreadSafetyEntry(name=name, level=level)
    return threadsafety_data


def add_annotations(app: Sphinx, doctree: nodes.document) -> None:
    state = app.env.domaindata["c_annotations"]
    refcount_data = state["refcount_data"]
    stable_abi_data = state["stable_abi_data"]
    threadsafety_data = state["threadsafety_data"]
    for node in doctree.findall(addnodes.desc_content):
        par = node.parent
        if par["domain"] != "c":
            continue
        if not par[0].get("ids", None):
            continue

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Call the coroutine function: runner.run(main())
  2. If building the awaitable dynamically, ensure the expression evaluates to a coroutine: runner.run(factory()) where factory is async def
  3. For plain results, wrap them: async def _wrap(): return value, then run(_wrap())
  4. Verify with inspect.iscoroutinefunction before passing callables through generic glue code

Example fix

# before
runner.run(main)      # main is a coroutine function -> TypeError
# after
runner.run(main())    # invoke to obtain the coroutine
Defensive patterns

Strategy: type-guard

Validate before calling

import inspect, asyncio

def run_entry(runner, main):
    if inspect.iscoroutinefunction(main):
        raise TypeError('call the coroutine function: pass main(), not main')
    if not (asyncio.iscoroutine(main) or inspect.isawaitable(main)):
        raise TypeError(f'not awaitable: {type(main).__name__}')
    return runner.run(main)

Type guard

def is_runnable_coroutine(obj) -> bool:
    return asyncio.iscoroutine(obj) or inspect.isawaitable(obj)

Try / catch

try:
    runner.run(main)
except TypeError as e:
    if 'awaitable is required' in str(e) and inspect.iscoroutinefunction(main):
        raise TypeError('forgot parentheses: use main()') from e
    raise

Prevention

When it happens

Trigger: 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.

Common situations: 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).

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/9802107c8755b001. Report an issue: GitHub.