1
0

cookbook.rst 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. Cookbook
  2. ==========================
  3. .. contents::
  4. :local:
  5. Interaction related
  6. ----------------------------------------------------------------------------------------------
  7. Equivalent to "Press any key to continue"
  8. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  9. .. exportable-codeblock::
  10. :name: cookbook-press-anykey-continue
  11. :summary: Press any key to continue
  12. actions(buttons=["Continue"])
  13. put_text("Go next") # ..demo-only
  14. Output pandas dataframe
  15. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  16. .. exportable-codeblock::
  17. :name: cookbook-pandas-df
  18. :summary: Output pandas dataframe
  19. import numpy as np
  20. import pandas as pd
  21. df = pd.DataFrame(np.random.randn(6, 4), columns=list("ABCD"))
  22. put_html(df.to_html(border=0))
  23. .. seealso:: `pandas.DataFrame.to_html — pandas documentation <https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_html.html#pandas-dataframe-to-html>`_
  24. Output Matplotlib figure
  25. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  26. Simply do not call ``matplotlib.pyplot.show``, directly save the figure to in-memory buffer and output the buffer
  27. via :func:`pywebio.output.put_image`:
  28. .. exportable-codeblock::
  29. :name: cookbook-matplotlib
  30. :summary: Output Matplotlib plot
  31. import matplotlib
  32. import matplotlib.pyplot as plt
  33. import io
  34. import pywebio
  35. matplotlib.use('agg') # required, use a non-interactive backend
  36. fig, ax = plt.subplots() # Create a figure containing a single axes.
  37. ax.plot([1, 2, 3, 4], [1, 4, 2, 3]) # Plot some data on the axes.
  38. buf = io.BytesIO()
  39. fig.savefig(buf)
  40. pywebio.output.put_image(buf.getvalue())
  41. The ``matplotlib.use('agg')`` is required so that the server does not try to create (and then destroy) GUI windows
  42. that will never be seen.
  43. When using Matplotlib in a web server (multiple threads environment), pyplot may cause some conflicts in some cases,
  44. read the following articles for more information:
  45. * `Multi Threading in Python and Pyplot | by Ranjitha Korrapati | Medium <https://medium.com/@ranjitha.korrapati/multi-threading-in-python-and-pyplot-46f325e6a9d0>`_
  46. * `Embedding in a web application server (Flask) — Matplotlib documentation <https://matplotlib.org/stable/gallery/user_interfaces/web_application_server_sgskip.html>`_
  47. Blocking confirm model
  48. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  49. The following code uses the lock mechanism to make the button callback function synchronous:
  50. .. collapse:: Click to expand the code
  51. .. exportable-codeblock::
  52. :name: cookbook-confirm-model
  53. :summary: Blocking confirm model
  54. import threading
  55. from pywebio import output
  56. def confirm(title, content=None, timeout=None):
  57. """Show a confirm model.
  58. :param str title: Model title.
  59. :param list/put_xxx() content: Model content.
  60. :param None/float timeout: Seconds for operation time out.
  61. :return: Return `True` when the "CONFIRM" button is clicked,
  62. return `False` when the "CANCEL" button is clicked,
  63. return `None` when a timeout is given and the operation times out.
  64. """
  65. if not isinstance(content, list):
  66. content = [content]
  67. event = threading.Event()
  68. result = None
  69. def onclick(val):
  70. nonlocal result
  71. result = val
  72. event.set()
  73. content.append(output.put_buttons([
  74. {'label': 'CONFIRM', 'value': True},
  75. {'label': 'CANCEL', 'value': False, 'color': 'danger'},
  76. ], onclick=onclick))
  77. output.popup(title=title, content=content, closable=False)
  78. event.wait(timeout=timeout) # wait the model buttons are clicked
  79. output.close_popup()
  80. return result
  81. res = confirm('Confirm', 'You have 5 seconds to make s choice', timeout=5)
  82. output.put_text("Your choice is:", res)
  83. Input in the popup
  84. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  85. .. https://github.com/pywebio/PyWebIO/discussions/132
  86. In the following code, we define a ``popup_input()`` function, which can be used to get input in popup:
  87. .. collapse:: Click to expand the code
  88. .. exportable-codeblock::
  89. :name: cookbook-redirect-stdout
  90. :summary: Redirect stdout to PyWebIO
  91. import threading
  92. def popup_input(pins, names, title='Please fill out the form'):
  93. """Show a form in popup window.
  94. :param list pins: pin output list.
  95. :param list pins: pin name list.
  96. :param str title: model title.
  97. :return: return the form as dict, return None when user cancel the form.
  98. """
  99. if not isinstance(pins, list):
  100. pins = [pins]
  101. event = threading.Event()
  102. confirmed_form = None
  103. def onclick(val):
  104. nonlocal confirmed_form
  105. confirmed_form = val
  106. event.set()
  107. pins.append(put_buttons([
  108. {'label': 'Submit', 'value': True},
  109. {'label': 'Cancel', 'value': False, 'color': 'danger'},
  110. ], onclick=onclick))
  111. popup(title=title, content=pins, closable=False)
  112. event.wait()
  113. close_popup()
  114. if not confirmed_form:
  115. return None
  116. from pywebio.pin import pin
  117. return {name: pin[name] for name in names}
  118. from pywebio.pin import put_input
  119. result = popup_input([
  120. put_input('name', label='Input your name'),
  121. put_input('age', label='Input your age', type="number")
  122. ], names=['name', 'age'])
  123. put_text(result)
  124. The code uses :doc:`pin module </pin>` to add input widgets to popup window,
  125. and uses the lock mechanism to wait the form buttons to be clicked.
  126. Redirect stdout to PyWebIO application
  127. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  128. .. https://github.com/pywebio/PyWebIO/discussions/21
  129. The following code shows how to redirect stdout of python code and subprocess to PyWebIO application:
  130. .. collapse:: Click to expand the code
  131. .. exportable-codeblock::
  132. :name: cookbook-redirect-stdout
  133. :summary: Redirect stdout to PyWebIO
  134. import io
  135. import time
  136. import subprocess # ..doc-only
  137. from contextlib import redirect_stdout
  138. # redirect `print()` to pywebio
  139. class WebIO(io.IOBase):
  140. def write(self, content):
  141. put_text(content, inline=True)
  142. with redirect_stdout(WebIO()):
  143. for i in range(10):
  144. print(i, time.time())
  145. time.sleep(0.2)
  146. ## ----
  147. import subprocess # ..demo-only
  148. # redirect a subprocess' stdout to pywebio
  149. process = subprocess.Popen("ls -ahl", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
  150. while True:
  151. output = process.stdout.readline()
  152. if output == '' and process.poll() is not None:
  153. break
  154. if output:
  155. put_text(output.decode('utf8'), inline=True)
  156. Add missing syntax highlight for code output
  157. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  158. When output code via `put_markdown()` or `put_code()`, PyWebIO provides syntax highlight for some common languages.
  159. If you find your code have no syntax highlight, you can add the syntax highlighter by two following steps:
  160. 1. Go to `prismjs CDN page <https://www.jsdelivr.com/package/npm/prismjs?version=1.23.0&path=components>`_ to get your syntax highlighter link.
  161. 2. Use :func:`config(js_file=...) <pywebio.config>` to load the syntax highlight module
  162. ::
  163. @config(js_file="https://cdn.jsdelivr.net/npm/prismjs@1.23.0/components/prism-diff.min.js")
  164. def main():
  165. put_code("""
  166. + AAA
  167. - BBB
  168. CCC
  169. """.strip(), language='diff')
  170. put_markdown("""
  171. ```diff
  172. + AAA
  173. - BBB
  174. CCC
  175. ```
  176. """, lstrip=True)
  177. Web application related
  178. ----------------------------------------------------------------------------------------------
  179. Get URL parameters of current page
  180. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  181. You can use URL parameter (known also as "query strings" or "URL query parameters") to pass information to your web
  182. application. In PyWebIO application, you can use the following code to get the URL parameters as a Python dict.
  183. .. exportable-codeblock::
  184. :name: cookbook-url-query
  185. :summary: Get URL parameters of current page
  186. # `query` is a dict
  187. query = eval_js("Object.fromEntries(new URLSearchParams(window.location.search))")
  188. put_text(query)
  189. Add Google AdSense/Analytics code
  190. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  191. When you setup Google AdSense/Analytics, you will get a javascript file and a piece of code that needs to be inserted
  192. into your application page, you can use :func:`pywebio.config()` to inject js file and code to your PyWebIO application::
  193. from pywebio import start_server, output, config
  194. js_file = "https://www.googletagmanager.com/gtag/js?id=G-xxxxxxx"
  195. js_code = """
  196. window.dataLayer = window.dataLayer || [];
  197. function gtag(){dataLayer.push(arguments);}
  198. gtag('js', new Date());
  199. gtag('config', 'G-xxxxxxx');
  200. """
  201. @config(js_file=js_file, js_code=js_code)
  202. def main():
  203. output.put_text("hello world")
  204. start_server(main, port=8080)
  205. Refresh page on connection lost
  206. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  207. Add the following code to the beginning of your PyWebIO application main function::
  208. session.run_js('WebIO._state.CurrentSession.on_session_close(()=>{setTimeout(()=>location.reload(), 4000})')
  209. Cookie and localStorage manipulation
  210. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  211. .. https://github.com/pywebio/PyWebIO/discussions/99
  212. You can use `pywebio.session.run_js()` and `pywebio.session.eval_js()` to deal with cookies or localStorage with js.
  213. ``localStorage`` manipulation:
  214. .. exportable-codeblock::
  215. :name: cookbook-localStorage
  216. :summary: ``localStorage`` manipulation
  217. set_localstorage = lambda key, value: run_js("localStorage.setItem(key, value)", key=key, value=value)
  218. get_localstorage = lambda key: eval_js("localStorage.getItem(key)", key=key)
  219. set_localstorage('hello', 'world')
  220. val = get_localstorage('hello')
  221. put_text(val)
  222. Cookie manipulation:
  223. .. collapse:: Click to expand the code
  224. .. exportable-codeblock::
  225. :name: cookbook-cookie
  226. :summary: Cookie manipulation
  227. # https://stackoverflow.com/questions/14573223/set-cookie-and-get-cookie-with-javascript
  228. run_js("""
  229. window.setCookie = function(name,value,days) {
  230. var expires = "";
  231. if (days) {
  232. var date = new Date();
  233. date.setTime(date.getTime() + (days*24*60*60*1000));
  234. expires = "; expires=" + date.toUTCString();
  235. }
  236. document.cookie = name + "=" + (value || "") + expires + "; path=/";
  237. }
  238. window.getCookie = function(name) {
  239. var nameEQ = name + "=";
  240. var ca = document.cookie.split(';');
  241. for(var i=0;i < ca.length;i++) {
  242. var c = ca[i];
  243. while (c.charAt(0)==' ') c = c.substring(1,c.length);
  244. if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
  245. }
  246. return null;
  247. }
  248. """)
  249. def setcookie(key, value, days=0):
  250. run_js("setCookie(key, value, days)", key=key, value=value, days=days)
  251. def getcookie(key):
  252. return eval_js("getCookie(key)", key=key)
  253. setcookie('hello', 'world')
  254. val = getcookie('hello')
  255. put_text(val)