Bläddra i källkod

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 år sedan
förälder
incheckning
25775609ed

+ 33 - 24
pywebio/input.py

@@ -76,11 +76,12 @@ import copy
 import logging
 import os.path
 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 .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__)
 
@@ -99,9 +100,10 @@ RADIO = 'radio'
 SELECT = 'select'
 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=()):
@@ -131,8 +133,9 @@ def _parse_args(kwargs, excludes=()):
     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
 
     :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)
 
 
-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)
 
     :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
 
 
-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
 
     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)
 
 
-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.
 
     :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)
 
 
-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.
 
     :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
 
 
-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
 
     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)
 
 
-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
 
     :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,
             'last_modified': Last modified time (timestamp) of the file
         }
-       
+
        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.
        If the user does not upload a file, an empty list is returned.
 
     .. note::
-    
+
         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
         :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)
 
 
-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.
 
     :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)
 
 
-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.
 
     :param str label: Label of input group.
@@ -747,7 +756,7 @@ def parse_input_update_spec(spec):
     return attributes
 
 
-def input_update(name=None, **spec):
+def input_update(name: str = None, **spec):
     """Update attributes of input field.
     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 collections.abc import Mapping, Sequence
 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 .session import get_current_session, download
@@ -256,11 +256,11 @@ class OutputPosition:
 _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.
 
     :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.
     :param int position: The location where this scope is created in the parent scope.
        (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))
 
 
-def get_scope(stack_idx=-1):
+def get_scope(stack_idx: int = -1):
     """Get the scope name of runtime scope stack
 
     :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
 
 
-def clear(scope=None):
+def clear(scope: str = None):
     """Clear the content of the specified 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)))
 
 
-def remove(scope=None):
+def remove(scope: str = None):
     """Remove the specified 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)))
 
 
-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
 
@@ -361,7 +361,7 @@ def _get_output_spec(type, scope, position, **other_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
 
@@ -397,7 +397,7 @@ def _put_message(color, contents, closable=False, scope=None, position=OutputPos
                       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.
 
     :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)
 
 
-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.
     .. seealso:: `put_info()`
     .. 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)
 
 
-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.
     .. seealso:: `put_info()`
     """
     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.
     .. seealso:: `put_info()`
     """
     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
 
@@ -452,7 +455,7 @@ def put_html(html, sanitize=False, scope=None, position=OutputPosition.BOTTOM) -
     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
 
@@ -504,8 +507,8 @@ def _left_strip_multiple_line_string_literal(s):
     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
 
@@ -534,6 +537,7 @@ def put_markdown(mdcontent, lstrip=True, options=None, sanitize=True,
     """
     if 'strip_indent' in kwargs:
         import warnings
+
         # 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)
 
@@ -551,7 +555,7 @@ class span_:
 
 
 @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()`
 
     :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')
-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
 
@@ -705,8 +709,9 @@ def _format_button(buttons):
     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
 
@@ -815,8 +820,8 @@ def put_buttons(buttons, onclick, small=None, link_style=False, outline=False, g
     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.
 
     :param str label: Button label
@@ -844,8 +849,8 @@ def put_button(label, onclick, color=None, small=None, link_style=False, outline
                        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
 
     :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)
 
 
-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
 
     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
 
 
-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.
 
     :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)
 
 
-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
 
     :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)
 
 
-def set_processbar(name, value, label=None):
+def set_processbar(name: str, value: float, label: str = None):
     """Set the progress of 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)
 
 
-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
 
     :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')
-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
 
     :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')
-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
 
     :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:
         import warnings
+
         # 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.",
                       DeprecationWarning, stacklevel=2)
         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
+    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,
                             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')
-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.
 
     :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')
-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
 
     :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')
-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
 
     :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')
-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
 
     :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')
-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
 
     :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')
-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
 
     :param str name:
@@ -1469,6 +1476,7 @@ def output(*contents):
     """
 
     import warnings
+
     # 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, "
                   "use `pywebio.output.put_scope()` instead", DeprecationWarning, stacklevel=2)
@@ -1530,7 +1538,7 @@ def output(*contents):
 
 
 @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
 
     .. deprecated:: 1.3
@@ -1590,7 +1598,8 @@ def style(outputs, css_style) -> Union[Output, OutputList]:
 
 
 @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.
 
@@ -1686,7 +1695,7 @@ def 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.
 
     :param str content: Notification content.
@@ -1729,7 +1738,7 @@ def toast(content, duration=2, position='center', color='info', onclick=None):
 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)
 
     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
+from typing import Any, Callable, Dict, List, Optional, Tuple, Union
 
 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
 
 _pin_name_chars = set(string.ascii_letters + string.digits + '_-')
@@ -145,8 +146,9 @@ def _pin_output(single_input_return, scope, position):
     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()`"""
     from pywebio.input import input
     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)
 
 
-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()`"""
     from pywebio.input import textarea
     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)
 
 
-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()`"""
     from pywebio.input import select
     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)
 
 
-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()`"""
     from pywebio.input import checkbox
     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)
 
 
-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()`"""
     from pywebio.input import radio
     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)
 
 
-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()`"""
     from pywebio.input import slider
     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)
 
 
-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()`
 
     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)
 
-    def __getattr__(self, name):
+    def __getattr__(self, name: str):
         """__getattr__ is only invoked if the attribute wasn't found the usual ways"""
         if name.startswith('__'):
             raise AttributeError('Pin object has no attribute %r' % name)
         return self.__getitem__(name)
 
-    def __getitem__(self, name):
+    def __getitem__(self, name: str):
         check_dom_name_value(name, 'pin `name`')
         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
         """
@@ -268,7 +275,7 @@ class Pin_:
         check_dom_name_value(name, 'pin `name`')
         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}))
 
 
@@ -276,7 +283,7 @@ class 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,
     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()
 
 
-def pin_update(name, **spec):
+def pin_update(name: str, **spec):
     """Update attributes of pin widgets.
 
     :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))
 
 
-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.
 

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

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

+ 5 - 5
pywebio/platform/django.py

@@ -102,11 +102,11 @@ urlpatterns = []
 
 
 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.
 
     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
-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
 
@@ -102,7 +102,7 @@ _app_list_tpl = template.Template("""
         {% end %}
 
         {% if meta.description %}
-            {{ meta.description }} 
+            {{ meta.description }}
         {% else %}
             <i>No description.</i>
         {% end %}

+ 1 - 1
pywebio/platform/path_deploy.py

@@ -116,7 +116,7 @@ _app_list_tpl = tornado.template.Template("""
 <h1>{{ title }}</h1>
 <hr>
 <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>
 <hr>
 </body>

+ 1 - 1
pywebio/platform/remote_access.py

@@ -20,7 +20,7 @@ success_msg = """
 ================================================================================
 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 webbrowser
 from functools import partial
+from typing import Callable, Dict, List, Optional, Union
 from urllib.parse import urlparse
 
 import tornado
@@ -206,13 +207,11 @@ def _setup_server(webio_handler, port=0, host='', static_dir=None, max_buffer_si
     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):
     """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)
 
-    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),
     # 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)):
         session: ScriptModeSession = None
-        instance: typing.ClassVar = None  # type: SingleSessionWSHandler
+        instance: typing.ClassVar = None
         closed = False
 
         def send_msg_to_client(self, session):