|
|
|
|
import math
|
|
|
|
|
import sys
|
|
|
|
|
import threading
|
|
|
|
|
from contextlib import contextmanager
|
|
|
|
|
from importlib import import_module
|
|
|
|
|
from typing import (
|
|
|
|
|
Any,
|
|
|
|
|
Callable,
|
|
|
|
|
Coroutine,
|
|
|
|
|
Dict,
|
|
|
|
|
Generator,
|
|
|
|
|
Optional,
|
|
|
|
|
Tuple,
|
|
|
|
|
Type,
|
|
|
|
|
TypeVar,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
import sniffio
|
|
|
|
|
|
|
|
|
|
# This must be updated when new backends are introduced
|
|
|
|
|
from ._compat import DeprecatedAwaitableFloat
|
|
|
|
|
|
|
|
|
|
BACKENDS = "asyncio", "trio"
|
|
|
|
|
|
|
|
|
|
T_Retval = TypeVar("T_Retval")
|
|
|
|
|
threadlocals = threading.local()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def run(
|
|
|
|
|
func: Callable[..., Coroutine[Any, Any, T_Retval]],
|
|
|
|
|
*args: object,
|
|
|
|
|
backend: str = "asyncio",
|
|
|
|
|
backend_options: Optional[Dict[str, Any]] = None,
|
|
|
|
|
) -> T_Retval:
|
|
|
|
|
"""
|
|
|
|
|
Run the given coroutine function in an asynchronous event loop.
|
|
|
|
|
|
|
|
|
|
The current thread must not be already running an event loop.
|
|
|
|
|
|
|
|
|
|
:param func: a coroutine function
|
|
|
|
|
:param args: positional arguments to ``func``
|
|
|
|
|
:param backend: name of the asynchronous event loop implementation – currently either
|
|
|
|
|
``asyncio`` or ``trio``
|
|
|
|
|
:param backend_options: keyword arguments to call the backend ``run()`` implementation with
|
|
|
|
|
(documented :ref:`here <backend options>`)
|
|
|
|
|
:return: the return value of the coroutine function
|
|
|
|
|
:raises RuntimeError: if an asynchronous event loop is already running in this thread
|
|
|
|
|
:raises LookupError: if the named backend is not found
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
asynclib_name = sniffio.current_async_library()
|
|
|
|
|
except sniffio.AsyncLibraryNotFoundError:
|
|
|
|
|
pass
|
|
|
|
|
else:
|
|
|
|
|
raise RuntimeError(f"Already running {asynclib_name} in this thread")
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
asynclib = import_module(f"..._backends._{backend}", package=__name__)
|
|
|
|
|
except ImportError as exc:
|
|
|
|
|
raise LookupError(f"No such backend: {backend}") from exc
|
|
|
|
|
|
|
|
|
|
token = None
|
|
|
|
|
if sniffio.current_async_library_cvar.get(None) is None:
|
|
|
|
|
# Since we're in control of the event loop, we can cache the name of the async library
|
|
|
|
|
token = sniffio.current_async_library_cvar.set(backend)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
backend_options = backend_options or {}
|
|
|
|
|
return asynclib.run(func, *args, **backend_options)
|
|
|
|
|
finally:
|
|
|
|
|
if token:
|
|
|
|
|
sniffio.current_async_library_cvar.reset(token)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def sleep(delay: float) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Pause the current task for the specified duration.
|
|
|
|
|
|
|
|
|
|
:param delay: the duration, in seconds
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
return await get_asynclib().sleep(delay)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def sleep_forever() -> None:
|
|
|
|
|
"""
|
|
|
|
|
Pause the current task until it's cancelled.
|
|
|
|
|
|
|
|
|
|
This is a shortcut for ``sleep(math.inf)``.
|
|
|
|
|
|
|
|
|
|
.. versionadded:: 3.1
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
await sleep(math.inf)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def sleep_until(deadline: float) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Pause the current task until the given time.
|
|
|
|
|
|
|
|
|
|
:param deadline: the absolute time to wake up at (according to the internal monotonic clock of
|
|
|
|
|
the event loop)
|
|
|
|
|
|
|
|
|
|
.. versionadded:: 3.1
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
now = current_time()
|
|
|
|
|
await sleep(max(deadline - now, 0))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def current_time() -> DeprecatedAwaitableFloat:
|
|
|
|
|
"""
|
|
|
|
|
Return the current value of the event loop's internal clock.
|
|
|
|
|
|
|
|
|
|
:return: the clock value (seconds)
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
return DeprecatedAwaitableFloat(get_asynclib().current_time(), current_time)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_all_backends() -> Tuple[str, ...]:
|
|
|
|
|
"""Return a tuple of the names of all built-in backends."""
|
|
|
|
|
return BACKENDS
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_cancelled_exc_class() -> Type[BaseException]:
|
|
|
|
|
"""Return the current async library's cancellation exception class."""
|
|
|
|
|
return get_asynclib().CancelledError
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
|
# Private API
|
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@contextmanager
|
|
|
|
|
def claim_worker_thread(backend: str) -> Generator[Any, None, None]:
|
|
|
|
|
module = sys.modules["anyio._backends._" + backend]
|
|
|
|
|
threadlocals.current_async_module = module
|
|
|
|
|
try:
|
|
|
|
|
yield
|
|
|
|
|
finally:
|
|
|
|
|
del threadlocals.current_async_module
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_asynclib(asynclib_name: Optional[str] = None) -> Any:
|
|
|
|
|
if asynclib_name is None:
|
|
|
|
|
asynclib_name = sniffio.current_async_library()
|
|
|
|
|
|
|
|
|
|
modulename = "anyio._backends._" + asynclib_name
|
|
|
|
|
try:
|
|
|
|
|
return sys.modules[modulename]
|
|
|
|
|
except KeyError:
|
|
|
|
|
return import_module(modulename)
|