11from  __future__ import  annotations 
22
3+ import  sys 
4+ import  asyncio 
35import  functools 
4- from  typing  import  TypeVar , Callable , Awaitable 
6+ import  contextvars 
7+ from  typing  import  Any , TypeVar , Callable , Awaitable 
58from  typing_extensions  import  ParamSpec 
69
7- import  anyio 
8- import  anyio .to_thread 
9- 
10- from  ._reflection  import  function_has_argument 
11- 
1210T_Retval  =  TypeVar ("T_Retval" )
1311T_ParamSpec  =  ParamSpec ("T_ParamSpec" )
1412
1513
16- # copied from `asyncer`, https://github.com/tiangolo/asyncer 
17- def  asyncify (
18-     function : Callable [T_ParamSpec , T_Retval ],
19-     * ,
20-     cancellable : bool  =  False ,
21-     limiter : anyio .CapacityLimiter  |  None  =  None ,
22- ) ->  Callable [T_ParamSpec , Awaitable [T_Retval ]]:
14+ if  sys .version_info  >=  (3 , 9 ):
15+     to_thread  =  asyncio .to_thread 
16+ else :
17+     # backport of https://docs.python.org/3/library/asyncio-task.html#asyncio.to_thread 
18+     # for Python 3.8 support 
19+     async  def  to_thread (
20+         func : Callable [T_ParamSpec , T_Retval ], / , * args : T_ParamSpec .args , ** kwargs : T_ParamSpec .kwargs 
21+     ) ->  Any :
22+         """Asynchronously run function *func* in a separate thread. 
23+ 
24+         Any *args and **kwargs supplied for this function are directly passed 
25+         to *func*. Also, the current :class:`contextvars.Context` is propagated, 
26+         allowing context variables from the main thread to be accessed in the 
27+         separate thread. 
28+ 
29+         Returns a coroutine that can be awaited to get the eventual result of *func*. 
30+         """ 
31+         loop  =  asyncio .events .get_running_loop ()
32+         ctx  =  contextvars .copy_context ()
33+         func_call  =  functools .partial (ctx .run , func , * args , ** kwargs )
34+         return  await  loop .run_in_executor (None , func_call )
35+ 
36+ 
37+ # inspired by `asyncer`, https://github.com/tiangolo/asyncer 
38+ def  asyncify (function : Callable [T_ParamSpec , T_Retval ]) ->  Callable [T_ParamSpec , Awaitable [T_Retval ]]:
2339    """ 
2440    Take a blocking function and create an async one that receives the same 
25-     positional and keyword arguments, and that when called, calls the original function 
26-     in a worker thread using `anyio.to_thread.run_sync()`. Internally, 
27-     `asyncer.asyncify()` uses the same `anyio.to_thread.run_sync()`, but it supports 
28-     keyword arguments additional to positional arguments and it adds better support for 
29-     autocompletion and inline errors for the arguments of the function called and the 
30-     return value. 
31- 
32-     If the `cancellable` option is enabled and the task waiting for its completion is 
33-     cancelled, the thread will still run its course but its return value (or any raised 
34-     exception) will be ignored. 
41+     positional and keyword arguments. For python version 3.9 and above, it uses 
42+     asyncio.to_thread to run the function in a separate thread. For python version 
43+     3.8, it uses locally defined copy of the asyncio.to_thread function which was 
44+     introduced in python 3.9. 
3545
36-     Use it like this : 
46+     Usage : 
3747
38-     ```Python  
39-     def do_work (arg1, arg2, kwarg1="", kwarg2="") -> str : 
40-         # Do work  
41-         return "Some  result"  
48+     ```python  
49+     def blocking_func (arg1, arg2, kwarg1=None) : 
50+         # blocking code  
51+         return result 
4252
4353
44-     result = await to_thread.asyncify(do_work)("spam", "ham", kwarg1="a", kwarg2="b") 
45-     print(result) 
54+     result = asyncify(blocking_function)(arg1, arg2, kwarg1=value1) 
4655    ``` 
4756
4857    ## Arguments 
4958
5059    `function`: a blocking regular callable (e.g. a function) 
51-     `cancellable`: `True` to allow cancellation of the operation 
52-     `limiter`: capacity limiter to use to limit the total amount of threads running 
53-         (if omitted, the default limiter is used) 
5460
5561    ## Return 
5662
@@ -60,22 +66,6 @@ def do_work(arg1, arg2, kwarg1="", kwarg2="") -> str:
6066    """ 
6167
6268    async  def  wrapper (* args : T_ParamSpec .args , ** kwargs : T_ParamSpec .kwargs ) ->  T_Retval :
63-         partial_f  =  functools .partial (function , * args , ** kwargs )
64- 
65-         # In `v4.1.0` anyio added the `abandon_on_cancel` argument and deprecated the old 
66-         # `cancellable` argument, so we need to use the new `abandon_on_cancel` to avoid 
67-         # surfacing deprecation warnings. 
68-         if  function_has_argument (anyio .to_thread .run_sync , "abandon_on_cancel" ):
69-             return  await  anyio .to_thread .run_sync (
70-                 partial_f ,
71-                 abandon_on_cancel = cancellable ,
72-                 limiter = limiter ,
73-             )
74- 
75-         return  await  anyio .to_thread .run_sync (
76-             partial_f ,
77-             cancellable = cancellable ,
78-             limiter = limiter ,
79-         )
69+         return  await  to_thread (function , * args , ** kwargs )
8070
8171    return  wrapper 
0 commit comments