6
6
import inspect
7
7
import logging
8
8
import threading
9
+ import warnings
9
10
from enum import Enum
10
11
import typing
11
12
from typing import (
@@ -536,6 +537,7 @@ def task(
536
537
f : None = None ,
537
538
* ,
538
539
prefer_threaded : bool = ...,
540
+ check_for_render_context : bool = ...,
539
541
) -> Callable [[Callable [P , R ]], Task [P , R ]]: ...
540
542
541
543
@@ -544,13 +546,15 @@ def task(
544
546
f : Callable [P , Union [Coroutine [Any , Any , R ], R ]],
545
547
* ,
546
548
prefer_threaded : bool = ...,
549
+ check_for_render_context : bool = ...,
547
550
) -> Task [P , R ]: ...
548
551
549
552
550
553
def task (
551
554
f : Union [None , Callable [P , Union [Coroutine [Any , Any , R ], R ]]] = None ,
552
555
* ,
553
556
prefer_threaded : bool = True ,
557
+ check_for_render_context : bool = True ,
554
558
) -> Union [Callable [[Callable [P , R ]], Task [P , R ]], Task [P , R ]]:
555
559
"""Decorator to turn a function or coroutine function into a task.
556
560
@@ -764,12 +768,76 @@ def Page():
764
768
This ensures that even when a coroutine functions calls a blocking function the UI is still responsive.
765
769
On platform where threads are not supported (like Pyodide / WASM / Emscripten / PyScript), a coroutine
766
770
function will always run in the current event loop.
771
+ - `check_for_render_context` - bool: If true, we will check if we are in a render context, and if so, we will
772
+ warn you that you should probably be using `use_task` instead of `task`.
767
773
768
774
```
769
775
770
776
"""
771
777
778
+ def check_if_we_should_use_use_task ():
779
+ import reacton .core
780
+
781
+ in_reacton_context = reacton .core .get_render_context (required = False ) is not None
782
+ if not in_reacton_context :
783
+ # We are not in a reacton context, so we should not (and cannot) use use_task
784
+ return
785
+ from .toestand import _find_outside_solara_frame
786
+
787
+ frame = _find_outside_solara_frame ()
788
+ if frame is None :
789
+ # We cannot determine which frame we are in, just skip this check
790
+ return
791
+ import inspect
792
+
793
+ tb = inspect .getframeinfo (frame )
794
+ msg = """You are calling task(...) from a component, while you should probably be using use_task.
795
+
796
+ Reason:
797
+ - task(...) creates a new task object on every render, and should only be used outside of a component.
798
+ - use_task(...) returns the same task object on every render, and should be used inside a component.
799
+
800
+ Example:
801
+ @solara.component
802
+ def Page():
803
+ @task # This is wrong, this creates a new task object on every render
804
+ def my_task():
805
+ ...
806
+
807
+ Instead, you should do:
808
+ @solara.component
809
+ def Page():
810
+ @use_task
811
+ def my_task():
812
+ ...
813
+
814
+ """
815
+ if tb :
816
+ if tb .code_context :
817
+ code = tb .code_context [0 ]
818
+ else :
819
+ code = "<No code context available>"
820
+ msg += f"This warning was triggered from:\n { tb .filename } :{ tb .lineno } \n { code .strip ()} "
821
+
822
+ # Check if the call is within a use_memo context by inspecting the call stack
823
+ if frame :
824
+ caller_frame = frame .f_back
825
+ # Check a few frames up the stack (e.g., up to 5) for 'use_memo'
826
+ for _ in range (5 ):
827
+ if caller_frame is None :
828
+ break
829
+ func_name = caller_frame .f_code .co_name
830
+ module_name = caller_frame .f_globals .get ("__name__" , "" )
831
+ if func_name == "use_memo" and (module_name .startswith ("solara." ) or module_name .startswith ("reacton." )):
832
+ # We are in a use_memo (or a context that should not trigger the warning)
833
+ return
834
+ caller_frame = caller_frame .f_back
835
+
836
+ warnings .warn (msg )
837
+
772
838
def wrapper (f : Union [None , Callable [P , Union [Coroutine [Any , Any , R ], R ]]]) -> Task [P , R ]:
839
+ if check_for_render_context :
840
+ check_if_we_should_use_use_task ()
773
841
# we use wraps to make the key of the reactive variable more unique
774
842
# and less likely to mixup during hot reloads
775
843
assert f is not None
@@ -919,7 +987,7 @@ async def square():
919
987
920
988
def wrapper (f ):
921
989
def create_task () -> "Task[[], R]" :
922
- return task (f , prefer_threaded = prefer_threaded )
990
+ return task (f , prefer_threaded = prefer_threaded , check_for_render_context = False )
923
991
924
992
task_instance = solara .use_memo (create_task , dependencies = [])
925
993
# we always update the function so we do not have stale data in the function
0 commit comments