Skip to content

Commit b01f606

Browse files
committed
Support Asynchronous FileHandler
Info: webui-dev/webui#697
1 parent fe797a6 commit b01f606

File tree

1 file changed

+43
-24
lines changed

1 file changed

+43
-24
lines changed

PyPI/Package/src/webui/webui.py

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
from __future__ import annotations
1313

1414
import array
15+
import asyncio
16+
import inspect
17+
import threading
1518
import warnings
1619
from typing import Any, Callable, Optional, TypeAlias
1720
from 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

Comments
 (0)