|
1 | | -from asyncio import ( |
2 | | - AbstractEventLoop, |
3 | | - get_event_loop, |
4 | | - new_event_loop, |
5 | | - set_event_loop, |
6 | | -) |
7 | | -from functools import lru_cache, wraps |
8 | | -from threading import Thread |
9 | | -from typing import ( |
10 | | - TYPE_CHECKING, |
11 | | - Any, |
12 | | - Callable, |
13 | | - Coroutine, |
14 | | - Optional, |
15 | | - Type, |
16 | | - TypeVar, |
17 | | -) |
| 1 | +from functools import lru_cache, partial, wraps |
| 2 | +from typing import TYPE_CHECKING, Any, Callable, Type, TypeVar |
18 | 3 |
|
| 4 | +import trio |
19 | 5 | from httpx import URL |
20 | 6 |
|
21 | 7 | T = TypeVar("T") |
@@ -84,100 +70,19 @@ def fix_url_schema(url: str) -> str: |
84 | 70 | return url if url.startswith("http") else f"https://{url}" |
85 | 71 |
|
86 | 72 |
|
87 | | -class AsyncJobThread: |
88 | | - """Thread runner that allows running async tasks synchronously in a separate thread. |
89 | | -
|
90 | | - Caches loop to be reused in all threads. |
91 | | - It allows running async functions synchronously inside a running event loop. |
92 | | - Since nesting loops is not allowed, we create a separate thread for a new event loop |
93 | | -
|
94 | | - Attributes: |
95 | | - result (Any): Value, returned by coroutine execution |
96 | | - exception (Optional[BaseException]): If any, exception that occurred |
97 | | - during coroutine execution |
98 | | - """ |
99 | | - |
100 | | - def __init__(self) -> None: |
101 | | - self._loop: Optional[AbstractEventLoop] = None |
102 | | - self.result: Any = None |
103 | | - self.exception: Optional[BaseException] = None |
104 | | - |
105 | | - def _initialize_loop(self) -> None: |
106 | | - """Initialize a loop once to use for later execution. |
107 | | -
|
108 | | - Tries to get a running loop. |
109 | | - Creates a new loop if no active one, and sets it as active. |
110 | | - """ |
111 | | - if not self._loop: |
112 | | - try: |
113 | | - # despite the docs, this function fails if no loop is set |
114 | | - self._loop = get_event_loop() |
115 | | - except RuntimeError: |
116 | | - self._loop = new_event_loop() |
117 | | - set_event_loop(self._loop) |
118 | | - |
119 | | - def _run(self, coro: Coroutine) -> None: |
120 | | - """Run coroutine in an event loop. |
121 | | -
|
122 | | - Execution return value is stored into ``result`` field. |
123 | | - If an exception occurs, it will be caught and stored into ``exception`` field. |
124 | | -
|
125 | | - Args: |
126 | | - coro (Coroutine): Coroutine to execute |
127 | | - """ |
128 | | - try: |
129 | | - self._initialize_loop() |
130 | | - assert self._loop is not None |
131 | | - self.result = self._loop.run_until_complete(coro) |
132 | | - except BaseException as e: |
133 | | - self.exception = e |
134 | | - |
135 | | - def execute(self, coro: Coroutine) -> Any: |
136 | | - """Execute coroutine in a separate thread. |
137 | | -
|
138 | | - Args: |
139 | | - coro (Coroutine): Coroutine to execute |
140 | | -
|
141 | | - Returns: |
142 | | - Any: Coroutine execution return value |
143 | | -
|
144 | | - Raises: |
145 | | - exception: Exeption, occured within coroutine |
146 | | - """ |
147 | | - thread = Thread(target=self._run, args=[coro]) |
148 | | - thread.start() |
149 | | - thread.join() |
150 | | - if self.exception: |
151 | | - raise self.exception |
152 | | - return self.result |
153 | | - |
154 | | - |
155 | | -def async_to_sync(f: Callable, async_job_thread: AsyncJobThread = None) -> Callable: |
| 73 | +def async_to_sync(f: Callable) -> Callable: |
156 | 74 | """Convert async function to sync. |
157 | 75 |
|
158 | 76 | Args: |
159 | 77 | f (Callable): function to convert |
160 | | - async_job_thread (AsyncJobThread): Job thread instance to use for async excution |
161 | | - (Default value = None) |
162 | 78 |
|
163 | 79 | Returns: |
164 | 80 | Callable: regular function, which can be executed synchronously |
165 | 81 | """ |
166 | 82 |
|
167 | 83 | @wraps(f) |
168 | 84 | def sync(*args: Any, **kwargs: Any) -> Any: |
169 | | - try: |
170 | | - loop = get_event_loop() |
171 | | - except RuntimeError: |
172 | | - loop = new_event_loop() |
173 | | - set_event_loop(loop) |
174 | | - # We are inside a running loop |
175 | | - if loop.is_running(): |
176 | | - nonlocal async_job_thread |
177 | | - if not async_job_thread: |
178 | | - async_job_thread = AsyncJobThread() |
179 | | - return async_job_thread.execute(f(*args, **kwargs)) |
180 | | - return loop.run_until_complete(f(*args, **kwargs)) |
| 85 | + return trio.run(partial(f, *args, **kwargs)) |
181 | 86 |
|
182 | 87 | return sync |
183 | 88 |
|
|
0 commit comments