Browse Source

button callback remove save parameter & adjust register_callback code

wangweimin 5 năm trước cách đây
mục cha
commit
2ba69bfdfb
5 tập tin đã thay đổi với 91 bổ sung60 xóa
  1. 15 13
      pywebio/demos/overview-zh.py
  2. 4 37
      pywebio/io_ctrl.py
  3. 22 8
      pywebio/output.py
  4. 41 2
      pywebio/session/asyncbased.py
  5. 9 0
      pywebio/session/base.py

+ 15 - 13
pywebio/demos/overview-zh.py

@@ -4,7 +4,7 @@
 
 import asyncio
 from datetime import datetime
-
+from functools import partial
 from pywebio.input import *
 from pywebio.ioloop import start_ioloop, run_async
 from pywebio.output import *
@@ -213,37 +213,39 @@ async def feature_overview():
     幸运的是,PyWebIO还支持输出可以绑定事件的按钮控件,非常适合上述场景的需求。
     上述场景通过按钮控件实现如下:
     ```python
-    def edit_row(choice, save):
-        put_text("You click %s button ar row %s" % (choice, save))
+    from functools import partial
+    
+    def edit_row(choice, row):
+        put_text("You click %s button ar row %s" % (choice, row))
 
     put_table([
         ['Idx', 'Actions'],
-        [1, td_buttons(['edit', 'delete'], onclick=edit_row, save=1)],
-        [2, td_buttons(['edit', 'delete'], onclick=edit_row, save=2)],
-        [3, td_buttons(['edit', 'delete'], onclick=edit_row, save=3)],
+        [1, td_buttons(['edit', 'delete'], onclick=partial(edit_row, row=1))],
+        [2, td_buttons(['edit', 'delete'], onclick=partial(edit_row, row=2))],
+        [3, td_buttons(['edit', 'delete'], onclick=partial(edit_row, row=3))],
     ])
     ```
     """, strip_indent=4)
 
-    def edit_row(choice, save):
-        put_text("You click %s button ar row %s" % (choice, save))
+    def edit_row(choice, row):
+        put_text("You click %s button ar row %s" % (choice, row))
 
     put_table([
         ['Idx', 'Actions'],
-        [1, td_buttons(['edit', 'delete'], onclick=edit_row, save=1)],
-        [2, td_buttons(['edit', 'delete'], onclick=edit_row, save=2)],
-        [3, td_buttons(['edit', 'delete'], onclick=edit_row, save=3)],
+        [1, td_buttons(['edit', 'delete'], onclick=partial(edit_row, row=1))],
+        [2, td_buttons(['edit', 'delete'], onclick=partial(edit_row, row=2))],
+        [3, td_buttons(['edit', 'delete'], onclick=partial(edit_row, row=3))],
     ])
     put_markdown("""这样,你不必等待用户点击某个按钮,而是可以继续往下运行程序,当用户点击了某行中的按钮时,程序会自动调用相应的处理函数\n
     当然,PyWebIO还支持单独的按钮控件:
     ```python
-    def btn_click(btn_val, save):
+    def btn_click(btn_val):
         put_text("You click btn_val button" % btn_val)
     put_buttons(['A', 'B', 'C'], onclick=btn_click)
     ```
     """, strip_indent=4)
 
-    def btn_click(btn_val, save):
+    def btn_click(btn_val):
         put_text("You click %s button" % btn_val)
 
     put_buttons(['A', 'B', 'C'], onclick=btn_click)

+ 4 - 37
pywebio/io_ctrl.py

@@ -4,6 +4,7 @@ import logging
 
 from .session.asyncbased import WebIOFuture, AsyncBasedSession, Task
 from .ioloop import run_async
+from functools import partial
 
 logger = logging.getLogger(__name__)
 
@@ -120,40 +121,6 @@ async def input_event_handle(item_valid_funcs, form_valid_funcs, preprocess_func
     return data
 
 
-def output_register_callback(callback, save, mutex_mode):
-    """
-    为输出区显示的控件注册回调函数
-
-    原理:
-        向框架注册一个新协程,在协程内对回调函数进行调用 callback(widget_data, save)
-        协程会在用户与控件交互时触发
-
-    :return: 协程id
-    """
-
-    async def callback_coro():
-        while True:
-            event = await next_event()
-            assert event['event'] == 'callback'
-            coro = None
-            if asyncio.iscoroutinefunction(callback):
-                coro = callback(event['data'], save)
-            elif inspect.isgeneratorfunction(callback):
-                coro = asyncio.coroutine(callback)(save, event['data'])
-            else:
-                try:
-                    callback(event['data'], save)
-                except:
-                    AsyncBasedSession.get_current_session().on_task_exception()
-
-            if coro is not None:
-                if mutex_mode:
-                    await coro
-                else:
-                    run_async(coro)
-
-    callback_task = Task(callback_coro(), AsyncBasedSession.get_current_session())
-    callback_task.coro.send(None)  # 激活,Non't callback.step() ,导致嵌套调用step  todo 与inactive_coro_instances整合
-    AsyncBasedSession.get_current_session().coros[callback_task.coro_id] = callback_task
-
-    return callback_task.coro_id
+def output_register_callback(callback, mutex_mode):
+    coro_id = AsyncBasedSession.get_current_session().register_callback(callback, mutex_mode)
+    return coro_id

+ 22 - 8
pywebio/output.py

@@ -250,21 +250,36 @@ def _format_button(buttons):
     return btns
 
 
-def td_buttons(buttons, onclick, save=None, mutex_mode=False):
+def td_buttons(buttons, onclick, mutex_mode=False):
     """
     在表格中显示一组按钮
 
     :param str buttons, onclick, save: 与 `put_buttons` 函数的同名参数含义一致
+
+    .. _td_buttons-code-sample:
+    使用示例::
+
+        from functools import partial
+
+        def edit_row(choice, row):
+            put_text("You click %s button ar row %s" % (choice, save))
+
+        put_table([
+            ['Idx', 'Actions'],
+            ['1', td_buttons(['edit', 'delete'], onclick=edit_row, save=1)],
+            ['2', td_buttons(['edit', 'delete'], onclick=edit_row, save=2)],
+            ['3', td_buttons(['edit', 'delete'], onclick=edit_row, save=3)],
+        ])
     """
     btns = _format_button(buttons)
-    callback_id = output_register_callback(onclick, save, mutex_mode)
+    callback_id = output_register_callback(onclick, mutex_mode)
     tpl = '<button type="button" value="{value}" class="btn btn-primary btn-sm" ' \
           'onclick="WebIO.DisplayAreaButtonOnClick(this, \'%s\')">{label}</button>' % callback_id
     btns_html = [tpl.format(**b) for b in btns]
     return ' '.join(btns_html)
 
 
-def put_buttons(buttons, onclick, small=False, save=None, mutex_mode=False, anchor=None, before=None, after=None):
+def put_buttons(buttons, onclick, small=False, mutex_mode=False, anchor=None, before=None, after=None):
     """
     输出一组按钮
 
@@ -276,16 +291,15 @@ def put_buttons(buttons, onclick, small=False, save=None, mutex_mode=False, anch
 
     :type onclick: Callable or Coroutine
     :param onclick: 按钮点击回调函数. ``onclick`` 可以是普通函数或者协程函数.
-        函数签名为 ``onclick(btn_value, save)``.
-        当按钮组中的按钮被点击时,``onclick`` 被调用,``onclick`` 接收两个参数,``btn_value``为被点击的按钮的 ``value`` 值,
-        ``save`` 为 `td_buttons` 的 ``save`` 参数值
-    :param any save: ``save`` 内容将传入 ``onclick`` 回调函数的第二个参数
+        函数签名为 ``onclick(btn_value)``.
+        当按钮组中的按钮被点击时,``onclick`` 被调用,并传入被点击的按钮的 ``value`` 值。
+        可以使用 ``functools.partial`` 来在 ``onclick`` 中保存更多上下文信息,见 `td_buttons` :ref:`代码示例 <td_buttons-code-sample>` 。
     :param bool mutex_mode: 互斥模式。若为 ``True`` ,则在运行回调函数过程中,无法响应当前按钮组的新点击事件,仅当 `onclick`` 为协程函数时有效
     :param str anchor, before, after: 与 `put_text` 函数的同名参数含义一致
     """
     assert not (before and after), "Parameter 'before' and 'after' cannot be specified at the same time"
     btns = _format_button(buttons)
-    callback_id = output_register_callback(onclick, save, mutex_mode)
+    callback_id = output_register_callback(onclick, mutex_mode)
     _put_content('buttons', callback_id=callback_id, buttons=btns, small=small, anchor=anchor, before=before,
                  after=after)
 

+ 41 - 2
pywebio/session/asyncbased.py

@@ -1,10 +1,12 @@
+import asyncio
+import inspect
 import logging
 import sys
 import traceback
 from contextlib import contextmanager
-import asyncio
-from ..utils import random_str
+
 from .base import AbstractSession
+from ..utils import random_str
 
 logger = logging.getLogger(__name__)
 
@@ -152,6 +154,43 @@ class AsyncBasedSession(AbstractSession):
         traceback_msg = ''.join(lines)
         put_markdown("发生错误:\n```\n%s\n```" % traceback_msg)
 
+    def register_callback(self, callback, mutex_mode):
+        """ 向Session注册一个回调函数,返回回调id
+
+        :type callback: Callable or Coroutine
+        :param callback: 回调函数. 可以是普通函数或者协程函数. 函数签名为 ``callback(data)``.
+        :return str: 回调id.
+            AsyncBasedSession保证当收到前端发送的事件消息 ``{event: "callback",coro_id: 回调id, data:...}`` 时,
+            ``callback`` 回调函数被执行, 并传入事件消息中的 ``data`` 字段值作为参数
+        """
+
+        async def callback_coro():
+            while True:
+                event = await self.next_client_event()
+                assert event['event'] == 'callback'
+                coro = None
+                if asyncio.iscoroutinefunction(callback):
+                    coro = callback(event['data'])
+                elif inspect.isgeneratorfunction(callback):
+                    coro = asyncio.coroutine(callback)(event['data'])
+                else:
+                    try:
+                        callback(event['data'])
+                    except:
+                        AsyncBasedSession.get_current_session().on_task_exception()
+
+                if coro is not None:
+                    if mutex_mode:
+                        await coro
+                    else:
+                        self.run_async(coro)
+
+        callback_task = Task(callback_coro(), AsyncBasedSession.get_current_session())
+        callback_task.coro.send(None)  # 激活,Non't callback.step() ,导致嵌套调用step  todo 与inactive_coro_instances整合
+        AsyncBasedSession.get_current_session().coros[callback_task.coro_id] = callback_task
+
+        return callback_task.coro_id
+
     def run_async(self, coro_obj):
         self.inactive_coro_instances.append(coro_obj)
 

+ 9 - 0
pywebio/session/base.py

@@ -7,6 +7,8 @@ class AbstractSession:
         send_task_message
         next_client_event
         on_task_exception
+        register_callback
+
 
     由Backend调用:
         send_client_event
@@ -52,3 +54,10 @@ class AbstractSession:
     def on_task_exception(self):
         raise NotImplementedError
 
+    def register_callback(self, callback, **options):
+        """ 向Session注册一个回调函数,返回回调id
+
+        Session需要保证当收到前端发送的事件消息 ``{event: "callback",coro_id: 回调id, data:...}`` 时,
+        ``callback`` 回调函数被执行, 并传入事件消息中的 ``data`` 字段值作为参数
+        """
+        raise NotImplementedError