66import inspect
77import logging
88import threading
9+ import warnings
910from enum import Enum
1011import typing
1112from typing import (
@@ -536,6 +537,7 @@ def task(
536537 f : None = None ,
537538 * ,
538539 prefer_threaded : bool = ...,
540+ check_for_render_context : bool = ...,
539541) -> Callable [[Callable [P , R ]], Task [P , R ]]: ...
540542
541543
@@ -544,13 +546,15 @@ def task(
544546 f : Callable [P , Union [Coroutine [Any , Any , R ], R ]],
545547 * ,
546548 prefer_threaded : bool = ...,
549+ check_for_render_context : bool = ...,
547550) -> Task [P , R ]: ...
548551
549552
550553def task (
551554 f : Union [None , Callable [P , Union [Coroutine [Any , Any , R ], R ]]] = None ,
552555 * ,
553556 prefer_threaded : bool = True ,
557+ check_for_render_context : bool = True ,
554558) -> Union [Callable [[Callable [P , R ]], Task [P , R ]], Task [P , R ]]:
555559 """Decorator to turn a function or coroutine function into a task.
556560
@@ -764,12 +768,76 @@ def Page():
764768 This ensures that even when a coroutine functions calls a blocking function the UI is still responsive.
765769 On platform where threads are not supported (like Pyodide / WASM / Emscripten / PyScript), a coroutine
766770 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`.
767773
768774 ```
769775
770776 """
771777
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+
772838 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 ()
773841 # we use wraps to make the key of the reactive variable more unique
774842 # and less likely to mixup during hot reloads
775843 assert f is not None
@@ -919,7 +987,7 @@ async def square():
919987
920988 def wrapper (f ):
921989 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 )
923991
924992 task_instance = solara .use_memo (create_task , dependencies = [])
925993 # we always update the function so we do not have stale data in the function
0 commit comments