overview-zh.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. """
  2. 使用PyWebIO来介绍PyWebIO的各个特性
  3. """
  4. import asyncio
  5. from datetime import datetime
  6. from functools import partial
  7. from pywebio.input import *
  8. from pywebio.ioloop import start_ioloop, run_async
  9. from pywebio.output import *
  10. async def other(data, save):
  11. put_text("You click %s button" % data)
  12. put_text("Button save:%s" % save)
  13. res = await input("You click %s button" % data)
  14. put_text(res)
  15. async def feature_overview():
  16. set_auto_scroll_bottom(False)
  17. set_output_fixed_height(False)
  18. set_title("PyWebIO 特性一览")
  19. put_markdown("""# PyWebIO 特性一览
  20. 你现在看到和即将看到的内容就是使用PyWebIO来创建的,"用自己来介绍自己" 是不是很有趣 😄(文末有彩蛋)
  21. ## What is PyWebIO
  22. PyWebIO,一个用于在浏览器上进行输入输出的工具库。能够将原有的通过终端交互的脚本快速服务化,供其他人在网络通过浏览器使用;PyWebIO还可以方便地整合进现有的web服务,非常适合于构建后端服务的功能原型。
  23. 特点:
  24. - 使用同步而不是基于回调的方式获取输入,无需在各个步骤之间保存状态,直观、方便
  25. - 代码侵入性小
  26. - 支持并发请求
  27. - 支持状态恢复
  28. - 支持整合到现有的web服务,目前支持与Tronado的集成
  29. 对上面的内容一脸黑人问号,没关系,下面是一些PyWebIO是什么,以及能够做什么的直观的例子
  30. ### 基本输入
  31. 首先是一些基本类型的输入
  32. #### 文本输入
  33. ```python
  34. age = await input("How old are you?", type=NUMBER) # type can be in {TEXT, NUMBER, PASSWORD}
  35. ```
  36. 这样一行代码的效果如下,浏览器会弹出一个文本输入框来获取输入,在你提交表单之前,你的程序不会往下运行
  37. """, lstrip=True)
  38. age = await input("How old are you?", type=NUMBER)
  39. put_text("你的年龄是:%s" % age)
  40. put_markdown("""#### 下拉选择框
  41. ```python
  42. gift = await select('Which gift you want?', ['keyboard', 'ipad'])
  43. ```
  44. """, lstrip=True)
  45. gift = await select('Which gift you want?', ['keyboard', 'ipad'])
  46. put_text("%s sounds great!" % gift)
  47. put_markdown("""#### CheckBox
  48. ```python
  49. agree = await checkbox("用户协议", options=['I agree to terms and conditions'])
  50. ```
  51. """, lstrip=True)
  52. agree = await checkbox("用户协议", options=[{'value': 'agree', 'label': 'I agree to terms and conditions'}])
  53. put_text("You %s to terms and conditions" % ('agree' if agree == 'agree' else 'disagree'))
  54. put_markdown("""#### Text Area
  55. ```python
  56. text = await textarea('Text Area', rows='3', placeholder='Some text')
  57. ```
  58. """, lstrip=True)
  59. text = await textarea('Text Area', rows='3', placeholder='Some text')
  60. put_text('Your input:%s' % text)
  61. put_markdown("""textarea还支持使用 <a href="https://codemirror.net/" target="_blank">Codemirror</a>实现代码风格的编辑区,只需使用`codemirror`参数传入Codemirror支持的选项:
  62. ```python
  63. code = await textarea('Code', codemirror={
  64. 'mode': "python", # 代码语言
  65. 'theme': 'darcula', # 使用darcula主题
  66. }, value='import something\n# Write your python code')
  67. ```
  68. """, lstrip=True)
  69. code = await textarea('Code', codemirror={
  70. 'mode': "python", # 代码语言
  71. 'theme': 'darcula', # 使用darcula主题
  72. }, value='import something\n# Write your python code')
  73. put_markdown('Your code:\n```python\n%s\n```' % code)
  74. put_markdown("""#### Actions
  75. ```python
  76. choice = await actions("What do you want in next?", ["Go homepage", "Quit"])
  77. ```
  78. """, lstrip=True)
  79. choice = await actions("What do you want in next?", ["Go homepage", "Quit"])
  80. put_text("You choose %s" % choice)
  81. put_markdown("""#### 文件上传
  82. ```python
  83. img = await file_upload("Select a image:", accept="image/*")
  84. ```
  85. """, lstrip=True)
  86. img = await file_upload("Select a image:", accept="image/*")
  87. put_text("Image name: %s\nImage size: %d KB" % (img['filename'], len(img['content']) / 1000))
  88. put_markdown("""### 输入选项
  89. 输入函数可指定的参数非常丰富,就比如:
  90. ```python
  91. await input('Help Text', type=TEXT, help_text='This is help text')
  92. ```
  93. """, lstrip=True)
  94. await input('Help Text', type=TEXT, help_text='This is help text')
  95. put_markdown("""```python
  96. await input('Placeholder', type=TEXT, placeholder='This is placeholder')
  97. ```
  98. """, lstrip=True)
  99. await input('Placeholder', type=TEXT, placeholder='This is placeholder')
  100. put_markdown("""```python
  101. await input('Readonly', type=TEXT, readonly=True, value="You can't change me")
  102. ```
  103. """, lstrip=True)
  104. await input('Readonly', type=TEXT, readonly=True, value="You can't change me")
  105. put_markdown("""我们可以为输入指定校验函数,校验函数校验通过时返回None,否则返回错误消息:
  106. ```python
  107. def check_age(p): # 检验函数校验通过时返回None,否则返回错误消息
  108. if p < 10:
  109. return 'Too young!!'
  110. if p > 60:
  111. return 'Too old!!'
  112. age = await input("How old are you?", type=NUMBER, valid_func=check_age)
  113. ```
  114. """, strip_indent=4)
  115. def check_age(p): # 检验函数校验通过时返回None,否则返回错误消息
  116. if p < 18:
  117. return 'Too young!!'
  118. if p > 60:
  119. return 'Too old!!'
  120. age = await input("How old are you?", type=NUMBER, valid_func=check_age, help_text='你可以输入一些不合法的数字(比如10)来查看错误提示的效果')
  121. put_markdown("""### 输入组
  122. PyWebIO还支持一组输入, 返回结果为一个字典。input_group接受前面的单项输入组成的列表作为参数,同时为了在返回的结果中区别出每一项输入,还需要在单项输入函数中传入`name`参数,input_group返回的字典就是以单项输入函数中的`name`作为键。
  123. ```python
  124. data = await input_group("Basic info",[
  125. input('Input your name', name='name'),
  126. input('Input your age', name='age', type=NUMBER, valid_func=check_age)
  127. ], valid_func=check_form)
  128. print(data['name'], data['age'])
  129. ```
  130. 输入组中同样支持设置校验函数,其接受整个表单数据作为参数:
  131. ```python
  132. def check_form(data): # 检验函数校验通过时返回None,否则返回 (input name,错误消息)
  133. if len(data['name']) > 6:
  134. return ('name', '名字太长!')
  135. if data['age'] <= 0:
  136. return ('age', '年龄不能为负数!')
  137. ```
  138. """, strip_indent=4)
  139. def check_form(data): # 检验函数校验通过时返回None,否则返回 (input name,错误消息)
  140. """返回 (name, error_msg) 表示输入错误""" # todo 也可返回单独error_msg表示错误消息
  141. if len(data['name']) > 6:
  142. return ('name', '名字太长!')
  143. if data['age'] <= 0:
  144. return ('age', '年龄不能为负数!')
  145. data = await input_group("Basic info", [
  146. input('Input your name', name='name'),
  147. input('Input your age', name='age', type=NUMBER, valid_func=check_age)
  148. ], valid_func=check_form)
  149. put_text('Your name:%s\nYour age:%d' % (data['name'], data['age']))
  150. put_markdown("""### 输出
  151. PyWebIO也提供了一些便捷函数来输出表格,链接等格式
  152. #### 基本输出
  153. 首先是文本输出:
  154. ```python
  155. # 文本输出
  156. put_text("Hello world!")
  157. # 表格输出
  158. put_table([
  159. ['商品', '价格'],
  160. ['苹果', '5.5'],
  161. ['香蕉', '7'],
  162. ])
  163. # Markdown输出
  164. put_markdown('~~删除线~~')
  165. # 文件输出
  166. put_file('hello_word.txt', b'hello word!')
  167. ```
  168. """, strip_indent=4)
  169. put_text("Hello world!")
  170. put_table([
  171. ['商品', '价格'],
  172. ['苹果', '5.5'],
  173. ['香蕉', '7'],
  174. ])
  175. put_markdown('~~删除线~~')
  176. put_file('hello_word.txt', b'hello word!')
  177. put_markdown("""#### 输出事件
  178. 通过刚刚的体验,相信聪明的你已经大概了解:PyWebIO可以通过调用不同的输入函数在浏览器中获取用户的输入,并且通过浏览器展示程序的输出。并且一旦调用 `await some_input_func()`,在表单提交之前程序将不会往下运行。
  179. 这种模式已经可以满足绝大部分的交互需求了,但是在某些场景下还是显得不太方便,就比如你通过表格输出了用户的登陆日志,用户可能希望对表格的某些行进行编辑或者对表格什么也不做,这个时候,你可能会使用一个`while`循环,并且在循环中调用`choice = await actions("What do you want in next?", ["Edit some rows", "Back"])`,如果用户选择了"Edit some rows",你还要接着询问用户希望编辑哪些行......,emm,想想就头大。
  180. 幸运的是,PyWebIO还支持输出可以绑定事件的按钮控件,非常适合上述场景的需求。
  181. 上述场景通过按钮控件实现如下:
  182. ```python
  183. from functools import partial
  184. def edit_row(choice, row):
  185. put_text("You click %s button ar row %s" % (choice, row))
  186. put_table([
  187. ['Idx', 'Actions'],
  188. [1, td_buttons(['edit', 'delete'], onclick=partial(edit_row, row=1))],
  189. [2, td_buttons(['edit', 'delete'], onclick=partial(edit_row, row=2))],
  190. [3, td_buttons(['edit', 'delete'], onclick=partial(edit_row, row=3))],
  191. ])
  192. ```
  193. """, strip_indent=4)
  194. def edit_row(choice, row):
  195. put_text("You click %s button ar row %s" % (choice, row))
  196. put_table([
  197. ['Idx', 'Actions'],
  198. [1, td_buttons(['edit', 'delete'], onclick=partial(edit_row, row=1))],
  199. [2, td_buttons(['edit', 'delete'], onclick=partial(edit_row, row=2))],
  200. [3, td_buttons(['edit', 'delete'], onclick=partial(edit_row, row=3))],
  201. ])
  202. put_markdown("""这样,你不必等待用户点击某个按钮,而是可以继续往下运行程序,当用户点击了某行中的按钮时,程序会自动调用相应的处理函数\n
  203. 当然,PyWebIO还支持单独的按钮控件:
  204. ```python
  205. def btn_click(btn_val):
  206. put_text("You click btn_val button" % btn_val)
  207. put_buttons(['A', 'B', 'C'], onclick=btn_click)
  208. ```
  209. """, strip_indent=4)
  210. def btn_click(btn_val):
  211. put_text("You click %s button" % btn_val)
  212. put_buttons(['A', 'B', 'C'], onclick=btn_click)
  213. await actions('', ['继续教程'])
  214. put_markdown("""#### 锚点
  215. 你可以调用`set_anchor(name)`对当前输出位置进行标记,这一调用不会在用户浏览器上产生任何输出,需要与下面几个函数结合使用:
  216. 调用`set_anchor(name)`可以清除anchor锚点之前输出的内容
  217. 调用`clear_after(name)`可以清除anchor锚点之后输出的内容
  218. 调用`clear_range(start_anchor, end_ancher)`可以清除start_anchor到end_ancher锚点之间的内容
  219. 调用`scroll_to(name)`可以将页面滚动到anchor锚点处
  220. """, strip_indent=4)
  221. set_anchor('anchor')
  222. put_markdown("""这个例子展示了锚点的一个用法:
  223. ```python
  224. import asyncio
  225. from datetime import datetime
  226. set_anchor('counter')
  227. for i in range(15, -1, -1):
  228. clear_after('counter')
  229. put_text('倒计时:%s' % i)
  230. await asyncio.sleep(1) # 睡眠一秒钟
  231. ```
  232. """, strip_indent=4)
  233. await actions('点击开始示例', ['开始示例'])
  234. set_anchor('counter')
  235. for i in range(5, -1, -1):
  236. clear_after('counter')
  237. put_text('倒计时:%s' % i)
  238. await asyncio.sleep(1) # 睡眠一秒钟
  239. put_markdown("""#### 环境设置
  240. ##### 输出区外观
  241. PyWebIO支持两种外观:输出区固定高度/可变高度。
  242. 可以通过调用`set_output_fixed_height(True)`来开启输出区固定高度。\n
  243. 你现在看到的是输出区可变高度的形态,你可以点击下面的按钮来切换外观。
  244. """, strip_indent=4)
  245. put_buttons([
  246. {'label': '输出区固定高度', 'value': 'fixed'},
  247. {'label': '输出区可变高度', 'value': 'no-fix'}
  248. ], lambda i, _: set_output_fixed_height(i == 'fixed'), small=True)
  249. put_markdown("""不过你最好在程序一开始就设置好输出区外观,否则你可能就会像现在这样手足无措~
  250. 调用`set_title(title)`可以设置标题。\n
  251. """, strip_indent=4)
  252. async def set_title_btn(data, save):
  253. title = await input("Input title")
  254. set_title(title)
  255. put_buttons(['设置标题'], onclick=set_title_btn)
  256. await actions('', ['继续教程'])
  257. put_markdown("""##### 自动滚动
  258. 通过调用`set_auto_scroll_bottom(True)`来开启自动滚动,当有新内容输出时会自动将页面滚动到底部。\n
  259. """, strip_indent=4)
  260. put_buttons([
  261. {'label': '开启自动滚动', 'value': 'enable'},
  262. {'label': '关闭自动滚动', 'value': 'disable'}
  263. ], lambda i, _: set_auto_scroll_bottom(i == 'enable'), small=True)
  264. put_markdown("""#### Async
  265. 由于PyWebIO是基于Tornado构建的,而Tornado又与Python标准库<a href="https://docs.python.org/3/library/asyncio.html" target="_blank">asyncio</a>兼容,所以在PyWebIO中,你也可以运行`asyncio`中的协程函数
  266. 这一点其实在上文已经出现过了,不记得了?
  267. """, strip_indent=4)
  268. put_buttons(['点此穿越🚀'], onclick=lambda a, b: scroll_to('anchor'))
  269. #
  270. put_markdown("""
  271. 上文中的例子,之所以要使用asyncio中的sleep函数而不是Python `time`标准库中的sleep函数,是因为Tornado以及`asyncio`实际上是一个单线程模型,当前协程当进行一些需要等待的操作时,可以使用`await`让出程序控制权,框架会选择协程授予执行控制权,而调用`time.sleep`并不会让出程序控制权,因此在程序等待的间隔内,其他协程无法得到执行。更具体的关于协程以及asyncio的讨论已经超出了PyWebIO的范畴,你可以取互联网搜索相关内容来进行了解。
  272. 回到PyWebIO,你也可以`await`自己编写的协程函数
  273. ```python
  274. import asyncio
  275. async def request():
  276. http_client = AsyncHTTPClient()
  277. response = await http_client.fetch("http://example.com")
  278. put_text(response.body)
  279. return response
  280. response = await request()
  281. ```
  282. `run_async`允许你在一个协程函数中在后台启动另一个协程函数,不会像使用`await`一样阻塞当前协程,当前协程可以继续往下执行。
  283. ```python
  284. import asyncio
  285. from datetime import datetime
  286. async def show_time():
  287. text = await input("来自后台协程的输入请求", placeholder='随便输入点啥')
  288. put_text('你刚刚输入了:%s' % text)
  289. for _ in range(10):
  290. put_text('来自后台协程的报时:%s' % datetime.now())
  291. await asyncio.sleep(1)
  292. run_async(show_time())
  293. for i in range(5, -1, -1):
  294. put_text('来自主协程的倒计时:%s' % i)
  295. await asyncio.sleep(1)
  296. ```
  297. 在新生成的协程内,依然可以调用输入函数,若用户当前已经有正在展示的输入表单,则会被新生成的表单替换,但是旧表单不会被销毁,旧表单的输入状态也会保留,当新表单提交后,旧输入表单会重新呈现给用户。
  298. """, strip_indent=4)
  299. async def show_time():
  300. text = await input("来自后台协程的输入请求", placeholder='随便输入点啥')
  301. put_text('你刚刚输入了:%s' % text)
  302. for _ in range(10):
  303. put_text('来自后台协程的报时:%s' % datetime.now())
  304. await asyncio.sleep(1)
  305. await actions('', ['运行run_async(show_time())'])
  306. run_async(show_time())
  307. for i in range(15, -1, -1):
  308. put_text('来自主协程的倒计时:%s' % i)
  309. await asyncio.sleep(1)
  310. await asyncio.sleep(2)
  311. put_markdown("""
  312. <hr/>
  313. 以上大概就是 PyWebIO 的所有特性了,如果觉得还不错的话,可以 Give me a 🌟 in <a href="https://github.com/wang0618/PyWebIO" target="_blank">Github</a>
  314. PS: <a href="https://github.com/wang0618/PyWebIO/blob/master/pywebio/demos/overview-zh.py" target="_blank">在这里</a>你可以找到生成本页面的脚本
  315. PPS:开头提到的彩蛋揭晓:"用自己来介绍自己"很具计算机领域风格,对此发挥至极的是<a href="https://en.wikipedia.org/wiki/Quine_(computing)" target="_blank">Quine</a>的概念,"A quine is a program which prints a copy of its own as the only output. "
  316. """, strip_indent=4)
  317. start_ioloop(feature_overview, debug=True)