jw-pkg/src/python/jw/pkg/lib/AsyncRunner.py
Jan Lindemann 5d1ba6e15a
pyproject.toml: Enforce import annotations style

Add new ruff rules and fix their fallout:

future-annotations = true

select = [ "TC", # type-checking import placement rules "FA", # future annotations rules ]

This comprises:

- Streamline imports and exports in cmds.xxx.Cmd

- Import base class as "Base"
- Export types Cmd and Parent via __all__

- Move all types imported only for annotation below TYPE_CHECKING

- Use "from __future__ import annotations" all over the place

Signed-off-by: Jan Lindemann <jan@janware.com>
2026-06-01 14:34:25 +02:00

57 lines
1.6 KiB
Python

from __future__ import annotations
import asyncio
import concurrent.futures
import contextlib
from typing import TypeVar, TYPE_CHECKING
if TYPE_CHECKING:
from collections.abc import Awaitable, Generator
T = TypeVar('T')
@contextlib.contextmanager
def loop_in_thread() -> Generator[asyncio.AbstractEventLoop, None, None]:
loop_fut: concurrent.futures.Future[asyncio.AbstractEventLoop] = (
concurrent.futures.Future()
)
stop_event = asyncio.Event()
async def main() -> None:
loop_fut.set_result(asyncio.get_running_loop())
await stop_event.wait()
with concurrent.futures.ThreadPoolExecutor(max_workers = 1) as tpe:
complete_fut = tpe.submit(asyncio.run, main())
for fut in concurrent.futures.as_completed((loop_fut, complete_fut)):
if fut is loop_fut:
loop = loop_fut.result()
try:
yield loop
finally:
loop.call_soon_threadsafe(stop_event.set)
else:
fut.result()
class AsyncRunner:
def __init__(self) -> None:
self._cm = loop_in_thread()
self._loop = self._cm.__enter__()
def call(self, awaitable: Awaitable[T], timeout: float | None = None) -> T:
fut = asyncio.run_coroutine_threadsafe(awaitable, self._loop) # type: ignore
return fut.result(timeout)
def close(self) -> None:
self._cm.__exit__(None, None, None)
def __enter__(self) -> AsyncRunner:
return self
def __exit__(self, exc_type, exc, tb) -> None:
self.close()