wangweimin 3 лет назад
Родитель
Сommit
ee4933f93b

+ 1 - 1
pywebio/__init__.py

@@ -9,7 +9,7 @@ from .__version__ import __description__, __url__, __version__
 from .exceptions import SessionException, SessionClosedException, SessionNotFoundException
 from .platform import start_server
 from .platform.bokeh import try_install_bokeh_hook
-from .platform.utils import config
+from .platform.page import config
 from .utils import STATIC_PATH
 
 try_install_bokeh_hook()

+ 1 - 1
pywebio/input.py

@@ -80,7 +80,7 @@ import copy
 from .io_ctrl import single_input, input_control, output_register_callback, send_msg, single_input_kwargs
 from .session import get_current_session, get_current_task_id
 from .utils import Setter, is_html_safe_value, parse_file_size
-from .platform import utils as platform_setting
+from .platform import page as platform_setting
 
 logger = logging.getLogger(__name__)
 

+ 3 - 3
pywebio/platform/__init__.py

@@ -145,7 +145,7 @@ Other
 """
 
 from .httpbased import run_event_loop
-from .tornado import start_server
-from .utils import seo
-from .utils import config
+from .page import config
+from .page import seo
 from .path_deploy import path_deploy_http, path_deploy
+from .tornado import start_server

+ 2 - 1
pywebio/platform/aiohttp.py

@@ -11,7 +11,8 @@ from aiohttp import web
 
 from .remote_access import start_remote_access_service
 from .tornado import open_webbrowser_on_server_started
-from .utils import make_applications, render_page, cdn_validation, deserialize_binary_event, print_listen_address
+from .page import make_applications, render_page
+from .utils import cdn_validation, deserialize_binary_event, print_listen_address
 from ..session import CoroutineBasedSession, ThreadBasedSession, register_session_implement_for_target, Session
 from ..session.base import get_session_info_from_headers
 from ..utils import get_free_port, STATIC_PATH, iscoroutinefunction, isgeneratorfunction

+ 4 - 3
pywebio/platform/django.py

@@ -5,11 +5,12 @@ import threading
 
 from django.http import HttpResponse, HttpRequest
 
-from . import utils
+from . import page
 from ..session import Session
 from .httpbased import HttpContext, HttpHandler, run_event_loop
 from .remote_access import start_remote_access_service
-from .utils import make_applications, cdn_validation
+from .page import make_applications
+from .utils import cdn_validation
 from ..utils import STATIC_PATH, iscoroutinefunction, isgeneratorfunction, get_free_port, parse_file_size
 
 logger = logging.getLogger(__name__)
@@ -122,7 +123,7 @@ def wsgi_app(applications, cdn=True,
     debug = Session.debug = os.environ.get('PYWEBIO_DEBUG', debug)
 
     max_payload_size = parse_file_size(max_payload_size)
-    utils.MAX_PAYLOAD_SIZE = max_payload_size
+    page.MAX_PAYLOAD_SIZE = max_payload_size
 
     django_options.update(dict(
         DEBUG=debug,

+ 2 - 1
pywebio/platform/fastapi.py

@@ -14,7 +14,8 @@ from starlette.websockets import WebSocketDisconnect
 
 from .remote_access import start_remote_access_service
 from .tornado import open_webbrowser_on_server_started
-from .utils import make_applications, render_page, cdn_validation, OriginChecker, deserialize_binary_event
+from .page import make_applications, render_page
+from .utils import cdn_validation, OriginChecker, deserialize_binary_event
 from ..session import CoroutineBasedSession, ThreadBasedSession, register_session_implement_for_target, Session
 from ..session.base import get_session_info_from_headers
 from ..utils import get_free_port, STATIC_PATH, iscoroutinefunction, isgeneratorfunction, strip_space

+ 4 - 3
pywebio/platform/flask.py

@@ -8,11 +8,12 @@ import threading
 
 from flask import Flask, request, send_from_directory, Response
 
-from . import utils
+from . import page
 from ..session import Session
 from .httpbased import HttpContext, HttpHandler, run_event_loop
 from .remote_access import start_remote_access_service
-from .utils import make_applications, cdn_validation
+from .page import make_applications
+from .utils import cdn_validation
 from ..utils import STATIC_PATH, iscoroutinefunction, isgeneratorfunction
 from ..utils import get_free_port, parse_file_size
 
@@ -114,7 +115,7 @@ def wsgi_app(applications, cdn=True,
 
     app = Flask(__name__) if static_dir is None else Flask(__name__, static_url_path="/static",
                                                            static_folder=static_dir)
-    utils.MAX_PAYLOAD_SIZE = app.config['MAX_CONTENT_LENGTH'] = parse_file_size(max_payload_size)
+    page.MAX_PAYLOAD_SIZE = app.config['MAX_CONTENT_LENGTH'] = parse_file_size(max_payload_size)
 
     app.add_url_rule('/', 'webio_view', webio_view(
         applications=applications, cdn=cdn,

+ 2 - 1
pywebio/platform/httpbased.py

@@ -16,7 +16,8 @@ import time
 from contextlib import contextmanager
 from typing import Dict
 
-from .utils import make_applications, render_page, deserialize_binary_event
+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.base import get_session_info_from_headers
 from ..utils import random_str, LRUDict, isgeneratorfunction, iscoroutinefunction, check_webio_js

+ 308 - 0
pywebio/platform/page.py

@@ -0,0 +1,308 @@
+import urllib.parse
+from collections import namedtuple
+from collections.abc import Mapping, Sequence
+from functools import lru_cache
+from functools import partial
+from os import path, environ
+
+from tornado import template
+
+from ..__version__ import __version__ as version
+from ..utils import isgeneratorfunction, iscoroutinefunction, get_function_name, get_function_doc, \
+    get_function_attr, STATIC_PATH
+
+"""
+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. 
+"""
+MAX_PAYLOAD_SIZE = 0
+
+DEFAULT_CDN = "https://cdn.jsdelivr.net/gh/wang0618/PyWebIO-assets@v{version}/"
+
+_global_config = {'title': 'PyWebIO Application'}
+config_keys = ['title', 'description', 'js_file', 'js_code', 'css_style', 'css_file', 'theme']
+AppMeta = namedtuple('App', config_keys)
+
+_here_dir = path.dirname(path.abspath(__file__))
+_index_page_tpl = template.Template(open(path.join(_here_dir, 'tpl', 'index.html'), encoding='utf8').read())
+
+
+def render_page(app, protocol, cdn):
+    """渲染前端页面的HTML框架, 支持SEO
+
+    :param callable app: PyWebIO app
+    :param str protocol: 'ws'/'http'
+    :param bool/str cdn: Whether to use CDN, also accept string as custom CDN URL
+    :return: bytes content of rendered page
+    """
+    assert protocol in ('ws', 'http')
+    meta = parse_app_metadata(app)
+    if cdn is True:
+        base_url = DEFAULT_CDN.format(version=version)
+    elif not cdn:
+        base_url = ''
+    else:  # user custom cdn
+        base_url = cdn.rstrip('/') + '/'
+
+    theme = environ.get('PYWEBIO_THEME', meta.theme) or 'default'
+    check_theme(theme)
+
+    return _index_page_tpl.generate(title=meta.title, description=meta.description, protocol=protocol,
+                                    script=True, content='', base_url=base_url,
+                                    js_file=meta.js_file or [], js_code=meta.js_code, css_style=meta.css_style,
+                                    css_file=meta.css_file or [], theme=theme)
+
+
+@lru_cache(maxsize=64)
+def check_theme(theme):
+    """check theme file existence"""
+    if not theme:
+        return
+
+    theme_file = path.join(STATIC_PATH, 'css', 'bs-theme', theme + '.min.css')
+    if not path.isfile(theme_file):
+        raise RuntimeError("Can't find css file for theme `%s`" % theme)
+
+
+def parse_app_metadata(func):
+    """Get metadata form pywebio task function, fallback to global config in empty meta field."""
+    prefix = '_pywebio_'
+    attrs = get_function_attr(func, [prefix + k for k in config_keys])
+    meta = AppMeta(**{k: attrs.get(prefix + k) for k in config_keys})
+
+    doc = get_function_doc(func)
+    parts = doc.strip().split('\n\n', 1)
+    if len(parts) == 2:
+        title, description = parts
+    else:
+        title, description = parts[0], ''
+
+    if not meta.title:
+        meta = meta._replace(title=title, description=description)
+
+    # fallback to global config
+    for key in config_keys:
+        if not getattr(meta, key, None) and _global_config.get(key):
+            kwarg = {key: _global_config.get(key)}
+            meta = meta._replace(**kwarg)
+
+    return meta
+
+
+_app_list_tpl = template.Template("""
+<h1>Applications index</h1>
+<ul>
+    {% for name,meta in apps_info.items() %}
+    <li>
+        {% if other_arguments is not None %}
+            <a href="?app={{name}}{{other_arguments}}">{{ meta.title or name }}</a>:
+        {% else %}
+            <a href="javascript:WebIO.openApp('{{ name }}', true)">{{ meta.title or name }}</a>:
+        {% end %}
+
+        {% if meta.description %}
+            {{ meta.description }} 
+        {% else %}
+            <i>No description.</i>
+        {% end %}
+    </li>
+    {% end %}
+</ul>
+""".strip())
+
+
+def get_static_index_content(apps, query_arguments=None):
+    """生成默认的静态主页
+
+    :param callable apps: PyWebIO apps
+    :param str query_arguments: Url Query Arguments。为None时,表示使用WebIO.openApp跳转
+    :return: bytes
+    """
+    apps_info = {
+        name: parse_app_metadata(func)
+        for name, func in apps.items()
+    }
+
+    qs = urllib.parse.parse_qs(query_arguments)
+    qs.pop('app', None)
+    other_arguments = urllib.parse.urlencode(qs, doseq=True)
+
+    if other_arguments:
+        other_arguments = '&' + other_arguments
+    else:
+        other_arguments = None
+    content = _app_list_tpl.generate(apps_info=apps_info, other_arguments=other_arguments).decode('utf8')
+    return content
+
+
+def _generate_default_index_app(apps):
+    """默认的主页任务函数"""
+    content = get_static_index_content(apps)
+
+    def index():
+        from pywebio.output import put_html
+        put_html(content)
+
+    return index
+
+
+def make_applications(applications):
+    """格式化 applications 为 任务名->任务函数 的映射, 并提供默认主页
+
+    :param applications: 接受 单一任务函数、字典、列表 类型
+    :return dict: 任务名->任务函数 的映射
+    """
+
+    if isinstance(applications, Sequence):  # 列表 类型
+        applications, app_list = {}, applications
+        for func in app_list:
+            name = get_function_name(func)
+            if name in applications:
+                raise ValueError("Duplicated application name:%r" % name)
+            applications[name] = func
+    elif not isinstance(applications, Mapping):  # 单一任务函数 类型
+        applications = {'index': applications}
+
+    # convert dict key to str
+    applications = {str(k): v for k, v in applications.items()}
+
+    for app in applications.values():
+        assert iscoroutinefunction(app) or isgeneratorfunction(app) or callable(app), \
+            "Don't support application type:%s" % type(app)
+
+    if 'index' not in applications:
+        applications['index'] = _generate_default_index_app(applications)
+
+    return applications
+
+
+def seo(title, description=None, app=None):
+    """Set the SEO information of the PyWebIO application (web page information provided when indexed by search engines)
+
+    :param str title: Application title
+    :param str description: Application description
+    :param callable app: PyWebIO task function
+
+    If ``seo()`` is not used, the `docstring <https://www.python.org/dev/peps/pep-0257/>`_ of the task function will be regarded as SEO information by default.
+
+    ``seo()`` can be used in 2 ways: direct call and decorator::
+
+        @seo("title", "description")
+        def foo():
+            pass
+
+        def bar():
+            pass
+
+        def hello():
+            \"""Application title
+
+            Application description...
+            (A empty line is used to separate the description and title)
+            \"""
+
+        start_server([
+            foo,
+            hello,
+            seo("title", "description", bar),
+        ])
+
+    .. versionadded:: 1.1
+    .. deprecated:: 1.4
+        Use :func:`pywebio.config` instead.
+    """
+    import warnings
+    warnings.warn("`pywebio.platform.seo()` is deprecated since v1.4 and will remove in the future version, "
+                  "use `pywebio.config` instead", DeprecationWarning, stacklevel=2)
+
+    if app is not None:
+        return config(title=title, description=description)(app)
+
+    return config(title=title, description=description)
+
+
+def config(*, title=None, description=None, theme=None, js_code=None, js_file=[], css_style=None, css_file=[]):
+    """PyWebIO application configuration
+
+    :param str title: Application title
+    :param str description: Application description
+    :param str theme: Application theme. Available themes are: ``dark``, ``sketchy``, ``minty``, ``yeti``.
+        You can also use environment variable ``PYWEBIO_THEME`` to specify the theme (with high priority).
+
+        :demo_host:`Theme preview demo </theme>`
+
+        .. collapse:: Open Source Credits
+
+            The dark theme is modified from ForEvolve's `bootstrap-dark <https://github.com/ForEvolve/bootstrap-dark>`_.
+            The sketchy, minty and yeti theme are from `bootswatch <https://bootswatch.com/4/>`_.
+
+    :param str js_code: The javascript code that you want to inject to page.
+    :param str/list js_file: The javascript files that inject to page, can be a URL in str or a list of it.
+    :param str css_style: The CSS style that you want to inject to page.
+    :param str/list css_file: The CSS files that inject to page, can be a URL in str or a list of it.
+
+    ``config()`` can be used in 2 ways: direct call and decorator.
+    If you call ``config()`` directly, the configuration will be global.
+    If you use ``config()`` as decorator, the configuration will only work on single PyWebIO application function.
+    ::
+
+        config(title="My application")
+
+        @config(css_style="* { color:red }")
+        def app():
+            put_text("hello PyWebIO")
+
+    ``title`` and ``description`` are used for SEO, which are provided when indexed by search engines.
+    If no ``title`` and ``description`` set for a PyWebIO application function,
+    the `docstring <https://www.python.org/dev/peps/pep-0257/>`_ of the function will be used as title and description by default::
+
+        def app():
+            \"""Application title
+
+            Application description...
+            (A empty line is used to separate the description and title)
+            \"""
+            pass
+
+    The above code is equal to::
+
+        @config(title="Application title", description="Application description...")
+        def app():
+            pass
+
+    .. versionadded:: 1.4
+
+    .. versionchanged:: 1.5
+       add ``theme`` parameter
+    """
+    if isinstance(js_file, str):
+        js_file = [js_file]
+    if isinstance(css_file, str):
+        css_file = [css_file]
+
+    configs = locals()
+
+    class Decorator:
+        def __init__(self):
+            self.called = False
+
+        def __call__(self, func):
+            self.called = True
+            try:
+                func = partial(func)  # to make a copy of the function
+                for key, val in configs.items():
+                    if val:
+                        setattr(func, '_pywebio_%s' % key, val)
+            except Exception:
+                pass
+            return func
+
+        def __del__(self):  # if not called as decorator, set the config to global
+            if self.called:
+                return
+
+            global _global_config
+            _global_config = configs
+
+    return Decorator()

+ 5 - 4
pywebio/platform/path_deploy.py

@@ -8,11 +8,12 @@ from tornado import template
 from tornado.web import HTTPError, Finish
 from tornado.web import StaticFileHandler
 
-from . import utils
+from . import page
 from .httpbased import HttpHandler
 from .tornado import webio_handler, set_ioloop
 from .tornado_http import TornadoHttpContext
-from .utils import cdn_validation, make_applications, print_listen_address
+from .utils import cdn_validation, print_listen_address
+from .page import make_applications
 from ..session import register_session_implement, CoroutineBasedSession, ThreadBasedSession, Session
 from ..utils import get_free_port, STATIC_PATH, parse_file_size
 
@@ -246,7 +247,7 @@ def path_deploy(base, port=0, host='',
     The rest arguments of ``path_deploy()`` have the same meaning as for :func:`pywebio.platform.tornado.start_server`
     """
     debug = Session.debug = os.environ.get('PYWEBIO_DEBUG', debug)
-    utils.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)
     # Since some cloud server may close idle connections (such as heroku),
     # use `websocket_ping_interval` to  keep the connection alive
     tornado_app_settings.setdefault('websocket_ping_interval', 30)
@@ -303,7 +304,7 @@ def path_deploy_http(base, port=0, host='',
     """
     debug = Session.debug = os.environ.get('PYWEBIO_DEBUG', debug)
 
-    utils.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)
 
     gen = _path_deploy(base, port=port, host=host,
                        static_dir=static_dir,

+ 4 - 3
pywebio/platform/tornado.py

@@ -16,9 +16,10 @@ import tornado.ioloop
 from tornado.web import StaticFileHandler
 from tornado.websocket import WebSocketHandler
 
-from . import utils
+from . import page
 from .remote_access import start_remote_access_service
-from .utils import make_applications, render_page, cdn_validation, deserialize_binary_event, print_listen_address
+from .page import make_applications, render_page
+from .utils import cdn_validation, deserialize_binary_event, print_listen_address
 from ..session import CoroutineBasedSession, ThreadBasedSession, ScriptModeSession, \
     register_session_implement_for_target, Session
 from ..session.base import get_session_info_from_headers
@@ -345,7 +346,7 @@ def start_server(applications, port=0, host='',
 
     cdn = cdn_validation(cdn, 'warn')  # if CDN is not available, warn user and disable CDN
 
-    utils.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)
 

+ 2 - 2
pywebio/platform/tornado_http.py

@@ -5,7 +5,7 @@ import logging
 import tornado.ioloop
 import tornado.web
 
-from . import utils
+from . import page
 from ..session import Session
 from .httpbased import HttpContext, HttpHandler
 from .tornado import set_ioloop, _setup_server, open_webbrowser_on_server_started
@@ -140,7 +140,7 @@ def start_server(applications, port=8080, host='',
 
     cdn = cdn_validation(cdn, 'warn')  # if CDN is not available, warn user and disable CDN
 
-    utils.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)
 

+ 0 - 306
pywebio/platform/utils.py

@@ -3,71 +3,9 @@ import json
 import socket
 import urllib.parse
 from collections import defaultdict
-from collections import namedtuple
-from collections.abc import Mapping, Sequence
-from functools import lru_cache
-from functools import partial
-from os import path, environ
-
-from tornado import template
 
 from ..__version__ import __version__ as version
 from ..exceptions import PyWebIOWarning
-from ..utils import isgeneratorfunction, iscoroutinefunction, get_function_name, get_function_doc, \
-    get_function_attr, STATIC_PATH
-
-"""
-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. 
-"""
-MAX_PAYLOAD_SIZE = 0
-
-DEFAULT_CDN = "https://cdn.jsdelivr.net/gh/wang0618/PyWebIO-assets@v{version}/"
-
-_global_config = {'title': 'PyWebIO Application'}
-config_keys = ['title', 'description', 'js_file', 'js_code', 'css_style', 'css_file', 'theme']
-AppMeta = namedtuple('App', config_keys)
-
-_here_dir = path.dirname(path.abspath(__file__))
-_index_page_tpl = template.Template(open(path.join(_here_dir, 'tpl', 'index.html'), encoding='utf8').read())
-
-
-def render_page(app, protocol, cdn):
-    """渲染前端页面的HTML框架, 支持SEO
-
-    :param callable app: PyWebIO app
-    :param str protocol: 'ws'/'http'
-    :param bool/str cdn: Whether to use CDN, also accept string as custom CDN URL
-    :return: bytes content of rendered page
-    """
-    assert protocol in ('ws', 'http')
-    meta = parse_app_metadata(app)
-    if cdn is True:
-        base_url = DEFAULT_CDN.format(version=version)
-    elif not cdn:
-        base_url = ''
-    else:  # user custom cdn
-        base_url = cdn.rstrip('/') + '/'
-
-    theme = environ.get('PYWEBIO_THEME', meta.theme) or 'default'
-    check_theme(theme)
-
-    return _index_page_tpl.generate(title=meta.title, description=meta.description, protocol=protocol,
-                                    script=True, content='', base_url=base_url,
-                                    js_file=meta.js_file or [], js_code=meta.js_code, css_style=meta.css_style,
-                                    css_file=meta.css_file or [], theme=theme)
-
-
-@lru_cache(maxsize=64)
-def check_theme(theme):
-    """check theme file existence"""
-    if not theme:
-        return
-
-    theme_file = path.join(STATIC_PATH, 'css', 'bs-theme', theme + '.min.css')
-    if not path.isfile(theme_file):
-        raise RuntimeError("Can't find css file for theme `%s`" % theme)
 
 
 def cdn_validation(cdn, level='warn', stacklevel=3):
@@ -91,118 +29,6 @@ def cdn_validation(cdn, level='warn', stacklevel=3):
     return cdn
 
 
-def parse_app_metadata(func):
-    """Get metadata form pywebio task function, fallback to global config in empty meta field."""
-    prefix = '_pywebio_'
-    attrs = get_function_attr(func, [prefix + k for k in config_keys])
-    meta = AppMeta(**{k: attrs.get(prefix + k) for k in config_keys})
-
-    doc = get_function_doc(func)
-    parts = doc.strip().split('\n\n', 1)
-    if len(parts) == 2:
-        title, description = parts
-    else:
-        title, description = parts[0], ''
-
-    if not meta.title:
-        meta = meta._replace(title=title, description=description)
-
-    # fallback to global config
-    for key in config_keys:
-        if not getattr(meta, key, None) and _global_config.get(key):
-            kwarg = {key: _global_config.get(key)}
-            meta = meta._replace(**kwarg)
-
-    return meta
-
-
-_app_list_tpl = template.Template("""
-<h1>Applications index</h1>
-<ul>
-    {% for name,meta in apps_info.items() %}
-    <li>
-        {% if other_arguments is not None %}
-            <a href="?app={{name}}{{other_arguments}}">{{ meta.title or name }}</a>:
-        {% else %}
-            <a href="javascript:WebIO.openApp('{{ name }}', true)">{{ meta.title or name }}</a>:
-        {% end %}
-        
-        {% if meta.description %}
-            {{ meta.description }} 
-        {% else %}
-            <i>No description.</i>
-        {% end %}
-    </li>
-    {% end %}
-</ul>
-""".strip())
-
-
-def get_static_index_content(apps, query_arguments=None):
-    """生成默认的静态主页
-
-    :param callable apps: PyWebIO apps
-    :param str query_arguments: Url Query Arguments。为None时,表示使用WebIO.openApp跳转
-    :return: bytes
-    """
-    apps_info = {
-        name: parse_app_metadata(func)
-        for name, func in apps.items()
-    }
-
-    qs = urllib.parse.parse_qs(query_arguments)
-    qs.pop('app', None)
-    other_arguments = urllib.parse.urlencode(qs, doseq=True)
-
-    if other_arguments:
-        other_arguments = '&' + other_arguments
-    else:
-        other_arguments = None
-    content = _app_list_tpl.generate(apps_info=apps_info, other_arguments=other_arguments).decode('utf8')
-    return content
-
-
-def _generate_default_index_app(apps):
-    """默认的主页任务函数"""
-    content = get_static_index_content(apps)
-
-    def index():
-        from pywebio.output import put_html
-        put_html(content)
-
-    return index
-
-
-def make_applications(applications):
-    """格式化 applications 为 任务名->任务函数 的映射, 并提供默认主页
-
-    :param applications: 接受 单一任务函数、字典、列表 类型
-    :return dict: 任务名->任务函数 的映射
-    """
-
-    if isinstance(applications, Sequence):  # 列表 类型
-        applications, app_list = {}, applications
-        for func in app_list:
-            name = get_function_name(func)
-            if name in applications:
-                raise ValueError("Duplicated application name:%r" % name)
-            applications[name] = func
-    elif not isinstance(applications, Mapping):  # 单一任务函数 类型
-        applications = {'index': applications}
-
-    # convert dict key to str
-    applications = {str(k): v for k, v in applications.items()}
-
-    for app in applications.values():
-        assert iscoroutinefunction(app) or isgeneratorfunction(app) or callable(app), \
-            "Don't support application type:%s" % type(app)
-
-    if 'index' not in applications:
-        applications['index'] = _generate_default_index_app(applications)
-
-    return applications
-
-
 class OriginChecker:
 
     @classmethod
@@ -315,135 +141,3 @@ def print_listen_address(host, port):
         print('Use http://%s:%s/ to access the application' % (host, port))
     else:
         print('Running on http://%s:%s/' % (host, port))
-
-
-
-def seo(title, description=None, app=None):
-    """Set the SEO information of the PyWebIO application (web page information provided when indexed by search engines)
-
-    :param str title: Application title
-    :param str description: Application description
-    :param callable app: PyWebIO task function
-
-    If ``seo()`` is not used, the `docstring <https://www.python.org/dev/peps/pep-0257/>`_ of the task function will be regarded as SEO information by default.
-
-    ``seo()`` can be used in 2 ways: direct call and decorator::
-
-        @seo("title", "description")
-        def foo():
-            pass
-
-        def bar():
-            pass
-
-        def hello():
-            \"""Application title
-
-            Application description...
-            (A empty line is used to separate the description and title)
-            \"""
-
-        start_server([
-            foo,
-            hello,
-            seo("title", "description", bar),
-        ])
-
-    .. versionadded:: 1.1
-    .. deprecated:: 1.4
-        Use :func:`pywebio.config` instead.
-    """
-    import warnings
-    warnings.warn("`pywebio.platform.seo()` is deprecated since v1.4 and will remove in the future version, "
-                  "use `pywebio.config` instead", DeprecationWarning, stacklevel=2)
-
-    if app is not None:
-        return config(title=title, description=description)(app)
-
-    return config(title=title, description=description)
-
-
-def config(*, title=None, description=None, theme=None, js_code=None, js_file=[], css_style=None, css_file=[]):
-    """PyWebIO application configuration
-
-    :param str title: Application title
-    :param str description: Application description
-    :param str theme: Application theme. Available themes are: ``dark``, ``sketchy``, ``minty``, ``yeti``.
-        You can also use environment variable ``PYWEBIO_THEME`` to specify the theme (with high priority).
-
-        :demo_host:`Theme preview demo </theme>`
-
-        .. collapse:: Open Source Credits
-
-            The dark theme is modified from ForEvolve's `bootstrap-dark <https://github.com/ForEvolve/bootstrap-dark>`_.
-            The sketchy, minty and yeti theme are from `bootswatch <https://bootswatch.com/4/>`_.
-
-    :param str js_code: The javascript code that you want to inject to page.
-    :param str/list js_file: The javascript files that inject to page, can be a URL in str or a list of it.
-    :param str css_style: The CSS style that you want to inject to page.
-    :param str/list css_file: The CSS files that inject to page, can be a URL in str or a list of it.
-
-    ``config()`` can be used in 2 ways: direct call and decorator.
-    If you call ``config()`` directly, the configuration will be global.
-    If you use ``config()`` as decorator, the configuration will only work on single PyWebIO application function.
-    ::
-
-        config(title="My application")
-
-        @config(css_style="* { color:red }")
-        def app():
-            put_text("hello PyWebIO")
-
-    ``title`` and ``description`` are used for SEO, which are provided when indexed by search engines.
-    If no ``title`` and ``description`` set for a PyWebIO application function,
-    the `docstring <https://www.python.org/dev/peps/pep-0257/>`_ of the function will be used as title and description by default::
-
-        def app():
-            \"""Application title
-
-            Application description...
-            (A empty line is used to separate the description and title)
-            \"""
-            pass
-
-    The above code is equal to::
-
-        @config(title="Application title", description="Application description...")
-        def app():
-            pass
-
-    .. versionadded:: 1.4
-
-    .. versionchanged:: 1.5
-       add ``theme`` parameter
-    """
-    if isinstance(js_file, str):
-        js_file = [js_file]
-    if isinstance(css_file, str):
-        css_file = [css_file]
-
-    configs = locals()
-
-    class Decorator:
-        def __init__(self):
-            self.called = False
-
-        def __call__(self, func):
-            self.called = True
-            try:
-                func = partial(func)  # to make a copy of the function
-                for key, val in configs.items():
-                    if val:
-                        setattr(func, '_pywebio_%s' % key, val)
-            except Exception:
-                pass
-            return func
-
-        def __del__(self):  # if not called as decorator, set the config to global
-            if self.called:
-                return
-
-            global _global_config
-            _global_config = configs
-
-    return Decorator()