浏览代码

support switch session implement

wangweimin 5 年之前
父节点
当前提交
f7de7a6416
共有 5 个文件被更改,包括 125 次插入34 次删除
  1. 18 11
      pywebio/input.py
  2. 40 17
      pywebio/io_ctrl.py
  3. 19 5
      pywebio/platform/tornado.py
  4. 24 1
      pywebio/session/__init__.py
  5. 24 0
      pywebio/utils.py

+ 18 - 11
pywebio/input.py

@@ -56,7 +56,7 @@ def _parse_args(kwargs):
     return kwargs, valid_func
 
 
-def input(label, type=TEXT, *, valid_func=None, name='data', value=None, placeholder=None, required=None,
+def input(label, type=TEXT, *, valid_func=None, name=None, value=None, placeholder=None, required=None,
           readonly=None, disabled=None, help_text=None, **other_html_attrs) -> Coroutine:
     r"""文本输入
 
@@ -72,7 +72,7 @@ def input(label, type=TEXT, *, valid_func=None, name='data', value=None, placeho
                     return 'Too young'
             await input('Input your age', type=NUMBER, valid_func=check_age)
 
-    :param name: 输入框的名字. 与 `input_group` 配合使用,用于在输入组的结果中标识不同输入项
+    :param name: 输入框的名字. 与 `input_group` 配合使用,用于在输入组的结果中标识不同输入项. Note: 在单个输入中,不可以设置该参数!
     :param str value: 输入框的初始值
     :param str placeholder: 输入框的提示内容。提示内容会在输入框未输入值时以浅色字体显示在输入框中
     :param bool required: 当前输入是否为必填项
@@ -97,7 +97,7 @@ def input(label, type=TEXT, *, valid_func=None, name='data', value=None, placeho
     return single_input(item_spec, valid_func, preprocess_func)
 
 
-def textarea(label, rows=6, *, code=None, maxlength=None, minlength=None, valid_func=None, name='data', value=None,
+def textarea(label, rows=6, *, code=None, maxlength=None, minlength=None, valid_func=None, name=None, value=None,
              placeholder=None, required=None, readonly=None, disabled=None, help_text=None, **other_html_attrs):
     r"""文本输入域
 
@@ -140,7 +140,7 @@ def _parse_select_options(options):
     return opts_res
 
 
-def select(label, options, *, multiple=None, valid_func=None, name='data', value=None,
+def select(label, options, *, multiple=None, valid_func=None, name=None, value=None,
            placeholder=None, required=None, readonly=None, disabled=None, help_text=None,
            **other_html_attrs):
     r"""下拉选择框
@@ -163,7 +163,7 @@ def select(label, options, *, multiple=None, valid_func=None, name='data', value
     return single_input(item_spec, valid_func, lambda d: d)
 
 
-def checkbox(label, options, *, inline=None, valid_func=None, name='data', value=None,
+def checkbox(label, options, *, inline=None, valid_func=None, name=None, value=None,
              placeholder=None, required=None, readonly=None, disabled=None, help_text=None, **other_html_attrs):
     r"""勾选选项
 
@@ -179,7 +179,7 @@ def checkbox(label, options, *, inline=None, valid_func=None, name='data', value
     return single_input(item_spec, valid_func, lambda d: d)
 
 
-def radio(label, options, *, inline=None, valid_func=None, name='data', value=None,
+def radio(label, options, *, inline=None, valid_func=None, name=None, value=None,
           placeholder=None, required=None, readonly=None, disabled=None, help_text=None,
           **other_html_attrs):
     r"""单选选项
@@ -220,7 +220,7 @@ def _parse_action_buttons(buttons):
     return act_res
 
 
-def actions(label, buttons, name='data', help_text=None):
+def actions(label, buttons, name=None, help_text=None):
     r"""按钮选项。
     在浏览器上显示为多个按钮,与其他输入元素不同,用户点击按钮选项后会立即将整个表单提交,其他输入元素不同则需要手动点击表单的"提交"按钮。
 
@@ -240,7 +240,7 @@ def actions(label, buttons, name='data', help_text=None):
     return single_input(item_spec, valid_func, lambda d: d)
 
 
-def file_upload(label, accept=None, name='data', placeholder='Choose file', help_text=None, **other_html_attrs):
+def file_upload(label, accept=None, name=None, placeholder='Choose file', help_text=None, **other_html_attrs):
     r"""文件上传。
 
     :param accept: 单值或列表, 表示可接受的文件类型。单值或列表项支持的形式有:
@@ -293,9 +293,16 @@ def input_group(label, inputs, valid_func=None):
     spec_inputs = []
     preprocess_funcs = {}
     item_valid_funcs = {}
-    for single_input_cr in inputs:
-        input_kwargs = dict(single_input_cr.cr_frame.f_locals)  # 拷贝一份,不可以对locals进行修改
-        single_input_cr.close()
+    for single_input_return in inputs:
+        try:
+            single_input_return.send(None)
+        except StopIteration as e:
+            input_kwargs = e.args[0]
+        except AttributeError:
+            input_kwargs = single_input_return
+        else:
+            raise RuntimeError("Can't get kwargs from single input")
+
         input_name = input_kwargs['item_spec']['name']
         preprocess_funcs[input_name] = input_kwargs['preprocess_func']
         item_valid_funcs[input_name] = input_kwargs['valid_func']

+ 40 - 17
pywebio/io_ctrl.py

@@ -1,25 +1,42 @@
-import asyncio
-import inspect
+"""
+输入输出的底层实现函数
+
+
+"""
 import logging
 
-from .session.asyncbased import WebIOFuture, AsyncBasedSession, Task
-from .ioloop import run_async
-from functools import partial
+from .session import get_session_implement, AsyncBasedSession, get_current_task_id, get_current_session
+from .utils import run_as_function, to_coroutine
+from functools import wraps
 
 logger = logging.getLogger(__name__)
 
 
 def send_msg(cmd, spec=None):
-    msg = dict(command=cmd, spec=spec, coro_id=AsyncBasedSession.get_current_task_id())
-    AsyncBasedSession.get_current_session().send_task_message(msg)
+    msg = dict(command=cmd, spec=spec, coro_id=get_current_task_id())
+    get_current_session().send_task_message(msg)
+
+
+def chose_impl(gen_func):
+    @wraps(gen_func)
+    def inner(*args, **kwargs):
+        gen = gen_func(*args, **kwargs)
+        if get_session_implement() == AsyncBasedSession:
+            return to_coroutine(gen)
+        else:
+            return run_as_function(gen)
+
+    return inner
 
 
-async def next_event():
-    res = await AsyncBasedSession.get_current_session().next_client_event()
+@chose_impl
+def next_event():
+    res = yield get_current_session().next_client_event()
     return res
 
 
-async def single_input(item_spec, valid_func, preprocess_func):
+@chose_impl
+def single_input(item_spec, valid_func, preprocess_func):
     """
     Note: 鲁棒性在上层完成
     将单个input构造成input_group,并获取返回值
@@ -27,6 +44,11 @@ async def single_input(item_spec, valid_func, preprocess_func):
     :param valid_func: Not None
     :param preprocess_func: Not None
     """
+    if item_spec.get('name') is None:  # single input
+        item_spec['name'] = 'data'
+    else:  # as input_group item
+        return dict(item_spec=item_spec, valid_func=valid_func, preprocess_func=preprocess_func)
+
     label = item_spec['label']
     name = item_spec['name']
     # todo 是否可以原地修改spec
@@ -35,12 +57,12 @@ async def single_input(item_spec, valid_func, preprocess_func):
     item_spec.setdefault('auto_focus', True)  # 如果没有设置autofocus参数,则开启参数  todo CHECKBOX, RADIO 特殊处理
 
     spec = dict(label=label, inputs=[item_spec])
-
-    data = await input_control(spec, {name: preprocess_func}, {name: valid_func})
+    data = yield input_control(spec, {name: preprocess_func}, {name: valid_func})
     return data[name]
 
 
-async def input_control(spec, preprocess_funcs, item_valid_funcs, form_valid_funcs=None):
+@chose_impl
+def input_control(spec, preprocess_funcs, item_valid_funcs, form_valid_funcs=None):
     """
     发送input命令,监听事件,验证输入项,返回结果
     :param spec:
@@ -51,7 +73,7 @@ async def input_control(spec, preprocess_funcs, item_valid_funcs, form_valid_fun
     """
     send_msg('input_group', spec)
 
-    data = await input_event_handle(item_valid_funcs, form_valid_funcs, preprocess_funcs)
+    data = yield input_event_handle(item_valid_funcs, form_valid_funcs, preprocess_funcs)
 
     send_msg('destroy_form')
     return data
@@ -73,7 +95,8 @@ def check_item(name, data, valid_func, preprocess_func):
     return True
 
 
-async def input_event_handle(item_valid_funcs, form_valid_funcs, preprocess_funcs):
+@chose_impl
+def input_event_handle(item_valid_funcs, form_valid_funcs, preprocess_funcs):
     """
     根据提供的校验函数处理表单事件
     :param item_valid_funcs: map(name -> valid_func)  valid_func 为 None 时,不进行验证
@@ -83,7 +106,7 @@ async def input_event_handle(item_valid_funcs, form_valid_funcs, preprocess_func
     :return:
     """
     while True:
-        event = await next_event()
+        event = yield next_event()
         event_name, event_data = event['event'], event['data']
         if event_name == 'input_event':
             input_event = event_data['event_name']
@@ -122,5 +145,5 @@ async def input_event_handle(item_valid_funcs, form_valid_funcs, preprocess_func
 
 
 def output_register_callback(callback, mutex_mode):
-    coro_id = AsyncBasedSession.get_current_session().register_callback(callback, mutex_mode)
+    coro_id = get_current_session().register_callback(callback, mutex_mode)
     return coro_id

+ 19 - 5
pywebio/platform/tornado.py

@@ -1,11 +1,12 @@
 import json
 
+import asyncio
 import tornado
 import tornado.websocket
-from ..session import AsyncBasedSession
+from ..session import AsyncBasedSession, ThreadBasedWebIOSession, get_session_implement
 
 
-def webio_handler(coro_func, debug=True):
+def webio_handler(task_func, debug=True):
     class WSHandler(tornado.websocket.WebSocketHandler):
 
         def check_origin(self, origin):
@@ -23,16 +24,29 @@ def webio_handler(coro_func, debug=True):
             print("WebSocket opened")
             self.set_nodelay(True)
 
-            self.controller = AsyncBasedSession(coro_func, on_task_message=self.send_msg_to_client,
-                                                on_session_close=self.close)
+            self._close_from_session = False  # 是否从session中关闭连接
+
+            if get_session_implement() is AsyncBasedSession:
+                self.controller = AsyncBasedSession(task_func, on_task_message=self.send_msg_to_client,
+                                                    on_session_close=self.close)
+            else:
+                self.controller = ThreadBasedWebIOSession(task_func, on_task_message=self.send_msg_to_client,
+                                                          on_session_close=self.close_from_session,
+                                                          loop=asyncio.get_event_loop())
+                print('open return, ThreadBasedWebIOSession.thread2session', ThreadBasedWebIOSession.thread2session)
 
         def on_message(self, message):
             # print('on_message', message)
             data = json.loads(message)
             self.controller.send_client_event(data)
 
+        def close_from_session(self):
+            self._close_from_session = True
+            self.close()
+
         def on_close(self):
-            self.controller.close(no_session_close_callback=True)
+            if not self._close_from_session:
+                self.controller.close(no_session_close_callback=True)
             print("WebSocket closed")
 
     return WSHandler

+ 24 - 1
pywebio/session/__init__.py

@@ -1 +1,24 @@
-from .asyncbased import AsyncBasedSession
+from .base import AbstractSession
+from .asyncbased import AsyncBasedSession
+from .threadbased import ThreadBasedWebIOSession
+
+_session_type = AsyncBasedSession
+
+
+def set_session_implement(session_type):
+    global _session_type
+    assert session_type in [ThreadBasedWebIOSession, AsyncBasedSession]
+    _session_type = session_type
+
+
+def get_session_implement():
+    global _session_type
+    return _session_type
+
+
+def get_current_session() -> "AbstractSession":
+    return _session_type.get_current_session()
+
+
+def get_current_task_id():
+    return _session_type.get_current_task_id()

+ 24 - 0
pywebio/utils.py

@@ -11,6 +11,30 @@ def random_str(len=16):
     return ''.join(random.SystemRandom().choice(string.ascii_lowercase + string.digits) for _ in range(len))
 
 
+def run_as_function(gen):
+    res = None
+    while 1:
+        try:
+            res = gen.send(res)
+        except StopIteration as e:
+            if len(e.args) == 1:
+                return e.args[0]
+            return
+
+
+async def to_coroutine(gen):
+    res = None
+    while 1:
+        try:
+            c = gen.send(res)
+            res = await c
+        except StopIteration as e:
+            if len(e.args) == 1:
+                return e.args[0]
+            return
+
+
+
 class LRUDict(OrderedDict):
     """
     Store items in the order the keys were last recent updated.