Skip to content

Commit e9a2933

Browse files
committed
feat: auto hold session as needed
1 parent 7512fa1 commit e9a2933

File tree

4 files changed

+119
-5
lines changed

4 files changed

+119
-5
lines changed

pywebio/session/base.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class Session:
2929
next_client_event
3030
on_task_exception
3131
register_callback
32+
need_keep_alive
3233
3334
defer_call
3435
@@ -159,6 +160,9 @@ def defer_call(self, func):
159160
"""设置会话结束时调用的函数。可以用于资源清理。"""
160161
self.deferred_functions.append(func)
161162

163+
def need_keep_alive(self) -> bool:
164+
raise NotImplementedError
165+
162166

163167
def get_session_info_from_headers(headers):
164168
"""从Http请求头中获取会话信息

pywebio/session/coroutinebased.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,22 @@ def __init__(self, target, session_info, on_task_command=None, on_session_close=
8787

8888
self._closed = False
8989

90+
self._need_keep_alive = False
91+
9092
# 当前会话未结束运行(已创建和正在运行的)的协程数量。当 _alive_coro_cnt 变为 0 时,会话结束。
9193
self._alive_coro_cnt = 1
9294

93-
main_task = Task(target(), session=self, on_coro_stop=self._on_task_finish)
95+
main_task = Task(self._start_main_task(target), session=self, on_coro_stop=self._on_task_finish)
9496
self.coros[main_task.coro_id] = main_task
9597

9698
self._step_task(main_task)
9799

100+
async def _start_main_task(self, target):
101+
await target()
102+
if self.need_keep_alive():
103+
from ..session import hold
104+
await hold()
105+
98106
def _step_task(self, task, result=None):
99107
asyncio.get_event_loop().call_soon_threadsafe(partial(task.step, result))
100108

@@ -203,6 +211,8 @@ async def callback_coro():
203211
callback_task.coro.send(None)
204212
cls.get_current_session().coros[callback_task.coro_id] = callback_task
205213

214+
self._need_keep_alive = True
215+
206216
return callback_task.coro_id
207217

208218
def run_async(self, coro_obj):
@@ -227,6 +237,9 @@ async def run_asyncio_coroutine(self, coro_obj):
227237
res = await WebIOFuture(coro=coro_obj)
228238
return res
229239

240+
def need_keep_alive(self) -> bool:
241+
return self._need_keep_alive
242+
230243

231244
class TaskHandler:
232245
"""The handler of coroutine task

pywebio/session/threadbased.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,15 @@ def main_task(target):
9191
for t in self.threads:
9292
if t.is_alive() and t is not threading.current_thread():
9393
t.join()
94-
try:
95-
self.send_task_command(dict(command='close_session'))
96-
except SessionClosedException:
97-
pass
94+
95+
if self.need_keep_alive():
96+
from ..session import hold
97+
hold()
98+
else:
99+
try:
100+
self.send_task_command(dict(command='close_session'))
101+
except SessionClosedException:
102+
pass
98103
self._trigger_close_event()
99104
self.close()
100105

@@ -281,6 +286,10 @@ def register_thread(self, t: threading.Thread):
281286
event_mq = queue.Queue(maxsize=self.event_mq_maxsize) # 线程内的用户事件队列
282287
self.task_mqs[self._get_task_id(t)] = event_mq
283288

289+
def need_keep_alive(self) -> bool:
290+
# if callback thread is activated, then the session need to keep alive
291+
return self.callback_thread is not None
292+
284293

285294
class ScriptModeSession(ThreadBasedSession):
286295
"""Script mode的会话实现"""

test/1.basic.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,92 @@ def target():
1919
template.basic_output()
2020
template.background_output()
2121

22+
def check_item(data):
23+
pass
24+
25+
input_group('Input group', [
26+
input('Text', type=TEXT, datalist=['data-%s' % i for i in range(10)], name='text',
27+
required=True, help_text='required=True', validate=check_item),
28+
input('Number', type=NUMBER, value="42", name='number', validate=check_item),
29+
input('Float', type=FLOAT, name='float', validate=check_item),
30+
input('Password', type=PASSWORD, name='password', validate=check_item),
31+
32+
textarea('Textarea', rows=3, maxlength=20, name='textarea',
33+
help_text='rows=3, maxlength=20', validate=check_item),
34+
35+
textarea('Code', name='code', code={
36+
'lineNumbers': False,
37+
'indentUnit': 2,
38+
}, value='import something\n# Write your python code', validate=check_item),
39+
40+
select('select-multiple', [
41+
{'label': '标签0,selected', 'value': '0', 'selected': True},
42+
{'label': '标签1,disabled', 'value': '1', 'disabled': True},
43+
('标签2,selected', '2', True),
44+
('标签3', '3'),
45+
('标签4,disabled', '4', False, True),
46+
'标签5,selected',
47+
], name='select-multiple', multiple=True, value=['标签5,selected'], required=True,
48+
help_text='required至少选择一项', validate=check_item),
49+
50+
select('select', [
51+
{'label': '标签0', 'value': '0', 'selected': False},
52+
{'label': '标签1,disabled', 'value': '1', 'disabled': True},
53+
('标签2', '2', False),
54+
('标签3', '3'),
55+
('标签4,disabled', '4', False, True),
56+
'标签5,selected',
57+
], name='select', value=['标签5,selected'], validate=check_item),
58+
59+
checkbox('checkbox-inline', [
60+
{'label': '标签0,selected', 'value': '0', 'selected': False},
61+
{'label': '标签1,disabled', 'value': '1', 'disabled': True},
62+
('标签2,selected', '2', True),
63+
('标签3', '3'),
64+
('标签4,disabled', '4', False, True),
65+
'标签5,selected',
66+
], inline=True, name='checkbox-inline', value=['标签5,selected', '标签0', '标签0,selected'], validate=check_item),
67+
68+
checkbox('checkbox', [
69+
{'label': '标签0,selected', 'value': '0', 'selected': True},
70+
{'label': '标签1,disabled', 'value': '1', 'disabled': True},
71+
('标签2,selected', '2', True),
72+
('标签3', '3'),
73+
('标签4,disabled', '4', False, True),
74+
'标签5',
75+
], name='checkbox', validate=check_item),
76+
77+
radio('radio-inline', [
78+
{'label': '标签0', 'value': '0', 'selected': False},
79+
{'label': '标签1,disabled', 'value': '1', 'disabled': True},
80+
('标签2', '2', False),
81+
('标签3', '3'),
82+
('标签4,disabled', '4', False, True),
83+
'标签5,selected',
84+
], inline=True, name='radio-inline', value='标签5,selected', validate=check_item),
85+
86+
radio('radio', [
87+
{'label': '标签0', 'value': '0', 'selected': False},
88+
{'label': '标签1,disabled', 'value': '1', 'disabled': True},
89+
('标签2', '2', False),
90+
('标签3', '3'),
91+
('标签4,disabled', '4', False, True),
92+
'标签5,selected',
93+
], inline=False, name='radio', value='标签5,selected', validate=check_item),
94+
95+
file_upload('file_upload', name='file_upload', max_size='10m'),
96+
97+
actions('actions', [
98+
{'label': '提交', 'value': 'submit'},
99+
('提交2', 'submit2'),
100+
'提交3',
101+
{'label': 'disabled', 'disabled': True},
102+
('重置', 'reset', 'reset'),
103+
{'label': '取消', 'type': 'cancel'},
104+
], name='actions', help_text='actions'),
105+
106+
] )
107+
22108
run_as_function(template.basic_input())
23109
actions(buttons=['Continue'])
24110
template.background_input()
@@ -32,6 +118,8 @@ def test(server_proc: subprocess.Popen, browser: Chrome):
32118
time.sleep(1)
33119
template.save_output(browser, '1.basic.html')
34120

121+
browser.get('http://localhost:8080/') # to close current session
122+
35123
template.test_defer_call()
36124

37125

0 commit comments

Comments
 (0)