1212from __future__ import annotations
1313
1414import array
15+ import asyncio
16+ import inspect
17+ import threading
1518import warnings
1619from typing import Any , Callable , Optional , TypeAlias
1720from ctypes import *
@@ -1055,7 +1058,7 @@ def set_file_handler(self, handler: Callable[[str], Optional[str]]) -> None:
10551058 None
10561059
10571060 Example:
1058- def my_handler(filename: str) -> Optional[str]:
1061+ async def my_handler(filename: str) -> Optional[str]:
10591062 response_body = "Hello, World!"
10601063 response_headers = (
10611064 "HTTP/1.1 200 OK\r \n "
@@ -1072,26 +1075,33 @@ def my_handler(filename: str) -> Optional[str]:
10721075 _raw .webui_set_file_handler .argtypes = [c_size_t , callback_handler_type ]
10731076 _raw .webui_set_file_handler .restype = None
10741077
1078+ # Force async mode so WebUI waits for webui_interface_set_response_file_handler
1079+ # instead of the C callback return value. This lets us safely run the user
1080+ # handler in a background thread (supporting both sync and async handlers).
1081+ _raw .webui_set_config (_raw .WebuiConfig .asynchronous_response , True )
1082+
10751083 self ._buffers .clear ()
10761084
1077- def _c_handler (filename_ptr , length_ptr ):
1078- path = filename_ptr .decode ("utf-8" )
1079- response_str = handler (path )
1085+ window_id = self ._window
10801086
1081- # None, tells WebUI to look for files elsewhere
1087+ def _respond ( response_str ):
10821088 if response_str is None :
1083- length_ptr [0 ] = 0
1084- return 0
1085-
1089+ _raw .webui_interface_set_response_file_handler (window_id , None , 0 )
1090+ return
10861091 data = response_str .encode ("latin-1" )
1087- length_ptr [0 ] = len (data )
1088-
1089- # allocate and pin
10901092 buf = create_string_buffer (data )
10911093 self ._buffers .append (buf )
1094+ _raw .webui_interface_set_response_file_handler (window_id , buf , len (data ))
10921095
1093- # return the raw address as an integer
1094- return addressof (buf )
1096+ def _c_handler (filename_ptr , _ ):
1097+ path = filename_ptr .decode ("utf-8" )
1098+ if inspect .iscoroutinefunction (handler ):
1099+ async def _run ():
1100+ _respond (await handler (path ))
1101+ asyncio .run (_run ())
1102+ else :
1103+ threading .Thread (target = lambda : _respond (handler (path )), daemon = True ).start ()
1104+ return 0
10951105
10961106 # Keep a reference so it doesn't get garbage collected
10971107 self ._file_handler_cb = callback_handler_type (_c_handler )
@@ -1112,7 +1122,7 @@ def set_file_handler_window(self, handler: Callable[[int, str], Optional[str]])
11121122 string, or None to let WebUI serve the file normally.
11131123
11141124 Example:
1115- def my_handler(window_id: int, filename: str) -> Optional[str]:
1125+ async def my_handler(window_id: int, filename: str) -> Optional[str]:
11161126 response_body = "Hello, World!"
11171127 response_headers = (
11181128 "HTTP/1.1 200 OK\r \n "
@@ -1128,23 +1138,32 @@ def my_handler(window_id: int, filename: str) -> Optional[str]:
11281138 _raw .webui_set_file_handler_window .argtypes = [c_size_t , callback_handler_type ]
11291139 _raw .webui_set_file_handler_window .restype = None
11301140
1131- self ._buffers .clear ()
1141+ # Force async mode so WebUI waits for webui_interface_set_response_file_handler
1142+ # instead of the C callback return value. This lets us safely run the user
1143+ # handler in a background thread (supporting both sync and async handlers).
1144+ _raw .webui_set_config (_raw .WebuiConfig .asynchronous_response , True )
11321145
1133- def _c_handler (window_id , filename_ptr , length_ptr ):
1134- path = filename_ptr .decode ("utf-8" ) if filename_ptr else ""
1135- response_str = handler (int (window_id ), path )
1146+ self ._buffers .clear ()
11361147
1148+ def _respond (wid , response_str ):
11371149 if response_str is None :
1138- length_ptr [0 ] = 0
1139- return 0
1140-
1150+ _raw .webui_interface_set_response_file_handler (wid , None , 0 )
1151+ return
11411152 data = response_str .encode ("latin-1" )
1142- length_ptr [0 ] = len (data )
1143-
11441153 buf = create_string_buffer (data )
11451154 self ._buffers .append (buf )
1155+ _raw .webui_interface_set_response_file_handler (wid , buf , len (data ))
11461156
1147- return addressof (buf )
1157+ def _c_handler (window_id , filename_ptr , _ ):
1158+ path = filename_ptr .decode ("utf-8" ) if filename_ptr else ""
1159+ wid = int (window_id )
1160+ if inspect .iscoroutinefunction (handler ):
1161+ async def _run ():
1162+ _respond (wid , await handler (wid , path ))
1163+ asyncio .run (_run ())
1164+ else :
1165+ threading .Thread (target = lambda : _respond (wid , handler (wid , path )), daemon = True ).start ()
1166+ return 0
11481167
11491168 self ._file_handler_cb = callback_handler_type (_c_handler )
11501169 _raw .webui_set_file_handler_window (self ._window , self ._file_handler_cb )
0 commit comments