Skip to content

Commit 9c8b32f

Browse files
authored
Rewrite internal run_sync (#222)
* Rewrite internal run_sync
1 parent 6416f26 commit 9c8b32f

4 files changed

Lines changed: 35 additions & 9 deletions

File tree

docs/en/docs/release-notes.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@ hide:
55

66
# Release Notes
77

8+
## 0.17.1
9+
10+
### Changed
11+
12+
* **Dual‑mode support in run_sync:** Now accepts either an async function with args or a standalone coroutine object.
13+
* **Input normalization:** Uses `inspect` to detect and wrap calls into a zero‑argument coroutine factory.
14+
* **Seamless execution:** Drives work on the main thread via `anyio.run`,
15+
with a clean fallback to `ThreadPoolExecutor` if an event loop is active.
16+
* **Error clarity:** Raises a precise `TypeError` when the argument is neither a coroutine function nor object.
17+
* **Simplified API:** Eliminates nested lambdas by centralizing logic into a single `wrapper_fn`
18+
passed to `anyio.run`.
19+
820
## 0.17.0
921

1022
### Added

lilya/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.17.0"
1+
__version__ = "0.17.1"

lilya/compat.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import functools
44
import hashlib
55
import inspect
6-
from collections.abc import Awaitable, Generator
6+
from collections.abc import Awaitable, Callable, Generator
77
from concurrent import futures
88
from typing import Any, Generic, Protocol, TypeVar
99

@@ -47,17 +47,31 @@ def is_async_callable(obj: Any) -> bool:
4747
)
4848

4949

50-
def run_sync(async_function: Awaitable, *args: Any, **kwargs: Any) -> Any:
50+
def run_sync(fn: Callable[..., Any] | Awaitable, *args: Any, **kwargs: Any) -> Any:
5151
"""
52-
Runs the queries in sync mode
52+
Run an async function or coroutine object in sync code.
53+
54+
- If you pass a coroutine function + args, e.g. run_sync(fetch_data, 42),
55+
it will call `anyio.run(fetch_data, 42)`.
56+
- If you pass a coroutine object, e.g. run_sync(fetch_data(42)), it will
57+
call `anyio.run(lambda: fetch_data(42))`.
58+
59+
Falls back to a ThreadPoolExecutor if we detect an existing running loop.
5360
"""
61+
if inspect.iscoroutine(fn):
62+
wrapper_fn: Callable[[], Awaitable[Any]] = lambda: fn # noqa: E731
63+
elif inspect.iscoroutinefunction(fn):
64+
wrapper_fn = lambda: fn(*args, **kwargs) # noqa: E731
65+
else:
66+
raise TypeError(
67+
f"run_sync() expects an async function or coroutine object; got {type(fn)}"
68+
)
5469
try:
55-
return anyio.run(async_function, *args, **kwargs)
70+
return anyio.run(wrapper_fn)
5671
except RuntimeError:
5772
with futures.ThreadPoolExecutor(max_workers=1) as executor:
58-
return executor.submit(
59-
lambda: anyio.run(lambda: async_function, *args, **kwargs)
60-
).result()
73+
future: futures.Future = executor.submit(anyio.run, wrapper_fn)
74+
return future.result()
6175

6276

6377
class AsyncResourceHandler(Generic[SupportsAsyncCloseType]):

tests/dependencies/resolve/test_resolve.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def test_use_requires_in_function_dependencies_using_provide(test_client_factory
4444
"current_user": Provide(get_current_user),
4545
},
4646
),
47-
]
47+
],
4848
) as client:
4949
response = client.get("/requires")
5050

0 commit comments

Comments
 (0)