Browse Source

button callback remove save parameter & adjust register_callback code

wangweimin 5 years ago
parent
commit
2ba69bfdfb
5 changed files with 91 additions and 60 deletions
  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
 import asyncio
 from datetime import datetime
 from datetime import datetime
-
+from functools import partial
 from pywebio.input import *
 from pywebio.input import *
 from pywebio.ioloop import start_ioloop, run_async
 from pywebio.ioloop import start_ioloop, run_async
 from pywebio.output import *
 from pywebio.output import *
@@ -213,37 +213,39 @@ async def feature_overview():
     幸运的是,PyWebIO还支持输出可以绑定事件的按钮控件,非常适合上述场景的需求。
     幸运的是,PyWebIO还支持输出可以绑定事件的按钮控件,非常适合上述场景的需求。
     上述场景通过按钮控件实现如下:
     上述场景通过按钮控件实现如下:
     ```python
     ```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([
     put_table([
         ['Idx', 'Actions'],
         ['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)
     """, 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([
     put_table([
         ['Idx', 'Actions'],
         ['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
     put_markdown("""这样,你不必等待用户点击某个按钮,而是可以继续往下运行程序,当用户点击了某行中的按钮时,程序会自动调用相应的处理函数\n
     当然,PyWebIO还支持单独的按钮控件:
     当然,PyWebIO还支持单独的按钮控件:
     ```python
     ```python
-    def btn_click(btn_val, save):
+    def btn_click(btn_val):
         put_text("You click btn_val button" % btn_val)
         put_text("You click btn_val button" % btn_val)
     put_buttons(['A', 'B', 'C'], onclick=btn_click)
     put_buttons(['A', 'B', 'C'], onclick=btn_click)
     ```
     ```
     """, strip_indent=4)
     """, strip_indent=4)
 
 
-    def btn_click(btn_val, save):
+    def btn_click(btn_val):
         put_text("You click %s button" % btn_val)
         put_text("You click %s button" % btn_val)
 
 
     put_buttons(['A', 'B', 'C'], onclick=btn_click)
     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 .session.asyncbased import WebIOFuture, AsyncBasedSession, Task
 from .ioloop import run_async
 from .ioloop import run_async
+from functools import partial
 
 
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
@@ -120,40 +121,6 @@ async def input_event_handle(item_valid_funcs, form_valid_funcs, preprocess_func
     return data
     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
     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` 函数的同名参数含义一致
     :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)
     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" ' \
     tpl = '<button type="button" value="{value}" class="btn btn-primary btn-sm" ' \
           'onclick="WebIO.DisplayAreaButtonOnClick(this, \'%s\')">{label}</button>' % callback_id
           'onclick="WebIO.DisplayAreaButtonOnClick(this, \'%s\')">{label}</button>' % callback_id
     btns_html = [tpl.format(**b) for b in btns]
     btns_html = [tpl.format(**b) for b in btns]
     return ' '.join(btns_html)
     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
     :type onclick: Callable or Coroutine
     :param onclick: 按钮点击回调函数. ``onclick`` 可以是普通函数或者协程函数.
     :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 bool mutex_mode: 互斥模式。若为 ``True`` ,则在运行回调函数过程中,无法响应当前按钮组的新点击事件,仅当 `onclick`` 为协程函数时有效
     :param str anchor, before, after: 与 `put_text` 函数的同名参数含义一致
     :param str anchor, before, after: 与 `put_text` 函数的同名参数含义一致
     """
     """
     assert not (before and after), "Parameter 'before' and 'after' cannot be specified at the same time"
     assert not (before and after), "Parameter 'before' and 'after' cannot be specified at the same time"
     btns = _format_button(buttons)
     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,
     _put_content('buttons', callback_id=callback_id, buttons=btns, small=small, anchor=anchor, before=before,
                  after=after)
                  after=after)
 
 

+ 41 - 2
pywebio/session/asyncbased.py

@@ -1,10 +1,12 @@
+import asyncio
+import inspect
 import logging
 import logging
 import sys
 import sys
 import traceback
 import traceback
 from contextlib import contextmanager
 from contextlib import contextmanager
-import asyncio
-from ..utils import random_str
+
 from .base import AbstractSession
 from .base import AbstractSession
+from ..utils import random_str
 
 
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
@@ -152,6 +154,43 @@ class AsyncBasedSession(AbstractSession):
         traceback_msg = ''.join(lines)
         traceback_msg = ''.join(lines)
         put_markdown("发生错误:\n```\n%s\n```" % traceback_msg)
         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):
     def run_async(self, coro_obj):
         self.inactive_coro_instances.append(coro_obj)
         self.inactive_coro_instances.append(coro_obj)
 
 

+ 9 - 0
pywebio/session/base.py

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