utils.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import urllib.parse
  2. from collections import namedtuple
  3. from collections.abc import Mapping, Sequence
  4. from functools import partial
  5. from os import path
  6. from tornado import template
  7. from ..utils import isgeneratorfunction, iscoroutinefunction, get_function_name, get_function_doc, get_function_seo_info
  8. AppMeta = namedtuple('App', 'title description')
  9. _here_dir = path.dirname(path.abspath(__file__))
  10. _index_page_tpl = template.Template(open(path.join(_here_dir, 'tpl', 'index.html')).read())
  11. def render_page(app, protocol):
  12. """渲染前端页面的HTML框架, 支持SEO
  13. :param callable app: PyWebIO app
  14. :param str protocol: 'ws'/'http'
  15. :return: bytes
  16. """
  17. assert protocol in ('ws', 'http')
  18. meta = parse_app_metadata(app)
  19. return _index_page_tpl.generate(title=meta.title or 'PyWebIO Application',
  20. description=meta.description, protocol=protocol,
  21. script=True, content='')
  22. def parse_app_metadata(func):
  23. """解析pywebio app元数据"""
  24. title, description = get_function_seo_info(func)
  25. if title:
  26. return AppMeta(title, description)
  27. doc = get_function_doc(func)
  28. parts = doc.strip().split('\n\n', 1)
  29. if len(parts) == 2:
  30. title, description = parts
  31. else:
  32. title, description = parts[0], ''
  33. return AppMeta(title, description)
  34. _app_list_tpl = template.Template("""
  35. <h1>Applications index</h1>
  36. <ul>
  37. {% for name,meta in apps_info.items() %}
  38. <li>
  39. {% if other_arguments is not None %}
  40. <a href="?app={{name}}{{other_arguments}}">{{ meta.title or name }}</a>:
  41. {% else %}
  42. <a href="javascript:WebIO.openApp('{{ name }}', true)">{{ meta.title or name }}</a>:
  43. {% end %}
  44. {% if meta.description %}
  45. {{ meta.description }}
  46. {% else %}
  47. <i>No description.</i>
  48. {% end %}
  49. </li>
  50. {% end %}
  51. </ul>
  52. """.strip())
  53. def get_static_index_content(apps, query_arguments=None):
  54. """生成默认的静态主页
  55. :param callable apps: PyWebIO apps
  56. :param str query_arguments: Url Query Arguments。为None时,表示使用WebIO.openApp跳转
  57. :return: bytes
  58. """
  59. apps_info = {
  60. name: parse_app_metadata(func)
  61. for name, func in apps.items()
  62. }
  63. qs = urllib.parse.parse_qs(query_arguments)
  64. qs.pop('app', None)
  65. other_arguments = urllib.parse.urlencode(qs, doseq=True)
  66. if other_arguments:
  67. other_arguments = '&' + other_arguments
  68. else:
  69. other_arguments = None
  70. content = _app_list_tpl.generate(apps_info=apps_info, other_arguments=other_arguments).decode('utf8')
  71. return content
  72. def _generate_default_index_app(apps):
  73. """默认的主页任务函数"""
  74. content = get_static_index_content(apps)
  75. def index():
  76. from pywebio.output import put_html
  77. put_html(content)
  78. return index
  79. def make_applications(applications):
  80. """格式化 applications 为 任务名->任务函数 的映射, 并提供默认主页
  81. :param applications: 接受 单一任务函数、字典、列表 类型
  82. :return dict: 任务名->任务函数 的映射
  83. """
  84. if isinstance(applications, Sequence): # 列表 类型
  85. applications, app_list = {}, applications
  86. for func in app_list:
  87. name = get_function_name(func)
  88. if name in applications:
  89. raise ValueError("Duplicated application name:%r" % name)
  90. applications[name] = func
  91. elif not isinstance(applications, Mapping): # 单一任务函数 类型
  92. applications = {'index': applications}
  93. # covert dict key to str
  94. applications = {str(k): v for k, v in applications.items()}
  95. for app in applications.values():
  96. assert iscoroutinefunction(app) or isgeneratorfunction(app) or callable(app), \
  97. "Don't support application type:%s" % type(app)
  98. if 'index' not in applications:
  99. applications['index'] = _generate_default_index_app(applications)
  100. return applications
  101. def seo(title, description=None, app=None):
  102. '''设置PyWebIO应用的SEO信息(在被搜索引擎索引时提供的网页信息)
  103. :param str title: 应用标题
  104. :param str description: 应用简介
  105. :param callable app: PyWebIO任务函数
  106. 可以通过装饰器或直接调用的方式使用 ``seo()`` 。
  107. 除了使用 ``seo()`` 函数,PyWebIO默认会将任务函数的函数注释作为SEO信息::
  108. @seo("title", "description")
  109. def foo():
  110. pass
  111. def bar():
  112. pass
  113. def hello():
  114. """应用标题
  115. 应用简介... (应用简介和标题之间需要使用一个空行分隔)
  116. """
  117. start_server([
  118. foo,
  119. hello,
  120. seo("title", "description", bar),
  121. ])
  122. '''
  123. if app is not None:
  124. return seo(title, description)(app)
  125. def decorator(func):
  126. try:
  127. func = partial(func)
  128. func._pywebio_title = title
  129. func._pywebio_description = description or ''
  130. except Exception:
  131. pass
  132. return func
  133. return decorator