Browse Source

Add Type Hints to all public funcs (#501)

* style: fix some trailing space and over-indented

* add type hints to `input.py`

* add type hints to `output.py`

* add type hints to `pin.py`

* fixed unsorted imports

* add type hitns to func `start_server()`

* remove some comment which prevent type check

* fix wrong type hints for position argument

* fix some type check error of internal modules

* fix wrong format of import

* add Type Hint to `container_scope` arg

Co-authored-by: WangWeimin <wang0.618@qq.com>

* Update pywebio/output.py

Co-authored-by: WangWeimin <wang0.618@qq.com>

* Update pywebio/output.py

Co-authored-by: WangWeimin <wang0.618@qq.com>

* Update pywebio/output.py

Co-authored-by: WangWeimin <wang0.618@qq.com>

* Update pywebio/output.py

Co-authored-by: WangWeimin <wang0.618@qq.com>

* Update pywebio/output.py

Co-authored-by: WangWeimin <wang0.618@qq.com>

* Update pywebio/pin.py

Co-authored-by: WangWeimin <wang0.618@qq.com>

* Update pywebio/input.py

Co-authored-by: WangWeimin <wang0.618@qq.com>

* fix wrong type of Type Hints

Co-authored-by: WangWeimin <wang0.618@qq.com>

* fix typo

Co-authored-by: WangWeimin <wang0.618@qq.com>

* Update pywebio/output.py

Co-authored-by: WangWeimin <wang0.618@qq.com>

* fix wrong type of `content` arg

Co-authored-by: WangWeimin <wang0.618@qq.com>

* fix wrong type of Type Hints

Co-authored-by: WangWeimin <wang0.618@qq.com>

* Update pywebio/output.py

Co-authored-by: WangWeimin <wang0.618@qq.com>

* Update pywebio/output.py

Co-authored-by: WangWeimin <wang0.618@qq.com>

* Update pywebio/output.py

Co-authored-by: WangWeimin <wang0.618@qq.com>

* Update pywebio/output.py

Co-authored-by: WangWeimin <wang0.618@qq.com>

Co-authored-by: WangWeimin <wang0.618@qq.com>
叶子 2 years ago
parent
commit
25775609ed

+ 33 - 24
pywebio/input.py

@@ -76,11 +76,12 @@ import copy
 import logging
 import logging
 import os.path
 import os.path
 from collections.abc import Mapping
 from collections.abc import Mapping
+from typing import Any, Callable, Dict, List, Optional, Tuple, Union
 
 
-from .io_ctrl import single_input, input_control, output_register_callback, send_msg, single_input_kwargs
+from .io_ctrl import input_control, output_register_callback, send_msg, single_input, single_input_kwargs
 from .platform import page as platform_setting
 from .platform import page as platform_setting
 from .session import get_current_session, get_current_task_id
 from .session import get_current_session, get_current_task_id
-from .utils import Setter, parse_file_size, check_dom_name_value
+from .utils import Setter, check_dom_name_value, parse_file_size
 
 
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
@@ -99,9 +100,10 @@ RADIO = 'radio'
 SELECT = 'select'
 SELECT = 'select'
 TEXTAREA = 'textarea'
 TEXTAREA = 'textarea'
 
 
-__all__ = ['TEXT', 'NUMBER', 'FLOAT', 'PASSWORD', 'URL', 'DATE', 'TIME', 'COLOR', 'DATETIME_LOCAL', 'input', 'textarea',
-           'select',
-           'checkbox', 'radio', 'actions', 'file_upload', 'slider', 'input_group', 'input_update']
+__all__ = ['TEXT', 'NUMBER', 'FLOAT', 'PASSWORD', 'URL', 'DATE',
+           'TIME', 'COLOR', 'DATETIME_LOCAL', 'input', 'textarea',
+           'select', 'checkbox', 'radio', 'actions', 'file_upload',
+           'slider', 'input_group', 'input_update']
 
 
 
 
 def _parse_args(kwargs, excludes=()):
 def _parse_args(kwargs, excludes=()):
@@ -131,8 +133,9 @@ def _parse_args(kwargs, excludes=()):
     return kwargs, valid_func, onchange_func
     return kwargs, valid_func, onchange_func
 
 
 
 
-def input(label='', type=TEXT, *, validate=None, name=None, value=None, action=None, onchange=None, placeholder=None,
-          required=None, readonly=None, datalist=None, help_text=None, **other_html_attrs):
+def input(label: str = '', type: str = TEXT, *, validate: Callable[[Any], Optional[str]] = None, name: str = None, value: str = None,
+          action: Tuple[str, Callable[[Callable], None]] = None, onchange: Callable[[Any], None] = None, placeholder: str = None, required: bool = None,
+          readonly: bool = None, datalist: List[str] = None, help_text: str = None, **other_html_attrs):
     r"""Text input
     r"""Text input
 
 
     :param str label: Label of input field.
     :param str label: Label of input field.
@@ -260,8 +263,9 @@ def input(label='', type=TEXT, *, validate=None, name=None, value=None, action=N
     return single_input(item_spec, valid_func, preprocess_func, onchange_func)
     return single_input(item_spec, valid_func, preprocess_func, onchange_func)
 
 
 
 
-def textarea(label='', *, rows=6, code=None, maxlength=None, minlength=None, validate=None, name=None, value=None,
-             onchange=None, placeholder=None, required=None, readonly=None, help_text=None, **other_html_attrs):
+def textarea(label: str = '', *, rows: int = 6, code: Union[bool, Dict] = None, maxlength: int = None, minlength: int = None,
+             validate: Callable[[Any], Optional[str]] = None, name: str = None, value: str = None, onchange: Callable[[Any], None] = None,
+             placeholder: str = None, required: bool = None, readonly: bool = None, help_text: str = None, **other_html_attrs):
     r"""Text input area (multi-line text input)
     r"""Text input area (multi-line text input)
 
 
     :param int rows: The number of visible text lines for the input area. Scroll bar will be used when content exceeds.
     :param int rows: The number of visible text lines for the input area. Scroll bar will be used when content exceeds.
@@ -326,8 +330,9 @@ def _set_options_selected(options, value):
     return options
     return options
 
 
 
 
-def select(label='', options=None, *, multiple=None, validate=None, name=None, value=None, onchange=None, required=None,
-           help_text=None, **other_html_attrs):
+def select(label: str = '', options: List[Union[Dict[str, Any], Tuple, List, str]] = None, *, multiple: bool = None, validate: Callable[[Any], Optional[str]] = None,
+           name: str = None, value: Union[List, str] = None, onchange: Callable[[Any], None] = None, required: bool = None,
+           help_text: str = None, **other_html_attrs):
     r"""Drop-down selection
     r"""Drop-down selection
 
 
     By default, only one option can be selected at a time, you can set ``multiple`` parameter to enable multiple selection.
     By default, only one option can be selected at a time, you can set ``multiple`` parameter to enable multiple selection.
@@ -371,8 +376,9 @@ def select(label='', options=None, *, multiple=None, validate=None, name=None, v
     return single_input(item_spec, valid_func=valid_func, preprocess_func=lambda d: d, onchange_func=onchange_func)
     return single_input(item_spec, valid_func=valid_func, preprocess_func=lambda d: d, onchange_func=onchange_func)
 
 
 
 
-def checkbox(label='', options=None, *, inline=None, validate=None, name=None, value=None, onchange=None,
-             help_text=None, **other_html_attrs):
+def checkbox(label: str = '', options: List[Union[Dict[str, Any], Tuple, List, str]] = None, *, inline: bool = None, validate: Callable[[Any], Optional[str]] = None,
+             name: str = None, value: List = None, onchange: Callable[[Any], None] = None, help_text: str = None,
+             **other_html_attrs):
     r"""A group of check box that allowing single values to be selected/deselected.
     r"""A group of check box that allowing single values to be selected/deselected.
 
 
     :param list options: List of options. The format is the same as the ``options`` parameter of the `select()` function
     :param list options: List of options. The format is the same as the ``options`` parameter of the `select()` function
@@ -393,8 +399,9 @@ def checkbox(label='', options=None, *, inline=None, validate=None, name=None, v
     return single_input(item_spec, valid_func, lambda d: d, onchange_func)
     return single_input(item_spec, valid_func, lambda d: d, onchange_func)
 
 
 
 
-def radio(label='', options=None, *, inline=None, validate=None, name=None, value=None, onchange=None, required=None,
-          help_text=None, **other_html_attrs):
+def radio(label: str = '', options: List[Union[Dict[str, Any], Tuple, List, str]] = None, *, inline: bool = None, validate: Callable[[Any], Optional[str]] = None,
+          name: str = None, value: str = None, onchange: Callable[[Any], None] = None, required: bool = None,
+          help_text: str = None, **other_html_attrs):
     r"""A group of radio button. Only a single button can be selected.
     r"""A group of radio button. Only a single button can be selected.
 
 
     :param list options: List of options. The format is the same as the ``options`` parameter of the `select()` function
     :param list options: List of options. The format is the same as the ``options`` parameter of the `select()` function
@@ -457,7 +464,7 @@ def _parse_action_buttons(buttons):
     return act_res
     return act_res
 
 
 
 
-def actions(label='', buttons=None, name=None, help_text=None):
+def actions(label: str = '', buttons: List[Union[Dict[str, Any], Tuple, List, str]] = None, name: str = None, help_text: str = None):
     r"""Actions selection
     r"""Actions selection
 
 
     It is displayed as a group of buttons on the page. After the user clicks the button of it,
     It is displayed as a group of buttons on the page. After the user clicks the button of it,
@@ -557,8 +564,9 @@ def actions(label='', buttons=None, name=None, help_text=None):
     return single_input(item_spec, valid_func, lambda d: d, onchange_func)
     return single_input(item_spec, valid_func, lambda d: d, onchange_func)
 
 
 
 
-def file_upload(label='', accept=None, name=None, placeholder='Choose file', multiple=False, max_size=0,
-                max_total_size=0, required=None, help_text=None, **other_html_attrs):
+def file_upload(label: str = '', accept: Union[List, str] = None, name: str = None, placeholder: str = 'Choose file',
+                multiple: bool = False, max_size: Union[int, str] = 0, max_total_size: Union[int, str] = 0,
+                required: bool = None, help_text: str = None, **other_html_attrs):
     r"""File uploading
     r"""File uploading
 
 
     :param accept: Single value or list, indicating acceptable file types. The available formats of file types are:
     :param accept: Single value or list, indicating acceptable file types. The available formats of file types are:
@@ -590,14 +598,14 @@ def file_upload(label='', accept=None, name=None, placeholder='Choose file', mul
             'mime_type': MIME type of the file,
             'mime_type': MIME type of the file,
             'last_modified': Last modified time (timestamp) of the file
             'last_modified': Last modified time (timestamp) of the file
         }
         }
-       
+
        If there is no file uploaded, return ``None``.
        If there is no file uploaded, return ``None``.
 
 
        When ``multiple=True``, a list is returned. The format of the list item is the same as the return value when ``multiple=False`` above.
        When ``multiple=True``, a list is returned. The format of the list item is the same as the return value when ``multiple=False`` above.
        If the user does not upload a file, an empty list is returned.
        If the user does not upload a file, an empty list is returned.
 
 
     .. note::
     .. note::
-    
+
         If uploading large files, please pay attention to the file upload size limit setting of the web framework.
         If uploading large files, please pay attention to the file upload size limit setting of the web framework.
         When using :func:`start_server() <pywebio.platform.tornado.start_server>` or
         When using :func:`start_server() <pywebio.platform.tornado.start_server>` or
         :func:`path_deploy() <pywebio.platform.path_deploy>` to start the PyWebIO application,
         :func:`path_deploy() <pywebio.platform.path_deploy>` to start the PyWebIO application,
@@ -639,8 +647,9 @@ def file_upload(label='', accept=None, name=None, placeholder='Choose file', mul
     return single_input(item_spec, valid_func, read_file, onchange_func)
     return single_input(item_spec, valid_func, read_file, onchange_func)
 
 
 
 
-def slider(label='', *, name=None, value=0, min_value=0, max_value=100, step=1, validate=None, onchange=None,
-           required=None, help_text=None, **other_html_attrs):
+def slider(label: str = '', *, name: str = None, value: Union[int, float] = 0, min_value: Union[int, float] = 0,
+           max_value: Union[int, float] = 100, step: int = 1, validate: Callable[[Any], Optional[str]] = None,
+           onchange: Callable[[Any], None] = None, required: bool = None, help_text: str = None, **other_html_attrs):
     r"""Range input.
     r"""Range input.
 
 
     :param int/float value: The initial value of the slider.
     :param int/float value: The initial value of the slider.
@@ -661,7 +670,7 @@ def slider(label='', *, name=None, value=0, min_value=0, max_value=100, step=1,
     return single_input(item_spec, valid_func, lambda d: d, onchange_func)
     return single_input(item_spec, valid_func, lambda d: d, onchange_func)
 
 
 
 
-def input_group(label='', inputs=None, validate=None, cancelable=False):
+def input_group(label: str = '', inputs: List = None, validate: Callable[[Dict], Optional[Tuple[str, str]]] = None, cancelable: bool = False):
     r"""Input group. Request a set of inputs from the user at once.
     r"""Input group. Request a set of inputs from the user at once.
 
 
     :param str label: Label of input group.
     :param str label: Label of input group.
@@ -747,7 +756,7 @@ def parse_input_update_spec(spec):
     return attributes
     return attributes
 
 
 
 
-def input_update(name=None, **spec):
+def input_update(name: str = None, **spec):
     """Update attributes of input field.
     """Update attributes of input field.
     This function can only be called in ``onchange`` callback of input functions.
     This function can only be called in ``onchange`` callback of input functions.
 
 

+ 57 - 48
pywebio/output.py

@@ -213,7 +213,7 @@ import string
 from base64 import b64encode
 from base64 import b64encode
 from collections.abc import Mapping, Sequence
 from collections.abc import Mapping, Sequence
 from functools import wraps
 from functools import wraps
-from typing import Union
+from typing import Any, Callable, Dict, List, Tuple, Union, Sequence as SequenceType
 
 
 from .io_ctrl import output_register_callback, send_msg, Output, safely_destruct_output_when_exp, OutputList, scope2dom
 from .io_ctrl import output_register_callback, send_msg, Output, safely_destruct_output_when_exp, OutputList, scope2dom
 from .session import get_current_session, download
 from .session import get_current_session, download
@@ -256,11 +256,11 @@ class OutputPosition:
 _scope_name_allowed_chars = set(string.ascii_letters + string.digits + '_-')
 _scope_name_allowed_chars = set(string.ascii_letters + string.digits + '_-')
 
 
 
 
-def set_scope(name, container_scope=None, position=OutputPosition.BOTTOM, if_exist=None):
+def set_scope(name: str, container_scope: str = None, position: int = OutputPosition.BOTTOM, if_exist: str = None):
     """Create a new scope.
     """Create a new scope.
 
 
     :param str name: scope name
     :param str name: scope name
-    :param str container_scope: Specify the parent scope of this scope. 
+    :param str container_scope: Specify the parent scope of this scope.
         When the scope doesn't exist, no operation is performed.
         When the scope doesn't exist, no operation is performed.
     :param int position: The location where this scope is created in the parent scope.
     :param int position: The location where this scope is created in the parent scope.
        (see :ref:`Scope related parameters <scope_param>`)
        (see :ref:`Scope related parameters <scope_param>`)
@@ -280,7 +280,7 @@ def set_scope(name, container_scope=None, position=OutputPosition.BOTTOM, if_exi
                                 position=position, if_exist=if_exist))
                                 position=position, if_exist=if_exist))
 
 
 
 
-def get_scope(stack_idx=-1):
+def get_scope(stack_idx: int = -1):
     """Get the scope name of runtime scope stack
     """Get the scope name of runtime scope stack
 
 
     :param int stack_idx: The index of the runtime scope stack. Default is -1.
     :param int stack_idx: The index of the runtime scope stack. Default is -1.
@@ -297,7 +297,7 @@ def get_scope(stack_idx=-1):
         return None
         return None
 
 
 
 
-def clear(scope=None):
+def clear(scope: str = None):
     """Clear the content of the specified scope
     """Clear the content of the specified scope
 
 
     :param str scope: Target scope name. Default is the current scope.
     :param str scope: Target scope name. Default is the current scope.
@@ -307,7 +307,7 @@ def clear(scope=None):
     send_msg('output_ctl', dict(clear=scope2dom(scope)))
     send_msg('output_ctl', dict(clear=scope2dom(scope)))
 
 
 
 
-def remove(scope=None):
+def remove(scope: str = None):
     """Remove the specified scope
     """Remove the specified scope
 
 
     :param str scope: Target scope name. Default is the current scope.
     :param str scope: Target scope name. Default is the current scope.
@@ -318,7 +318,7 @@ def remove(scope=None):
     send_msg('output_ctl', dict(remove=scope2dom(scope)))
     send_msg('output_ctl', dict(remove=scope2dom(scope)))
 
 
 
 
-def scroll_to(scope=None, position=Position.TOP):
+def scroll_to(scope: str = None, position: str = Position.TOP):
     """
     """
     Scroll the page to the specified scope
     Scroll the page to the specified scope
 
 
@@ -361,7 +361,7 @@ def _get_output_spec(type, scope, position, **other_spec):
     return spec
     return spec
 
 
 
 
-def put_text(*texts, sep=' ', inline=False, scope=None, position=OutputPosition.BOTTOM) -> Output:
+def put_text(*texts: Any, sep: str = ' ', inline: bool = False, scope: str = None, position: int = OutputPosition.BOTTOM) -> Output:
     """
     """
     Output plain text
     Output plain text
 
 
@@ -397,7 +397,7 @@ def _put_message(color, contents, closable=False, scope=None, position=OutputPos
                       scope=scope, position=position).enable_context_manager()
                       scope=scope, position=position).enable_context_manager()
 
 
 
 
-def put_info(*contents, closable=False, scope=None, position=OutputPosition.BOTTOM) -> Output:
+def put_info(*contents: Any, closable: bool = False, scope: str = None, position: int = OutputPosition.BOTTOM) -> Output:
     """Output information message.
     """Output information message.
 
 
     :param contents: Message contents.
     :param contents: Message contents.
@@ -410,7 +410,7 @@ def put_info(*contents, closable=False, scope=None, position=OutputPosition.BOTT
     return _put_message(color='info', contents=contents, closable=closable, scope=scope, position=position)
     return _put_message(color='info', contents=contents, closable=closable, scope=scope, position=position)
 
 
 
 
-def put_success(*contents, closable=False, scope=None, position=OutputPosition.BOTTOM) -> Output:
+def put_success(*contents: Any, closable: bool = False, scope: str = None, position: int = OutputPosition.BOTTOM) -> Output:
     """Output success message.
     """Output success message.
     .. seealso:: `put_info()`
     .. seealso:: `put_info()`
     .. versionadded:: 1.2
     .. versionadded:: 1.2
@@ -418,21 +418,24 @@ def put_success(*contents, closable=False, scope=None, position=OutputPosition.B
     return _put_message(color='success', contents=contents, closable=closable, scope=scope, position=position)
     return _put_message(color='success', contents=contents, closable=closable, scope=scope, position=position)
 
 
 
 
-def put_warning(*contents, closable=False, scope=None, position=OutputPosition.BOTTOM) -> Output:
+def put_warning(*contents: Any, closable: bool = False, scope: str = None, position: int = OutputPosition.BOTTOM) -> Output:
     """Output warning message.
     """Output warning message.
     .. seealso:: `put_info()`
     .. seealso:: `put_info()`
     """
     """
     return _put_message(color='warning', contents=contents, closable=closable, scope=scope, position=position)
     return _put_message(color='warning', contents=contents, closable=closable, scope=scope, position=position)
 
 
 
 
-def put_error(*contents, closable=False, scope=None, position=OutputPosition.BOTTOM) -> Output:
+def put_error(*contents: Any, closable: bool = False, scope: str = None, position: int = OutputPosition.BOTTOM) -> Output:
     """Output error message.
     """Output error message.
     .. seealso:: `put_info()`
     .. seealso:: `put_info()`
     """
     """
     return _put_message(color='danger', contents=contents, closable=closable, scope=scope, position=position)
     return _put_message(color='danger', contents=contents, closable=closable, scope=scope, position=position)
 
 
 
 
-def put_html(html, sanitize=False, scope=None, position=OutputPosition.BOTTOM) -> Output:
+# Due to the IPython rich output compatibility,
+# declare argument `html` to type `str` will cause type check error
+# so leave this argument's type `Any`
+def put_html(html: Any, sanitize: bool = False, scope: str = None, position: int = OutputPosition.BOTTOM) -> Output:
     """
     """
     Output HTML content
     Output HTML content
 
 
@@ -452,7 +455,7 @@ def put_html(html, sanitize=False, scope=None, position=OutputPosition.BOTTOM) -
     return Output(spec)
     return Output(spec)
 
 
 
 
-def put_code(content, language='', rows=None, scope=None, position=OutputPosition.BOTTOM) -> Output:
+def put_code(content: str, language: str = '', rows: int = None, scope: str = None, position: int = OutputPosition.BOTTOM) -> Output:
     """
     """
     Output code block
     Output code block
 
 
@@ -504,8 +507,8 @@ def _left_strip_multiple_line_string_literal(s):
     return '\n'.join(lines[:1] + lines_)
     return '\n'.join(lines[:1] + lines_)
 
 
 
 
-def put_markdown(mdcontent, lstrip=True, options=None, sanitize=True,
-                 scope=None, position=OutputPosition.BOTTOM, **kwargs) -> Output:
+def put_markdown(mdcontent: str, lstrip: bool = True, options: Dict[str, Union[str, bool]] = None, sanitize: bool = True,
+                 scope: str = None, position: int = OutputPosition.BOTTOM, **kwargs) -> Output:
     """
     """
     Output Markdown
     Output Markdown
 
 
@@ -534,6 +537,7 @@ def put_markdown(mdcontent, lstrip=True, options=None, sanitize=True,
     """
     """
     if 'strip_indent' in kwargs:
     if 'strip_indent' in kwargs:
         import warnings
         import warnings
+
         # use stacklevel=2 to make the warning refer to put_markdown() call
         # use stacklevel=2 to make the warning refer to put_markdown() call
         warnings.warn("`strip_indent` parameter is deprecated in `put_markdown()`", DeprecationWarning, stacklevel=2)
         warnings.warn("`strip_indent` parameter is deprecated in `put_markdown()`", DeprecationWarning, stacklevel=2)
 
 
@@ -551,7 +555,7 @@ class span_:
 
 
 
 
 @safely_destruct_output_when_exp('content')
 @safely_destruct_output_when_exp('content')
-def span(content, row=1, col=1):
+def span(content: Union[str, Output], row: int = 1, col: int = 1):
     """Create cross-cell content in :func:`put_table()` and :func:`put_grid()`
     """Create cross-cell content in :func:`put_table()` and :func:`put_grid()`
 
 
     :param content: cell content. It can be a string or ``put_xxx()`` call.
     :param content: cell content. It can be a string or ``put_xxx()`` call.
@@ -579,7 +583,7 @@ def span(content, row=1, col=1):
 
 
 
 
 @safely_destruct_output_when_exp('tdata')
 @safely_destruct_output_when_exp('tdata')
-def put_table(tdata, header=None, scope=None, position=OutputPosition.BOTTOM) -> Output:
+def put_table(tdata: List[Union[List, Dict]], header: List[Union[str, Tuple[Any, str]]] = None, scope: str = None, position: int = OutputPosition.BOTTOM) -> Output:
     """
     """
     Output table
     Output table
 
 
@@ -705,8 +709,9 @@ def _format_button(buttons):
     return btns, values
     return btns, values
 
 
 
 
-def put_buttons(buttons, onclick, small=None, link_style=False, outline=False, group=False, scope=None,
-                position=OutputPosition.BOTTOM, **callback_options) -> Output:
+def put_buttons(buttons: List[Union[Dict[str, Any], Tuple[str, Any], List, str]], onclick: Union[Callable[[Any], None], SequenceType[Callable[[], None]]],
+                small: bool = None, link_style: bool = False, outline: bool = False, group: bool = False, scope: str = None,
+                position: int = OutputPosition.BOTTOM, **callback_options) -> Output:
     """
     """
     Output a group of buttons and bind click event
     Output a group of buttons and bind click event
 
 
@@ -815,8 +820,8 @@ def put_buttons(buttons, onclick, small=None, link_style=False, outline=False, g
     return Output(spec)
     return Output(spec)
 
 
 
 
-def put_button(label, onclick, color=None, small=None, link_style=False, outline=False, disabled=False, scope=None,
-               position=OutputPosition.BOTTOM) -> Output:
+def put_button(label: str, onclick: Callable[[], None], color: str = None, small: bool = None, link_style: bool = False,
+               outline: bool = False, disabled: bool = False, scope: str = None, position: int = OutputPosition.BOTTOM) -> Output:
     """Output a single button and bind click event to it.
     """Output a single button and bind click event to it.
 
 
     :param str label: Button label
     :param str label: Button label
@@ -844,8 +849,8 @@ def put_button(label, onclick, color=None, small=None, link_style=False, outline
                        position=position)
                        position=position)
 
 
 
 
-def put_image(src, format=None, title='', width=None, height=None,
-              scope=None, position=OutputPosition.BOTTOM) -> Output:
+def put_image(src: Union[str, bytes, PILImage], format: str = None, title: str = '', width: str = None, height: str = None,
+              scope: str = None, position: int = OutputPosition.BOTTOM) -> Output:
     """Output image
     """Output image
 
 
     :param src: Source of image. It can be a string specifying image URL, a bytes-like object specifying
     :param src: Source of image. It can be a string specifying image URL, a bytes-like object specifying
@@ -891,7 +896,7 @@ def put_image(src, format=None, title='', width=None, height=None,
     return put_html(tag, scope=scope, position=position)
     return put_html(tag, scope=scope, position=position)
 
 
 
 
-def put_file(name, content, label=None, scope=None, position=OutputPosition.BOTTOM) -> Output:
+def put_file(name: str, content: bytes, label: str = None, scope: str = None, position: int = OutputPosition.BOTTOM) -> Output:
     """Output a link to download a file
     """Output a link to download a file
 
 
     To show a link with the file name on the browser. When click the link, the browser automatically downloads the file.
     To show a link with the file name on the browser. When click the link, the browser automatically downloads the file.
@@ -919,8 +924,8 @@ def put_file(name, content, label=None, scope=None, position=OutputPosition.BOTT
     return output
     return output
 
 
 
 
-def put_link(name, url=None, app=None, new_window=False, scope=None,
-             position=OutputPosition.BOTTOM) -> Output:
+def put_link(name: str, url: str = None, app: str = None, new_window: bool = False, scope: str = None,
+             position: int = OutputPosition.BOTTOM) -> Output:
     """Output hyperlinks to other web page or PyWebIO Application page.
     """Output hyperlinks to other web page or PyWebIO Application page.
 
 
     :param str name: The label of the link
     :param str name: The label of the link
@@ -940,8 +945,8 @@ def put_link(name, url=None, app=None, new_window=False, scope=None,
     return put_html(tag, scope=scope, position=position)
     return put_html(tag, scope=scope, position=position)
 
 
 
 
-def put_processbar(name, init=0, label=None, auto_close=False, scope=None,
-                   position=OutputPosition.BOTTOM) -> Output:
+def put_processbar(name: str, init: float = 0, label: str = None, auto_close: bool = False, scope: str = None,
+                   position: int = OutputPosition.BOTTOM) -> Output:
     """Output a process bar
     """Output a process bar
 
 
     :param str name: The name of the progress bar, which is the unique identifier of the progress bar
     :param str name: The name of the progress bar, which is the unique identifier of the progress bar
@@ -979,7 +984,7 @@ def put_processbar(name, init=0, label=None, auto_close=False, scope=None,
                       position=position)
                       position=position)
 
 
 
 
-def set_processbar(name, value, label=None):
+def set_processbar(name: str, value: float, label: str = None):
     """Set the progress of progress bar
     """Set the progress of progress bar
 
 
     :param str name: The name of the progress bar
     :param str name: The name of the progress bar
@@ -1008,7 +1013,7 @@ def set_processbar(name, value, label=None):
     run_js(js_code)
     run_js(js_code)
 
 
 
 
-def put_loading(shape='border', color='dark', scope=None, position=OutputPosition.BOTTOM) -> Output:
+def put_loading(shape: str = 'border', color: str = 'dark', scope: str = None, position: int = OutputPosition.BOTTOM) -> Output:
     """Output loading prompt
     """Output loading prompt
 
 
     :param str shape: The shape of loading prompt. The available values are: `'border'` (default)、 `'grow'`
     :param str shape: The shape of loading prompt. The available values are: `'border'` (default)、 `'grow'`
@@ -1061,7 +1066,7 @@ def put_loading(shape='border', color='dark', scope=None, position=OutputPositio
 
 
 
 
 @safely_destruct_output_when_exp('content')
 @safely_destruct_output_when_exp('content')
-def put_collapse(title, content=[], open=False, scope=None, position=OutputPosition.BOTTOM) -> Output:
+def put_collapse(title: str, content: Union[str, Output, List[Union[str, Output]]] = [], open: bool = False, scope: str = None, position: int = OutputPosition.BOTTOM) -> Output:
     """Output collapsible content
     """Output collapsible content
 
 
     :param str title: Title of content
     :param str title: Title of content
@@ -1105,8 +1110,8 @@ def put_collapse(title, content=[], open=False, scope=None, position=OutputPosit
 
 
 
 
 @safely_destruct_output_when_exp('content')
 @safely_destruct_output_when_exp('content')
-def put_scrollable(content=[], height=400, keep_bottom=False, border=True,
-                   scope=None, position=OutputPosition.BOTTOM, **kwargs) -> Output:
+def put_scrollable(content: Union[str, Output, List[Union[str, Output]]] = [], height: Union[int, Tuple[int, int]] = 400, keep_bottom: bool = False, border: bool = True,
+                   scope: str = None, position: int = OutputPosition.BOTTOM, **kwargs) -> Output:
     """Output a fixed height content area. scroll bar is displayed when the content exceeds the limit
     """Output a fixed height content area. scroll bar is displayed when the content exceeds the limit
 
 
     :type content: list/str/put_xxx()
     :type content: list/str/put_xxx()
@@ -1148,15 +1153,16 @@ def put_scrollable(content=[], height=400, keep_bottom=False, border=True,
 
 
     if 'max_height' in kwargs:
     if 'max_height' in kwargs:
         import warnings
         import warnings
+
         # use stacklevel=2 to make the warning refer to the put_scrollable() call
         # use stacklevel=2 to make the warning refer to the put_scrollable() call
         warnings.warn("`max_height` parameter is deprecated in `put_scrollable()`, use `height` instead.",
         warnings.warn("`max_height` parameter is deprecated in `put_scrollable()`, use `height` instead.",
                       DeprecationWarning, stacklevel=2)
                       DeprecationWarning, stacklevel=2)
         height = kwargs['max_height']  # Backward compatible
         height = kwargs['max_height']  # Backward compatible
 
 
-    try:
-        min_height, max_height = height
-    except Exception:
+    if isinstance(height, int):  # height is a int
         min_height, max_height = height, height
         min_height, max_height = height, height
+    else:  # height is a tuple of (min_height, max_height)
+        min_height, max_height = height
 
 
     spec = _get_output_spec('scrollable', contents=content, min_height=min_height, max_height=max_height,
     spec = _get_output_spec('scrollable', contents=content, min_height=min_height, max_height=max_height,
                             keep_bottom=keep_bottom, border=border, scope=scope, position=position)
                             keep_bottom=keep_bottom, border=border, scope=scope, position=position)
@@ -1164,7 +1170,7 @@ def put_scrollable(content=[], height=400, keep_bottom=False, border=True,
 
 
 
 
 @safely_destruct_output_when_exp('tabs')
 @safely_destruct_output_when_exp('tabs')
-def put_tabs(tabs, scope=None, position=OutputPosition.BOTTOM) -> Output:
+def put_tabs(tabs: List[Dict[str, Any]], scope: str = None, position: int = OutputPosition.BOTTOM) -> Output:
     """Output tabs.
     """Output tabs.
 
 
     :param list tabs: Tab list, each item is a dict: ``{"title": "Title", "content": ...}`` .
     :param list tabs: Tab list, each item is a dict: ``{"title": "Title", "content": ...}`` .
@@ -1199,7 +1205,7 @@ def put_tabs(tabs, scope=None, position=OutputPosition.BOTTOM) -> Output:
 
 
 
 
 @safely_destruct_output_when_exp('data')
 @safely_destruct_output_when_exp('data')
-def put_widget(template, data, scope=None, position=OutputPosition.BOTTOM) -> Output:
+def put_widget(template: str, data: Dict[str, Any], scope: str = None, position: int = OutputPosition.BOTTOM) -> Output:
     """Output your own widget
     """Output your own widget
 
 
     :param template: html template, using `mustache.js <https://github.com/janl/mustache.js>`_ syntax
     :param template: html template, using `mustache.js <https://github.com/janl/mustache.js>`_ syntax
@@ -1246,7 +1252,7 @@ def put_widget(template, data, scope=None, position=OutputPosition.BOTTOM) -> Ou
 
 
 
 
 @safely_destruct_output_when_exp('content')
 @safely_destruct_output_when_exp('content')
-def put_row(content=[], size=None, scope=None, position=OutputPosition.BOTTOM) -> Output:
+def put_row(content: List[Union[Output, None]] = [], size: str = None, scope: str = None, position: int = OutputPosition.BOTTOM) -> Output:
     """Use row layout to output content. The content is arranged horizontally
     """Use row layout to output content. The content is arranged horizontally
 
 
     :param list content: Content list, the item is ``put_xxx()`` call or ``None``. ``None`` represents the space between the output
     :param list content: Content list, the item is ``put_xxx()`` call or ``None``. ``None`` represents the space between the output
@@ -1286,7 +1292,7 @@ def put_row(content=[], size=None, scope=None, position=OutputPosition.BOTTOM) -
 
 
 
 
 @safely_destruct_output_when_exp('content')
 @safely_destruct_output_when_exp('content')
-def put_column(content=[], size=None, scope=None, position=OutputPosition.BOTTOM) -> Output:
+def put_column(content: List[Union[Output, None]] = [], size: str = None, scope: str = None, position: int = OutputPosition.BOTTOM) -> Output:
     """Use column layout to output content. The content is arranged vertically
     """Use column layout to output content. The content is arranged vertically
 
 
     :param list content: Content list, the item is ``put_xxx()`` call or ``None``. ``None`` represents the space between the output
     :param list content: Content list, the item is ``put_xxx()`` call or ``None``. ``None`` represents the space between the output
@@ -1321,8 +1327,9 @@ def _row_column_layout(content, flow, size, scope=None, position=OutputPosition.
 
 
 
 
 @safely_destruct_output_when_exp('content')
 @safely_destruct_output_when_exp('content')
-def put_grid(content, cell_width='auto', cell_height='auto', cell_widths=None, cell_heights=None, direction='row',
-             scope=None, position=OutputPosition.BOTTOM) -> Output:
+def put_grid(content: List[List[Union[Output, None]]], cell_width: str = 'auto', cell_height: str = 'auto',
+             cell_widths: str = None, cell_heights: str = None, direction: str = 'row', scope: str = None,
+             position: int = OutputPosition.BOTTOM) -> Output:
     """Output content using grid layout
     """Output content using grid layout
 
 
     :param content: Content of grid, which is a two-dimensional list. The item of list is ``put_xxx()`` call or ``None``.
     :param content: Content of grid, which is a two-dimensional list. The item of list is ``put_xxx()`` call or ``None``.
@@ -1407,7 +1414,7 @@ def put_grid(content, cell_width='auto', cell_height='auto', cell_widths=None, c
 
 
 
 
 @safely_destruct_output_when_exp('content')
 @safely_destruct_output_when_exp('content')
-def put_scope(name, content=[], scope=None, position=OutputPosition.BOTTOM) -> Output:
+def put_scope(name: str, content: Union[Output, List[Output]] = [], scope: str = None, position: int = OutputPosition.BOTTOM) -> Output:
     """Output a scope
     """Output a scope
 
 
     :param str name:
     :param str name:
@@ -1469,6 +1476,7 @@ def output(*contents):
     """
     """
 
 
     import warnings
     import warnings
+
     # use stacklevel=2 to make the warning refer to the output() call
     # use stacklevel=2 to make the warning refer to the output() call
     warnings.warn("`pywebio.output.output()` is deprecated since v1.5 and will remove in the future version, "
     warnings.warn("`pywebio.output.output()` is deprecated since v1.5 and will remove in the future version, "
                   "use `pywebio.output.put_scope()` instead", DeprecationWarning, stacklevel=2)
                   "use `pywebio.output.put_scope()` instead", DeprecationWarning, stacklevel=2)
@@ -1530,7 +1538,7 @@ def output(*contents):
 
 
 
 
 @safely_destruct_output_when_exp('outputs')
 @safely_destruct_output_when_exp('outputs')
-def style(outputs, css_style) -> Union[Output, OutputList]:
+def style(outputs: Union[Output, List[Output]], css_style: str) -> Union[Output, OutputList]:
     """Customize the css style of output content
     """Customize the css style of output content
 
 
     .. deprecated:: 1.3
     .. deprecated:: 1.3
@@ -1590,7 +1598,8 @@ def style(outputs, css_style) -> Union[Output, OutputList]:
 
 
 
 
 @safely_destruct_output_when_exp('content')
 @safely_destruct_output_when_exp('content')
-def popup(title, content=None, size=PopupSize.NORMAL, implicit_close=True, closable=True):
+def popup(title: str, content: Union[str, Output, List[Union[str, Output]]] = None, size: str = PopupSize.NORMAL, implicit_close: bool = True,
+          closable: bool = True):
     """
     """
     Show a popup.
     Show a popup.
 
 
@@ -1686,7 +1695,7 @@ def close_popup():
     send_msg(cmd='close_popup')
     send_msg(cmd='close_popup')
 
 
 
 
-def toast(content, duration=2, position='center', color='info', onclick=None):
+def toast(content: str, duration: float = 2, position: str = 'center', color: str = 'info', onclick: Callable[[], None] = None):
     """Show a notification message.
     """Show a notification message.
 
 
     :param str content: Notification content.
     :param str content: Notification content.
@@ -1729,7 +1738,7 @@ def toast(content, duration=2, position='center', color='info', onclick=None):
 clear_scope = clear
 clear_scope = clear
 
 
 
 
-def use_scope(name=None, clear=False, **kwargs):
+def use_scope(name: str = None, clear: bool = False, **kwargs):
     """use_scope(name=None, clear=False)
     """use_scope(name=None, clear=False)
 
 
     Open or enter a scope. Can be used as context manager and decorator.
     Open or enter a scope. Can be used as context manager and decorator.

+ 32 - 25
pywebio/pin.py

@@ -125,12 +125,13 @@ Pin utils
 """
 """
 
 
 import string
 import string
+from typing import Any, Callable, Dict, List, Optional, Tuple, Union
 
 
 from pywebio.input import parse_input_update_spec
 from pywebio.input import parse_input_update_spec
-from pywebio.output import OutputPosition, Output
-from pywebio.output import _get_output_spec
-from .io_ctrl import send_msg, single_input_kwargs, output_register_callback
-from .session import next_client_event, chose_impl
+from pywebio.output import Output, OutputPosition, _get_output_spec
+
+from .io_ctrl import output_register_callback, send_msg, single_input_kwargs
+from .session import chose_impl, next_client_event
 from .utils import check_dom_name_value
 from .utils import check_dom_name_value
 
 
 _pin_name_chars = set(string.ascii_letters + string.digits + '_-')
 _pin_name_chars = set(string.ascii_letters + string.digits + '_-')
@@ -145,8 +146,9 @@ def _pin_output(single_input_return, scope, position):
     return Output(spec)
     return Output(spec)
 
 
 
 
-def put_input(name, type='text', *, label='', value=None, placeholder=None, readonly=None, datalist=None,
-              help_text=None, scope=None, position=OutputPosition.BOTTOM) -> Output:
+def put_input(name: str, type: str = 'text', *, label: str = '', value: str = None, placeholder: str = None,
+              readonly: bool = None, datalist: List[str] = None, help_text: str = None, scope: str = None,
+              position: int = OutputPosition.BOTTOM) -> Output:
     """Output an input widget. Refer to: `pywebio.input.input()`"""
     """Output an input widget. Refer to: `pywebio.input.input()`"""
     from pywebio.input import input
     from pywebio.input import input
     check_dom_name_value(name, 'pin `name`')
     check_dom_name_value(name, 'pin `name`')
@@ -155,8 +157,9 @@ def put_input(name, type='text', *, label='', value=None, placeholder=None, read
     return _pin_output(single_input_return, scope, position)
     return _pin_output(single_input_return, scope, position)
 
 
 
 
-def put_textarea(name, *, label='', rows=6, code=None, maxlength=None, minlength=None, value=None, placeholder=None,
-                 readonly=None, help_text=None, scope=None, position=OutputPosition.BOTTOM) -> Output:
+def put_textarea(name: str, *, label: str = '', rows: int = 6, code: Union[bool, Dict] = None, maxlength: int = None,
+                 minlength: int = None, value: str = None, placeholder: str = None, readonly: bool = None,
+                 help_text: str = None, scope: str = None, position: int = OutputPosition.BOTTOM) -> Output:
     """Output a textarea widget. Refer to: `pywebio.input.textarea()`"""
     """Output a textarea widget. Refer to: `pywebio.input.textarea()`"""
     from pywebio.input import textarea
     from pywebio.input import textarea
     check_dom_name_value(name, 'pin `name`')
     check_dom_name_value(name, 'pin `name`')
@@ -166,8 +169,9 @@ def put_textarea(name, *, label='', rows=6, code=None, maxlength=None, minlength
     return _pin_output(single_input_return, scope, position)
     return _pin_output(single_input_return, scope, position)
 
 
 
 
-def put_select(name, options=None, *, label='', multiple=None, value=None, help_text=None,
-               scope=None, position=OutputPosition.BOTTOM) -> Output:
+def put_select(name: str, options: List[Union[Dict[str, Any], Tuple, List, str]] = None, *, label: str = '',
+               multiple: bool = None, value: Union[List, str] = None, help_text: str = None,
+               scope: str = None, position: int = OutputPosition.BOTTOM) -> Output:
     """Output a select widget. Refer to: `pywebio.input.select()`"""
     """Output a select widget. Refer to: `pywebio.input.select()`"""
     from pywebio.input import select
     from pywebio.input import select
     check_dom_name_value(name, 'pin `name`')
     check_dom_name_value(name, 'pin `name`')
@@ -176,8 +180,9 @@ def put_select(name, options=None, *, label='', multiple=None, value=None, help_
     return _pin_output(single_input_return, scope, position)
     return _pin_output(single_input_return, scope, position)
 
 
 
 
-def put_checkbox(name, options=None, *, label='', inline=None, value=None, help_text=None,
-                 scope=None, position=OutputPosition.BOTTOM) -> Output:
+def put_checkbox(name: str, options: List[Union[Dict[str, Any], Tuple, List, str]] = None, *, label: str = '',
+                 inline: bool = None, value: List = None, help_text: str = None, scope: str = None,
+                 position: int = OutputPosition.BOTTOM) -> Output:
     """Output a checkbox widget. Refer to: `pywebio.input.checkbox()`"""
     """Output a checkbox widget. Refer to: `pywebio.input.checkbox()`"""
     from pywebio.input import checkbox
     from pywebio.input import checkbox
     check_dom_name_value(name, 'pin `name`')
     check_dom_name_value(name, 'pin `name`')
@@ -186,8 +191,9 @@ def put_checkbox(name, options=None, *, label='', inline=None, value=None, help_
     return _pin_output(single_input_return, scope, position)
     return _pin_output(single_input_return, scope, position)
 
 
 
 
-def put_radio(name, options=None, *, label='', inline=None, value=None, help_text=None,
-              scope=None, position=OutputPosition.BOTTOM) -> Output:
+def put_radio(name: str, options: List[Union[Dict[str, Any], Tuple, List, str]] = None, *, label: str = '',
+              inline: bool = None, value: str = None, help_text: str = None, scope: str = None,
+              position: int = OutputPosition.BOTTOM) -> Output:
     """Output a radio widget. Refer to: `pywebio.input.radio()`"""
     """Output a radio widget. Refer to: `pywebio.input.radio()`"""
     from pywebio.input import radio
     from pywebio.input import radio
     check_dom_name_value(name, 'pin `name`')
     check_dom_name_value(name, 'pin `name`')
@@ -196,8 +202,9 @@ def put_radio(name, options=None, *, label='', inline=None, value=None, help_tex
     return _pin_output(single_input_return, scope, position)
     return _pin_output(single_input_return, scope, position)
 
 
 
 
-def put_slider(name, *, label='', value=0, min_value=0, max_value=100, step=1, required=None, help_text=None,
-               scope=None, position=OutputPosition.BOTTOM) -> Output:
+def put_slider(name: str, *, label: str = '', value: Union[int, float] = 0, min_value: Union[int, float] = 0,
+               max_value: Union[int, float] = 100, step: int = 1, required: bool = None, help_text: str = None,
+               scope: str = None, position: int = OutputPosition.BOTTOM) -> Output:
     """Output a slide widget. Refer to: `pywebio.input.slider()`"""
     """Output a slide widget. Refer to: `pywebio.input.slider()`"""
     from pywebio.input import slider
     from pywebio.input import slider
     check_dom_name_value(name, 'pin `name`')
     check_dom_name_value(name, 'pin `name`')
@@ -206,8 +213,8 @@ def put_slider(name, *, label='', value=0, min_value=0, max_value=100, step=1, r
     return _pin_output(single_input_return, scope, position)
     return _pin_output(single_input_return, scope, position)
 
 
 
 
-def put_actions(name, *, label='', buttons=None, help_text=None,
-                scope=None, position=OutputPosition.BOTTOM) -> Output:
+def put_actions(name: str, *, label: str = '', buttons: List[Union[Dict[str, Any], Tuple, List, str]] = None,
+                help_text: str = None, scope: str = None, position: int = OutputPosition.BOTTOM) -> Output:
     """Output a group of action button. Refer to: `pywebio.input.actions()`
     """Output a group of action button. Refer to: `pywebio.input.actions()`
 
 
     Unlike the ``actions()``, ``put_actions()`` won't submit any form, it will only set the value of the pin widget.
     Unlike the ``actions()``, ``put_actions()`` won't submit any form, it will only set the value of the pin widget.
@@ -250,17 +257,17 @@ class Pin_:
         """
         """
         object.__setattr__(self, '_strict', True)
         object.__setattr__(self, '_strict', True)
 
 
-    def __getattr__(self, name):
+    def __getattr__(self, name: str):
         """__getattr__ is only invoked if the attribute wasn't found the usual ways"""
         """__getattr__ is only invoked if the attribute wasn't found the usual ways"""
         if name.startswith('__'):
         if name.startswith('__'):
             raise AttributeError('Pin object has no attribute %r' % name)
             raise AttributeError('Pin object has no attribute %r' % name)
         return self.__getitem__(name)
         return self.__getitem__(name)
 
 
-    def __getitem__(self, name):
+    def __getitem__(self, name: str):
         check_dom_name_value(name, 'pin `name`')
         check_dom_name_value(name, 'pin `name`')
         return get_pin_value(name, self._strict)
         return get_pin_value(name, self._strict)
 
 
-    def __setattr__(self, name, value):
+    def __setattr__(self, name: str, value):
         """
         """
         __setattr__ will be invoked regardless of whether the attribute be found
         __setattr__ will be invoked regardless of whether the attribute be found
         """
         """
@@ -268,7 +275,7 @@ class Pin_:
         check_dom_name_value(name, 'pin `name`')
         check_dom_name_value(name, 'pin `name`')
         self.__setitem__(name, value)
         self.__setitem__(name, value)
 
 
-    def __setitem__(self, name, value):
+    def __setitem__(self, name: str, value):
         send_msg('pin_update', spec=dict(name=name, attributes={"value": value}))
         send_msg('pin_update', spec=dict(name=name, attributes={"value": value}))
 
 
 
 
@@ -276,7 +283,7 @@ class Pin_:
 pin = Pin_()
 pin = Pin_()
 
 
 
 
-def pin_wait_change(*names, timeout=None):
+def pin_wait_change(*names, timeout: Optional[int] = None):
     """``pin_wait_change()`` listens to a list of pin widgets, when the value of any widgets changes,
     """``pin_wait_change()`` listens to a list of pin widgets, when the value of any widgets changes,
     the function returns with the name and value of the changed widget.
     the function returns with the name and value of the changed widget.
 
 
@@ -318,7 +325,7 @@ def pin_wait_change(*names, timeout=None):
     return get_client_val()
     return get_client_val()
 
 
 
 
-def pin_update(name, **spec):
+def pin_update(name: str, **spec):
     """Update attributes of pin widgets.
     """Update attributes of pin widgets.
 
 
     :param str name: The ``name`` of the target input widget.
     :param str name: The ``name`` of the target input widget.
@@ -330,7 +337,7 @@ def pin_update(name, **spec):
     send_msg('pin_update', spec=dict(name=name, attributes=attributes))
     send_msg('pin_update', spec=dict(name=name, attributes=attributes))
 
 
 
 
-def pin_on_change(name, onchange=None, clear=False, init_run=False, **callback_options):
+def pin_on_change(name: str, onchange: Callable[[Any], None] = None, clear: bool = False, init_run: bool = False, **callback_options):
     """
     """
     Bind a callback function to pin widget, the function will be called when user change the value of the pin widget.
     Bind a callback function to pin widget, the function will be called when user change the value of the pin widget.
 
 

+ 4 - 5
pywebio/platform/adaptor/http.py

@@ -14,11 +14,11 @@ import logging
 import threading
 import threading
 import time
 import time
 from contextlib import contextmanager
 from contextlib import contextmanager
-from typing import Dict
+from typing import Dict, Optional
 
 
 from ..page import make_applications, render_page
 from ..page import make_applications, render_page
 from ..utils import deserialize_binary_event
 from ..utils import deserialize_binary_event
-from ...session import CoroutineBasedSession, Session, ThreadBasedSession, register_session_implement_for_target
+from ...session import CoroutineBasedSession, ThreadBasedSession, register_session_implement_for_target
 from ...session.base import get_session_info_from_headers
 from ...session.base import get_session_info_from_headers
 from ...utils import random_str, LRUDict, isgeneratorfunction, iscoroutinefunction, check_webio_js
 from ...utils import random_str, LRUDict, isgeneratorfunction, iscoroutinefunction, check_webio_js
 
 
@@ -40,7 +40,7 @@ class HttpContext:
         Return the HTTP method of the current request, uppercase"""
         Return the HTTP method of the current request, uppercase"""
         pass
         pass
 
 
-    def request_headers(self) -> dict:
+    def request_headers(self) -> Dict:
         """返回当前请求的header字典
         """返回当前请求的header字典
         Return the header dictionary of the current request"""
         Return the header dictionary of the current request"""
         pass
         pass
@@ -58,7 +58,7 @@ class HttpContext:
         """
         """
         return b''
         return b''
 
 
-    def request_json(self) -> dict:
+    def request_json(self) -> Optional[Dict]:
         """返回当前请求的json反序列化后的内容,若请求数据不为json格式,返回None
         """返回当前请求的json反序列化后的内容,若请求数据不为json格式,返回None
         Return the data (json deserialization) of the currently requested, if the data is not in json format, return None"""
         Return the data (json deserialization) of the currently requested, if the data is not in json format, return None"""
         try:
         try:
@@ -111,7 +111,6 @@ class HttpHandler:
         https://stackoverflow.com/questions/1312331/using-a-global-dictionary-with-threads-in-python
         https://stackoverflow.com/questions/1312331/using-a-global-dictionary-with-threads-in-python
 
 
     """
     """
-    # type: Dict[str, Session]
     _webio_sessions = {}  # WebIOSessionID -> WebIOSession()
     _webio_sessions = {}  # WebIOSessionID -> WebIOSession()
     _webio_last_commands = {}  # WebIOSessionID -> (last commands, commands sequence id)
     _webio_last_commands = {}  # WebIOSessionID -> (last commands, commands sequence id)
     _webio_expire = LRUDict()  # WebIOSessionID -> last active timestamp. In increasing order of last active time
     _webio_expire = LRUDict()  # WebIOSessionID -> last active timestamp. In increasing order of last active time

+ 7 - 7
pywebio/platform/adaptor/ws.py

@@ -1,14 +1,14 @@
+import abc
 import asyncio
 import asyncio
 import json
 import json
 import logging
 import logging
 import time
 import time
 import typing
 import typing
-from typing import Dict
-import abc
+from typing import Dict, Optional
+
+from ...session import CoroutineBasedSession, Session, ThreadBasedSession
+from ...utils import LRUDict, iscoroutinefunction, isgeneratorfunction, random_str
 from ..utils import deserialize_binary_event
 from ..utils import deserialize_binary_event
-from ...session import CoroutineBasedSession, ThreadBasedSession, Session
-from ...utils import iscoroutinefunction, isgeneratorfunction, \
-    random_str, LRUDict
 
 
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
@@ -103,8 +103,8 @@ class WebSocketHandler:
     share one session with multiple connection in session lifetime, but one conn at a time
     share one session with multiple connection in session lifetime, but one conn at a time
     """
     """
 
 
-    session_id: str = None
-    session: Session = None  # the session that current connection attaches
+    session_id: Optional[str] = None
+    session: Optional[Session] = None  # the session that current connection attaches
     connection: WebSocketConnection
     connection: WebSocketConnection
     reconnectable: bool
     reconnectable: bool
 
 

+ 7 - 7
pywebio/platform/bokeh.py

@@ -144,13 +144,13 @@ def show_app(app, state, notebook_url, port=0, **kw):
     script = server_document(url, resources=None)
     script = server_document(url, resources=None)
 
 
     script = re.sub(r'<script(.*?)>([\s\S]*?)</script>',  # lgtm [py/bad-tag-filter]
     script = re.sub(r'<script(.*?)>([\s\S]*?)</script>',  # lgtm [py/bad-tag-filter]
-    r"""
-    <script \g<1>>
-        requirejs(['bokeh', 'bokeh-widgets', 'bokeh-tables'], function(Bokeh) {
-            \g<2>
-        });
-    </script>
-    """, script, flags=re.I)
+                    r"""
+                    <script \g<1>>
+                        requirejs(['bokeh', 'bokeh-widgets', 'bokeh-tables'], function(Bokeh) {
+                            \g<2>
+                        });
+                    </script>
+                    """, script, flags=re.I)
 
 
     put_html(script, sanitize=False)
     put_html(script, sanitize=False)
 
 

+ 5 - 5
pywebio/platform/django.py

@@ -102,11 +102,11 @@ urlpatterns = []
 
 
 
 
 def wsgi_app(applications, cdn=True,
 def wsgi_app(applications, cdn=True,
-                 static_dir=None,
-                 allowed_origins=None, check_origin=None,
-                 session_expire_seconds=None,
-                 session_cleanup_interval=None,
-                 debug=False, max_payload_size='200M', **django_options):
+             static_dir=None,
+             allowed_origins=None, check_origin=None,
+             session_expire_seconds=None,
+             session_cleanup_interval=None,
+             debug=False, max_payload_size='200M', **django_options):
     """Get the Django WSGI app for running PyWebIO applications.
     """Get the Django WSGI app for running PyWebIO applications.
 
 
     The arguments of ``wsgi_app()`` have the same meaning as for :func:`pywebio.platform.django.start_server`
     The arguments of ``wsgi_app()`` have the same meaning as for :func:`pywebio.platform.django.start_server`

+ 3 - 3
pywebio/platform/page.py

@@ -13,8 +13,8 @@ from ..utils import isgeneratorfunction, iscoroutinefunction, get_function_name,
 
 
 """
 """
 The maximum size in bytes of a http request body or a websocket message, after which the request or websocket is aborted
 The maximum size in bytes of a http request body or a websocket message, after which the request or websocket is aborted
-Set by `start_server()` or `path_deploy()` 
-Used in `file_upload()` as the `max_size`/`max_total_size` parameter default or to validate the parameter. 
+Set by `start_server()` or `path_deploy()`
+Used in `file_upload()` as the `max_size`/`max_total_size` parameter default or to validate the parameter.
 """
 """
 MAX_PAYLOAD_SIZE = 0
 MAX_PAYLOAD_SIZE = 0
 
 
@@ -102,7 +102,7 @@ _app_list_tpl = template.Template("""
         {% end %}
         {% end %}
 
 
         {% if meta.description %}
         {% if meta.description %}
-            {{ meta.description }} 
+            {{ meta.description }}
         {% else %}
         {% else %}
             <i>No description.</i>
             <i>No description.</i>
         {% end %}
         {% end %}

+ 1 - 1
pywebio/platform/path_deploy.py

@@ -116,7 +116,7 @@ _app_list_tpl = tornado.template.Template("""
 <h1>{{ title }}</h1>
 <h1>{{ title }}</h1>
 <hr>
 <hr>
 <pre style="line-height: 1.6em; font-size: 16px;">
 <pre style="line-height: 1.6em; font-size: 16px;">
-{% for name,doc in files %} <a href="{{ name }}">{{ name }}</a>  <span>{{ doc }}</span> 
+{% for name,doc in files %} <a href="{{ name }}">{{ name }}</a>  <span>{{ doc }}</span>
 {% end %}</pre>
 {% end %}</pre>
 <hr>
 <hr>
 </body>
 </body>

+ 1 - 1
pywebio/platform/remote_access.py

@@ -20,7 +20,7 @@ success_msg = """
 ================================================================================
 ================================================================================
 PyWebIO Application Remote Access
 PyWebIO Application Remote Access
 
 
-Remote access address: {address} 
+Remote access address: {address}
 ================================================================================
 ================================================================================
 """
 """
 
 

+ 9 - 9
pywebio/platform/tornado.py

@@ -7,6 +7,7 @@ import threading
 import typing
 import typing
 import webbrowser
 import webbrowser
 from functools import partial
 from functools import partial
+from typing import Callable, Dict, List, Optional, Union
 from urllib.parse import urlparse
 from urllib.parse import urlparse
 
 
 import tornado
 import tornado
@@ -206,13 +207,11 @@ def _setup_server(webio_handler, port=0, host='', static_dir=None, max_buffer_si
     return server, port
     return server, port
 
 
 
 
-def start_server(applications, port=0, host='',
-                 debug=False, cdn=True, static_dir=None,
-                 remote_access=False,
-                 reconnect_timeout=0,
-                 allowed_origins=None, check_origin=None,
-                 auto_open_webbrowser=False,
-                 max_payload_size='200M',
+def start_server(applications: Union[Callable[[], None], List[Callable[[], None]], Dict[str, Callable[[], None]]],
+                 port: int = 0, host: str = '', debug: bool = False, cdn: Union[bool, str] = True,
+                 static_dir: Optional[str] = None, remote_access: bool = False, reconnect_timeout: int = 0,
+                 allowed_origins: Optional[List[str]] = None, check_origin: Callable[[str], bool] = None,
+                 auto_open_webbrowser: bool = False, max_payload_size: Union[int, str] = '200M',
                  **tornado_app_settings):
                  **tornado_app_settings):
     """Start a Tornado server to provide the PyWebIO application as a web service.
     """Start a Tornado server to provide the PyWebIO application as a web service.
 
 
@@ -277,7 +276,8 @@ def start_server(applications, port=0, host='',
 
 
     page.MAX_PAYLOAD_SIZE = max_payload_size = parse_file_size(max_payload_size)
     page.MAX_PAYLOAD_SIZE = max_payload_size = parse_file_size(max_payload_size)
 
 
-    debug = Session.debug = os.environ.get('PYWEBIO_DEBUG', debug)
+    # covered `os.environ.get()` func with `bool()` to pervent type check error
+    debug = Session.debug = bool(os.environ.get('PYWEBIO_DEBUG', debug))
 
 
     # Since some cloud server may close idle connections (such as heroku),
     # Since some cloud server may close idle connections (such as heroku),
     # use `websocket_ping_interval` to  keep the connection alive
     # use `websocket_ping_interval` to  keep the connection alive
@@ -314,7 +314,7 @@ def start_server_in_current_thread_session():
 
 
     class SingleSessionWSHandler(_webio_handler(cdn=False)):
     class SingleSessionWSHandler(_webio_handler(cdn=False)):
         session: ScriptModeSession = None
         session: ScriptModeSession = None
-        instance: typing.ClassVar = None  # type: SingleSessionWSHandler
+        instance: typing.ClassVar = None
         closed = False
         closed = False
 
 
         def send_msg_to_client(self, session):
         def send_msg_to_client(self, session):