File size: 1,316 Bytes
c509967
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
"""Async helpers shared across ChemGraph CLI and UI."""

from __future__ import annotations

import asyncio
import threading
from typing import Any, Callable


def run_async_callable(fn: Callable[..., Any]) -> Any:
    """Run an async callable and return its result in a sync context.

    If no event loop is running, uses ``asyncio.run`` directly.
    Otherwise, spawns a daemon thread so that the call does not
    conflict with an already-running loop (e.g. inside Streamlit).

    Parameters
    ----------
    fn : Callable[..., Any]
        Zero-argument callable that returns an awaitable.

    Returns
    -------
    Any
        Result of the awaited callable.
    """
    try:
        asyncio.get_running_loop()
    except RuntimeError:
        return asyncio.run(fn())

    result_container: dict[str, Any] = {}
    error_container: dict[str, Exception] = {}

    def runner() -> None:
        """Run the awaitable in a background event loop."""
        try:
            result_container["value"] = asyncio.run(fn())
        except Exception as exc:
            error_container["error"] = exc

    thread = threading.Thread(target=runner, daemon=True)
    thread.start()
    thread.join()
    if "error" in error_container:
        raise error_container["error"]
    return result_container.get("value")