55 "Calc" ,
66 "CalcAsync" ,
77 "calc" ,
8- "calc_async" ,
98 "Effect" ,
109 "EffectAsync" ,
1110 "effect" ,
12- "effect_async" ,
1311 "isolate" ,
1412 "invalidate_later" ,
1513)
2523 TypeVar ,
2624 Union ,
2725 Generic ,
26+ cast ,
2827)
2928import typing
3029import inspect
@@ -70,17 +69,25 @@ def set(self, value: T) -> bool:
7069# ==============================================================================
7170# Calc
7271# ==============================================================================
72+
73+ CalcFunction = Callable [[], T ]
74+ CalcFunctionAsync = Callable [[], Awaitable [T ]]
75+
76+
7377class Calc (Generic [T ]):
7478 def __init__ (
7579 self ,
76- func : Callable [[], T ],
80+ fn : CalcFunction [ T ],
7781 * ,
7882 session : Union [MISSING_TYPE , "Session" , None ] = MISSING ,
7983 ) -> None :
80- if inspect . iscoroutinefunction ( func ):
81- raise TypeError ( "Reactive requires a non-async function" )
84+ self . __name__ = fn . __name__
85+ self . __doc__ = fn . __doc__
8286
83- self ._func : Callable [[], Awaitable [T ]] = utils .wrap_async (func )
87+ # The CalcAsync subclass will pass in an async function, but it tells the
88+ # static type checker that it's synchronous. wrap_async() is smart -- if is
89+ # passed an async function, it will not change it.
90+ self ._fn : CalcFunctionAsync [T ] = utils .wrap_async (fn )
8491 self ._is_async : bool = False
8592
8693 self ._dependents : Dependents = Dependents ()
@@ -152,26 +159,22 @@ def _on_invalidate_cb(self) -> None:
152159 async def _run_func (self ) -> None :
153160 self ._error .clear ()
154161 try :
155- self ._value .append (await self ._func ())
162+ self ._value .append (await self ._fn ())
156163 except Exception as err :
157164 self ._error .append (err )
158165
159166
160167class CalcAsync (Calc [T ]):
161168 def __init__ (
162169 self ,
163- func : Callable [[], Awaitable [ T ] ],
170+ fn : CalcFunctionAsync [ T ],
164171 * ,
165172 session : Union [MISSING_TYPE , "Session" , None ] = MISSING ,
166173 ) -> None :
167- if not inspect .iscoroutinefunction (func ):
168- raise TypeError ("CalcAsync requires an async function" )
169-
170- # Init the Calc base class with a placeholder synchronous function so it won't
171- # throw an error, then replace it with the async function. Need the `cast` to
172- # satisfy the type checker.
173- super ().__init__ (lambda : typing .cast (T , None ), session = session )
174- self ._func : Callable [[], Awaitable [T ]] = func
174+ if not inspect .iscoroutinefunction (fn ):
175+ raise TypeError (self .__class__ .__name__ + " requires an async function" )
176+
177+ super ().__init__ (cast (CalcFunction [T ], fn ), session = session )
175178 self ._is_async = True
176179
177180 async def __call__ (self ) -> T :
@@ -180,37 +183,41 @@ async def __call__(self) -> T:
180183
181184def calc (
182185 * , session : Union [MISSING_TYPE , "Session" , None ] = MISSING
183- ) -> Callable [[Callable [[], T ]], Calc [T ]]:
184- def create_calc (fn : Callable [[], T ]) -> Calc [T ]:
185- return Calc (fn , session = session )
186+ ) -> Callable [[Union [CalcFunction [T ], CalcFunctionAsync [T ]]], Calc [T ]]:
187+ def create_calc (fn : Union [CalcFunction [T ], CalcFunctionAsync [T ]]) -> Calc [T ]:
188+ if inspect .iscoroutinefunction (fn ):
189+ fn = cast (CalcFunctionAsync [T ], fn )
190+ return CalcAsync (fn , session = session )
191+ else :
192+ fn = cast (CalcFunction [T ], fn )
193+ return Calc (fn , session = session )
186194
187195 return create_calc
188196
189197
190- def calc_async (
191- * , session : Union [MISSING_TYPE , "Session" , None ] = MISSING
192- ) -> Callable [[Callable [[], Awaitable [T ]]], CalcAsync [T ]]:
193- def create_calc_async (fn : Callable [[], Awaitable [T ]]) -> CalcAsync [T ]:
194- return CalcAsync (fn , session = session )
195-
196- return create_calc_async
197-
198-
199198# ==============================================================================
200199# Effect
201200# ==============================================================================
201+
202+ EffectFunction = Callable [[], None ]
203+ EffectFunctionAsync = Callable [[], Awaitable [None ]]
204+
205+
202206class Effect :
203207 def __init__ (
204208 self ,
205- func : Callable [[], None ] ,
209+ fn : EffectFunction ,
206210 * ,
207211 priority : int = 0 ,
208212 session : Union [MISSING_TYPE , "Session" , None ] = MISSING ,
209213 ) -> None :
210- if inspect . iscoroutinefunction ( func ):
211- raise TypeError ( "Effect requires a non-async function" )
214+ self . __name__ = fn . __name__
215+ self . __doc__ = fn . __doc__
212216
213- self ._func : Callable [[], Awaitable [None ]] = utils .wrap_async (func )
217+ # The EffectAsync subclass will pass in an async function, but it tells the
218+ # static type checker that it's synchronous. wrap_async() is smart -- if is
219+ # passed an async function, it will not change it.
220+ self ._fn : EffectFunctionAsync = utils .wrap_async (fn )
214221 self ._is_async : bool = False
215222
216223 self ._priority : int = priority
@@ -270,7 +277,7 @@ async def run(self) -> None:
270277 with shiny_session .session_context (self ._session ):
271278 try :
272279 with ctx ():
273- await self ._func ()
280+ await self ._fn ()
274281 except SilentException :
275282 # It's OK for SilentException to cause an Effect to stop running
276283 pass
@@ -297,24 +304,21 @@ def _on_session_ended_cb(self) -> None:
297304class EffectAsync (Effect ):
298305 def __init__ (
299306 self ,
300- func : Callable [[], Awaitable [ None ]] ,
307+ fn : EffectFunctionAsync ,
301308 * ,
302309 priority : int = 0 ,
303310 session : Union [MISSING_TYPE , "Session" , None ] = MISSING ,
304311 ) -> None :
305- if not inspect .iscoroutinefunction (func ):
306- raise TypeError ("EffectAsync requires an async function" )
312+ if not inspect .iscoroutinefunction (fn ):
313+ raise TypeError (self . __class__ . __name__ + " requires an async function" )
307314
308- # Init the Efect base class with a placeholder synchronous function
309- # so it won't throw an error, then replace it with the async function.
310- super ().__init__ (lambda : None , session = session , priority = priority )
311- self ._func : Callable [[], Awaitable [None ]] = func
315+ super ().__init__ (cast (EffectFunction , fn ), session = session , priority = priority )
312316 self ._is_async = True
313317
314318
315319def effect (
316320 * , priority : int = 0 , session : Union [MISSING_TYPE , "Session" , None ] = MISSING
317- ) -> Callable [[Callable [[], None ]], Effect ]:
321+ ) -> Callable [[Union [ EffectFunction , EffectFunctionAsync ]], Effect ]:
318322 """[summary]
319323
320324 Args:
@@ -325,23 +329,17 @@ def effect(
325329 [description]
326330 """
327331
328- def create_effect (fn : Callable [[], None ]) -> Effect :
329- return Effect (fn , priority = priority , session = session )
332+ def create_effect (fn : Union [EffectFunction , EffectFunctionAsync ]) -> Effect :
333+ if inspect .iscoroutinefunction (fn ):
334+ fn = cast (EffectFunctionAsync , fn )
335+ return EffectAsync (fn , priority = priority , session = session )
336+ else :
337+ fn = cast (EffectFunction , fn )
338+ return Effect (fn , priority = priority , session = session )
330339
331340 return create_effect
332341
333342
334- def effect_async (
335- * ,
336- priority : int = 0 ,
337- session : Union [MISSING_TYPE , "Session" , None ] = MISSING ,
338- ) -> Callable [[Callable [[], Awaitable [None ]]], EffectAsync ]:
339- def create_effect_async (fn : Callable [[], Awaitable [None ]]) -> EffectAsync :
340- return EffectAsync (fn , priority = priority , session = session )
341-
342- return create_effect_async
343-
344-
345343# ==============================================================================
346344# Miscellaneous functions
347345# ==============================================================================
0 commit comments