guide.rst 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075
  1. User's guide
  2. ============
  3. If you are familiar with web development, you may not be accustomed to the usage of PyWebIO described below, which is different from the traditional web development patton that backend implement api and frontend display content. In PyWebIO, you only need write code in Python.
  4. In fact, the way of writing PyWebIO applications is more like writing a console program, except that the terminal here becomes a browser. Using the imperative API provided by PyWebIO,
  5. you can simply call ``put_text``, ``put_image``, ``put_table`` and other functions to output text, pictures, tables and other content to the browser, or you can call some functions such as ``input``, ``select``, ``file_upload`` to display different forms on the browser to get user input. In addition, PyWebIO also provides support for click events, layout, etc. PyWebIO aims to allow you to use the least code to interact with the user and provide a good user experience as much as possible.
  6. This user guide introduces you the most of the features of PyWebIO. There is a demo link at the top right of the most of the example codes in this document, where you can preview the running effect of the code online.
  7. Input
  8. ------------
  9. The input functions are defined in the :doc:`pywebio.input </input>` module and can be imported using ``from pywebio.input import *``.
  10. When calling the input function, an input form will be popped up on the browser. PyWebIO's input functions is blocking (same as Python's built-in ``input()`` function) and will not return until the form is successfully submitted.
  11. Basic input
  12. ^^^^^^^^^^^^^
  13. Here are some basic types of input.
  14. Text input:
  15. .. exportable-codeblock::
  16. :name: text-input
  17. :summary: Text input
  18. age = input("How old are you?", type=NUMBER)
  19. put_text('age = %r' % age) # ..demo-only
  20. After running the above code, the browser will pop up a text input field to get the input. After the user completes the input and submits the form, the function returns the value entered by the user.
  21. Here are some other types of input functions:
  22. .. exportable-codeblock::
  23. :name: basic-input
  24. :summary: Basic input
  25. # Password input
  26. password = input("Input password", type=PASSWORD)
  27. put_text('password = %r' % password) # ..demo-only
  28. ## ----
  29. # Drop-down selection
  30. gift = select('Which gift you want?', ['keyboard', 'ipad'])
  31. put_text('gift = %r' % gift) # ..demo-only
  32. ## ----
  33. # Checkbox
  34. agree = checkbox("User Term", options=['I agree to terms and conditions'])
  35. put_text('agree = %r' % agree) # ..demo-only
  36. ## ----
  37. # Single choice
  38. answer = radio("Choose one", options=['A', 'B', 'C', 'D'])
  39. put_text('answer = %r' % answer) # ..demo-only
  40. ## ----
  41. # Multi-line text input
  42. text = textarea('Text Area', rows=3, placeholder='Some text')
  43. put_text('text = %r' % text) # ..demo-only
  44. ## ----
  45. # File Upload
  46. img = file_upload("Select a image:", accept="image/*")
  47. if img: # ..demo-only
  48. put_image(img['content'], title=img['filename']) # ..demo-only
  49. Parameter of input functions
  50. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  51. There are many parameters that can be passed to the input function(for complete parameters, please refer to the :doc:`function document </input>`):
  52. .. exportable-codeblock::
  53. :name: input-args
  54. :summary: Parameter of input functions
  55. input('This is label', type=TEXT, placeholder='This is placeholder',
  56. help_text='This is help text', required=True)
  57. The results of the above example are as follows:
  58. .. image:: /assets/input_1.png
  59. You can specify a validation function for the input by using ``validate`` parameter. The validation function should return ``None`` when the check passes, otherwise an error message will be returned:
  60. .. exportable-codeblock::
  61. :name: input-valid-func
  62. :summary: Input validate function for
  63. def check_age(p): # return None when the check passes, otherwise return the error message
  64. if p < 10:
  65. return 'Too young!!'
  66. if p > 60:
  67. return 'Too old!!'
  68. age = input("How old are you?", type=NUMBER, validate=check_age)
  69. put_text('age = %r' % age) # ..demo-only
  70. When the user input an illegal value, the input field is displayed as follows:
  71. .. image:: /assets/input_2.png
  72. You can use ``code`` parameter in :func:`pywebio.input.textarea()` to make a code editing textarea. This feature uses `Codemirror <https://codemirror.net/>`_ as underlying implementation. The ``code`` parameter accept the Codemirror options as a dict.
  73. .. exportable-codeblock::
  74. :name: codemirror
  75. :summary: Code editing by using textarea
  76. code = textarea('Code Edit', code={
  77. 'mode': "python", # code language
  78. 'theme': 'darcula', # Codemirror theme. Visit https://codemirror.net/demo/theme.html#cobalt to get more themes
  79. }, value='import something\n# Write your python code')
  80. put_code(code, language='python') # ..demo-only
  81. The results of the above example are as follows:
  82. .. image:: /assets/codemirror_textarea.png
  83. :ref:`Here <codemirror_options>` are some commonly used Codemirror options. For complete Codemirror options, please visit: https://codemirror.net/doc/manual.html#config
  84. Input Group
  85. ^^^^^^^^^^^^^
  86. PyWebIO uses input group to get multiple inputs in a single form. `pywebio.input.input_group()` accepts a list of single input function call as parameter, and returns a dictionary with the ``name`` of the single input function as the key and the input data as the value:
  87. .. exportable-codeblock::
  88. :name: input-group
  89. :summary: Input Group
  90. def check_age(p): # ..demo-only
  91. if p < 10: # ..demo-only
  92. return 'Too young!!' # ..demo-only
  93. if p > 60: # ..demo-only
  94. return 'Too old!!' # ..demo-only
  95. # ..demo-only
  96. data = input_group("Basic info",[
  97. input('Input your name', name='name'),
  98. input('Input your age', name='age', type=NUMBER, validate=check_age)
  99. ])
  100. put_text(data['name'], data['age'])
  101. The input group also supports using ``validate`` parameter to set the validation function, which accepts the entire form data as parameter:
  102. .. exportable-codeblock::
  103. :name: input-group
  104. :summary: Input Group
  105. def check_age(p): # single input item validation # ..demo-only
  106. if p < 10: # ..demo-only
  107. return 'Too young!!' # ..demo-only
  108. if p > 60: # ..demo-only
  109. return 'Too old!!' # ..demo-only
  110. # ..demo-only
  111. def check_form(data): # input group validation: return (input name, error msg) when validation fail
  112. if len(data['name']) > 6:
  113. return ('name', 'Name too long!')
  114. if data['age'] <= 0:
  115. return ('age', 'Age can not be negative!')
  116. data = input_group("Basic info",[ # ..demo-only
  117. input('Input your name', name='name'), # ..demo-only
  118. input('Input your age', name='age', type=NUMBER, validate=check_age) # ..demo-only
  119. ], validate=check_form) # ..demo-only
  120. put_text(data['name'], data['age']) # ..demo-only
  121. .. attention::
  122. PyWebIO determines whether the input function is in `input_group` or is called alone according to whether the ``name`` parameter is passed. So when calling an input function alone, **do not** set the ``name`` parameter; when calling the input function in `input_group`, you **must** provide the ``name`` parameter.
  123. Output
  124. ------------
  125. The output functions are all defined in the :doc:`pywebio.output </output>` module and can be imported using ``from pywebio.output import *``.
  126. When output functions is called, the content will be output to the browser in real time. The output functions can be called at any time during the application lifetime.
  127. Basic Output
  128. ^^^^^^^^^^^^^^
  129. PyWebIO provides a series of functions to output text, tables, links, etc:
  130. .. exportable-codeblock::
  131. :name: basic-output
  132. :summary: Basic Output
  133. # Text Output
  134. put_text("Hello world!")
  135. ## ----
  136. # Table Output
  137. put_table([
  138. ['Commodity', 'Price'],
  139. ['Apple', '5.5'],
  140. ['Banana', '7'],
  141. ])
  142. ## ----
  143. # Markdown Output
  144. put_markdown('~~Strikethrough~~')
  145. ## ----
  146. # File Output
  147. put_file('hello_word.txt', b'hello word!')
  148. ## ----
  149. # PopUp Output
  150. popup('popup title', 'popup text content')
  151. For all output functions provided by PyWebIO, please refer to the :doc:`pywebio.output </output>` module. In addition, PyWebIO also supports data visualization with some third-party libraries, see :doc:`Third-party library ecology </libraries_support>`.
  152. .. note::
  153. If you use PyWebIO in interactive execution environment of Python shell, IPython or jupyter notebook,
  154. you need call `show()` method explicitly to show output::
  155. >>> put_text("Hello world!").show()
  156. >>> put_table([
  157. ... ['A', 'B'],
  158. ... [put_markdown(...), put_text('C')]
  159. ... ]).show()
  160. .. _combine_output:
  161. Combined Output
  162. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  163. The output functions whose name starts with ``put_`` can be combined with some output functions as part of the final output:
  164. You can pass ``put_xxx()`` calls to `put_table() <pywebio.output.put_table>` as cell content:
  165. .. exportable-codeblock::
  166. :name: putxxx
  167. :summary: Combination output
  168. put_table([
  169. ['Type', 'Content'],
  170. ['html', put_html('X<sup>2</sup>')],
  171. ['text', '<hr/>'], # equal to ['text', put_text('<hr/>')]
  172. ['buttons', put_buttons(['A', 'B'], onclick=...)], # ..doc-only
  173. ['buttons', put_buttons(['A', 'B'], onclick=put_text)], # ..demo-only
  174. ['markdown', put_markdown('`Awesome PyWebIO!`')],
  175. ['file', put_file('hello.text', b'hello world')],
  176. ['table', put_table([['A', 'B'], ['C', 'D']])]
  177. ])
  178. The results of the above example are as follows:
  179. .. image:: /assets/put_table.png
  180. Similarly, you can pass ``put_xxx()`` calls to `popup() <pywebio.output.popup>` as the popup content:
  181. .. exportable-codeblock::
  182. :name: popup
  183. :summary: Popup
  184. popup('Popup title', [
  185. put_html('<h3>Popup Content</h3>'),
  186. 'plain html: <br/>', # Equivalent to: put_text('plain html: <br/>')
  187. put_table([['A', 'B'], ['C', 'D']]),
  188. put_buttons(['close_popup()'], onclick=lambda _: close_popup())
  189. ])
  190. In addition, you can use `put_widget() <pywebio.output.put_widget>` to make your own output widgets that can accept ``put_xxx()`` calls.
  191. For a full list of functions that accept ``put_xxx()`` calls as content, see :ref:`Output functions list <output_func_list>`
  192. **Placeholder**
  193. When using combination output, if you want to dynamically update the ``put_xxx()`` content after it has been output, you can use the `output() <pywebio.output.output>` function. `output() <pywebio.output.output>` is like a placeholder, it can be passed in anywhere that ``put_xxx()`` can passed in. And after being output, the content can also be modified:
  194. .. exportable-codeblock::
  195. :name: output
  196. :summary: Output placeholder——`output()`
  197. hobby = output('Coding') # equal to output(put_text('Coding'))
  198. put_table([
  199. ['Name', 'Hobbies'],
  200. ['Wang', hobby] # hobby is initialized to Coding
  201. ])
  202. ## ----
  203. hobby.reset('Movie') # hobby is reset to Movie
  204. ## ----
  205. hobby.append('Music', put_text('Drama')) # append Music, Drama to hobby
  206. ## ----
  207. hobby.insert(0, put_markdown('**Coding**')) # insert the Coding into the top of the hobby
  208. **Context Manager**
  209. Some output functions that accept ``put_xxx()`` calls as content can be used as context manager:
  210. .. exportable-codeblock::
  211. :name: output-context-manager
  212. :summary: Output as context manager
  213. with put_collapse('This is title'):
  214. for i in range(4):
  215. put_text(i)
  216. put_table([
  217. ['Commodity', 'Price'],
  218. ['Apple', '5.5'],
  219. ['Banana', '7'],
  220. ])
  221. For a full list of functions that support context manager, see :ref:`Output functions list <output_func_list>`
  222. Callback
  223. ^^^^^^^^^^^^^^
  224. As we can see from the above, the interaction of PyWebIO has two parts: input and output. The input function of PyWebIO is blocking, a form will be displayed on the user's web browser when calling input function, the input function will not return until the user submits the form. The output function is used to output content to the browser in real time. The input/output behavior of PyWebIO is consistent with the console program. That's why we say PyWebIO turning the browser into a "rich text terminal". So you can write PyWebIO applications in script programing way.
  225. In addition, PyWebIO also supports event callbacks: PyWebIO allows you to output some buttons and bind callbacks to them. The provided callback function will be executed when the button is clicked.
  226. This is an example:
  227. .. exportable-codeblock::
  228. :name: onclick-callback
  229. :summary: Event callback
  230. from functools import partial
  231. def edit_row(choice, row):
  232. put_text("You click %s button ar row %s" % (choice, row))
  233. put_table([
  234. ['Idx', 'Actions'],
  235. [1, put_buttons(['edit', 'delete'], onclick=partial(edit_row, row=1))],
  236. [2, put_buttons(['edit', 'delete'], onclick=partial(edit_row, row=2))],
  237. [3, put_buttons(['edit', 'delete'], onclick=partial(edit_row, row=3))],
  238. ])
  239. The call to `put_table() <pywebio.output.put_table>` will not block. When user clicks a button, the corresponding callback function will be invoked:
  240. .. image:: /assets/table_onclick.*
  241. Of course, PyWebIO also supports outputting individual button:
  242. .. exportable-codeblock::
  243. :name: put-buttons
  244. :summary: Event callback of button widget
  245. def btn_click(btn_val):
  246. put_text("You click %s button" % btn_val)
  247. put_buttons(['A', 'B', 'C'], onclick=btn_click)
  248. .. note::
  249. After the PyWebIO session (see :ref:`Server and script mode <server_and_script_mode>` for more information about session) closed, the event callback will not work. You can call the :func:`pywebio.session.hold()` function at the end of the task function to hold the session, so that the event callback will always be available before the browser page is closed by user.
  250. Output Scope
  251. ^^^^^^^^^^^^^^
  252. PyWebIO uses the scope model to give more control to the location of content output. The output area of PyWebIO can be divided into different output domains. The output domain is called Scope in PyWebIO.
  253. The output domain is a container of output content, and each output domain is arranged vertically, and the output domains can also be nested.
  254. Each output function (function name like ``put_xxx()``) will output its content to a scope, the default is "current scope". "current scope" is determined by the runtime context. The output function can also manually specify the scope to output. The scope name is unique within the session.
  255. .. _use_scope:
  256. **use_scope()**
  257. You can use `use_scope() <pywebio.output.use_scope>` to open and enter a new output scope, or enter an existing output scope:
  258. .. exportable-codeblock::
  259. :name: use-scope
  260. :summary: use `use_scope()` to open or enter scope
  261. with use_scope('scope1'): # open and enter a new output: 'scope1'
  262. put_text('text1 in scope1') # output text to scope1
  263. put_text('text in parent scope of scope1') # output text to ROOT scope
  264. with use_scope('scope1'): # enter an existing scope: 'scope1'
  265. put_text('text2 in scope1') # output text to scope1
  266. The results of the above code are as follows::
  267. text1 in scope1
  268. text2 in scope1
  269. text in parent scope of scope1
  270. You can use ``clear`` parameter in `use_scope() <pywebio.output.use_scope>` to clear the previous content in the scope:
  271. .. exportable-codeblock::
  272. :name: use-scope
  273. :summary: `use_scope()`'s `clear` parameter
  274. with use_scope('scope2'):
  275. put_text('create scope2')
  276. put_text('text in parent scope of scope2')
  277. ## ----
  278. with use_scope('scope2', clear=True): # enter the existing scope and clear the previous content
  279. put_text('text in scope2')
  280. The results of the above code are as follows::
  281. text in scope2
  282. text in parent scope of scope2
  283. `use_scope() <pywebio.output.use_scope>` can also be used as a decorator:
  284. .. exportable-codeblock::
  285. :name: use-scope-decorator
  286. :summary: `use_scope()` as decorator
  287. import time # ..demo-only
  288. from datetime import datetime
  289. @use_scope('time', clear=True)
  290. def show_time():
  291. put_text(datetime.now())
  292. while 1: # ..demo-only
  293. show_time() # ..demo-only
  294. time.sleep(1) # ..demo-only
  295. When calling ``show_time()`` for the first time, a ``time`` scope will be created, and the current time will be output to it. And then every time the ``show_time()`` is called, the new content will replace the previous content.
  296. Scopes can be nested. At the beginning, PyWebIO applications have only one ``ROOT`` Scope. Each time a new scope is created, the nesting level of the scope will increase by one level, and each time the current scope is exited, the nesting level of the scope will be reduced by one. PyWebIO uses the Scope stack to save the scope nesting level at runtime.
  297. For example, the following code will create 3 scopes:
  298. .. exportable-codeblock::
  299. :name: use-scope-nested
  300. :summary: Nested Scope
  301. with use_scope('A'):
  302. put_text('Text in scope A')
  303. with use_scope('B'):
  304. put_text('Text in scope B')
  305. with use_scope('C'):
  306. put_text('Text in scope C')
  307. put_html("""<style> # ..demo-only
  308. #pywebio-scope-A {border: 1px solid red;} # ..demo-only
  309. #pywebio-scope-B {border: 1px solid blue;margin:2px} # ..demo-only
  310. #pywebio-scope-C {border: 1px solid green;margin-top:2px} # ..demo-only
  311. </style>""") # ..demo-only
  312. put_text() # ..demo-only
  313. put_buttons([('Put text to %s' % i, i) for i in ('A', 'B', 'C')], lambda s: put_text(s, scope=s)) # ..demo-only
  314. The above code will generate the following scope layout::
  315. ┌─ROOT────────────────────┐
  316. │ │
  317. │ ┌─A───────────────────┐ │
  318. │ │ Text in scope A │ │
  319. │ │ ┌─B───────────────┐ │ │
  320. │ │ │ Text in scope B │ │ │
  321. │ │ └─────────────────┘ │ │
  322. │ └─────────────────────┘ │
  323. │ │
  324. │ ┌─C───────────────────┐ │
  325. │ │ Text in scope C │ │
  326. │ └─────────────────────┘ │
  327. └─────────────────────────┘
  328. .. _scope_param:
  329. **Scope related parameters of output function**
  330. The output function (function name like ``put_xxx()``) will output the content to the "current scope" by default, and the "current scope" of the runtime context can be set by ``use_scope()``.
  331. In addition, you can use the ``scope`` parameter of the output function to specify the destination scope to output:
  332. .. exportable-codeblock::
  333. :name: put-xxx-scope
  334. :summary: ``scope`` parameter of the output function
  335. with use_scope('scope3'):
  336. put_text('text1 in scope3') # output to current scope: scope3
  337. put_text('text in ROOT scope', scope='ROOT') # output to ROOT Scope
  338. put_text('text2 in scope3', scope='scope3') # output to scope3
  339. The results of the above code are as follows::
  340. text1 in scope3
  341. text2 in scope3
  342. text in ROOT scope
  343. In addition to directly specifying the target scope name, the ``scope`` parameter can also accept an integer to determine the scope by indexing the scope stack: 0 means the top level scope(the ROOT Scope), -1 means the current scope, -2 means the scope used before entering the current scope, ...
  344. By default, the content output to the same scope will be arranged from top to bottom according to the calling order of the output function. The output content can be inserted into other positions of the target scope by using the ``position`` parameter of the output function.
  345. Each output item in a scope has an index, the first item's index is 0, and the next item's index is incremented by one. You can also use a negative number to index the items in the scope, -1 means the last item, -2 means the item before the last...
  346. The ``position`` parameter of output functions accepts an integer. When ``position>=0``, it means to insert content before the item whose index equal ``position``; when ``position<0``, it means to insert content after the item whose index equal ``position``:
  347. .. exportable-codeblock::
  348. :name: put-xxx-position
  349. :summary: `position` parameter of the output function
  350. with use_scope('scope1'):
  351. put_text('A')
  352. ## ----
  353. with use_scope('scope1'): # ..demo-only
  354. put_text('B', position=0) # insert B before A -> B A
  355. ## ----
  356. with use_scope('scope1'): # ..demo-only
  357. put_text('C', position=-2) # insert C after B -> B C A
  358. ## ----
  359. with use_scope('scope1'): # ..demo-only
  360. put_text('D', position=1) # insert D before C B -> B D C A
  361. **Scope control**
  362. In addition to `use_scope() <pywebio.output.use_scope>`, PyWebIO also provides the following scope control functions:
  363. * `set_scope(name) <pywebio.output.set_scope>` : Create scope at current location(or specified location)
  364. * `clear(scope) <pywebio.output.clear>` : Clear the contents of the scope
  365. * `remove(scope) <pywebio.output.remove>` : Remove scope
  366. * `scroll_to(scope) <pywebio.output.scroll_to>` : Scroll the page to the scope
  367. Page environment settings
  368. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  369. **Page Title**
  370. You can call `set_env(title=...) <pywebio.session.set_env>` to set the page title。
  371. **Auto Scroll**
  372. When performing some continuous output (such as log output), you may want to scroll the page to the bottom automatically when there is new output. You can call `set_env(auto_scroll_bottom=True) <pywebio.session.set_env>` to enable automatic scrolling. Note that when enabled, only outputting to ROOT scope can trigger automatic scrolling.
  373. **Output Animation**
  374. By default, PyWebIO will use the fade-in animation effect to display the content. You can use `set_env(output_animation=False) <pywebio.session.set_env>` to turn off the animation.
  375. To view the effects of environment settings, please visit :demo_host:`set_env Demo </set_env_demo>`
  376. Layout
  377. ^^^^^^^^^^^^^^
  378. In general, using the output functions introduced above is enough to output what you want, but these outputs are arranged vertically. If you want to create a more complex layout (such as displaying a code block on the left side of the page and an image on the right), you need to use layout functions.
  379. The ``pywebio.output`` module provides 3 layout functions, and you can create complex layouts by combining them:
  380. * `put_row() <pywebio.output.put_row>` : Use row layout to output content. The content is arranged horizontally
  381. * `put_column() <pywebio.output.put_column>` : Use column layout to output content. The content is arranged vertically
  382. * `put_grid() <pywebio.output.put_grid>` : Output content using grid layout
  383. Here is an example by combining ``put_row()`` and ``put_column()``:
  384. .. exportable-codeblock::
  385. :name: put-row-column
  386. :summary: Layout functions
  387. put_row([
  388. put_column([
  389. put_code('A'),
  390. put_row([
  391. put_code('B1'), None, # None represents the space between the output
  392. put_code('B2'), None,
  393. put_code('B3'),
  394. ]),
  395. put_code('C'),
  396. ]), None,
  397. put_code('D'), None,
  398. put_code('E')
  399. ])
  400. The results of the above example are as follows:
  401. .. image:: /assets/layout.png
  402. :align: center
  403. The layout function also supports customizing the size of each part::
  404. put_row([put_image(...), put_image(...)], size='40% 60%') # The ratio of the width of two images is 2:3
  405. For more information, please refer to the :ref:`layout functions documentation <style_and_layout>`.
  406. .. _style:
  407. Style
  408. ^^^^^^^^^^^^^^
  409. If you are familiar with `CSS <https://en.wikipedia.org/wiki/CSS>`_ styles,
  410. you can use the ``style()`` method of output return to set a custom style for the output.
  411. You can set the CSS style for a single ``put_xxx()`` output:
  412. .. exportable-codeblock::
  413. :name: style
  414. :summary: style of output
  415. put_text('hello').style('color: red; font-size: 20px')
  416. ## ----
  417. put_row([
  418. put_text('hello').style('color: red'),
  419. put_markdown('markdown')
  420. ]).style('margin-top: 20px')
  421. .. _server_and_script_mode:
  422. Server mode and Script mode
  423. ------------------------------------
  424. In PyWebIO, there are two modes to run PyWebIO applications: running as a script and using `start_server() <pywebio.platform.tornado.start_server>` or `path_deploy() <pywebio.platform.path_deploy>` to run as a web service.
  425. Overview
  426. ^^^^^^^^^^^^^^
  427. .. _server_mode:
  428. **Server mode**
  429. In server mode, PyWebIO will start a web server to continuously provide services. When the user accesses the service address, PyWebIO will open a new session and run PyWebIO application in it.
  430. Use `start_server() <pywebio.platform.tornado.start_server>` to start a web server and serve given PyWebIO applications on it. `start_server() <pywebio.platform.tornado.start_server>` accepts a function as PyWebIO application. In addition, `start_server() <pywebio.platform.tornado.start_server>` also accepts a list of task function or a dictionary of it, so one PyWebIO Server can have multiple services with different functions. You can use `go_app() <pywebio.session.go_app>` or `put_link() <pywebio.output.put_link>` to jump between services::
  431. def task_1():
  432. put_text('task_1')
  433. put_buttons(['Go task 2'], [lambda: go_app('task_2')])
  434. hold()
  435. def task_2():
  436. put_text('task_2')
  437. put_buttons(['Go task 1'], [lambda: go_app('task_1')])
  438. hold()
  439. def index():
  440. put_link('Go task 1', app='task_1') # Use `app` parameter to specify the task name
  441. put_link('Go task 2', app='task_2')
  442. # equal to `start_server({'index': index, 'task_1': task_1, 'task_2': task_2})`
  443. start_server([index, task_1, task_2])
  444. The `start_server() <pywebio.platform.tornado.start_server>` provide a remote access support, when enabled (by passing `remote_access=True` to `start_server()`), you can get a temporary public network access address for the current application, others can access your application via this address. Using remote access makes it easy to temporarily share the application with others. This service is powered by `localhost.run <https://localhost.run>`_.
  445. Use `path_deploy() <pywebio.platform.path_deploy>` to deploy the PyWebIO applications from a directory.
  446. The python file under this directory need contain the ``main`` function to be seen as the PyWebIO application.
  447. You can access the application by using the file path as the URL.
  448. For example, given the following folder structure::
  449. .
  450. ├── A
  451. │ └── a.py
  452. ├── B
  453. │ └── b.py
  454. └── c.py
  455. If you use this directory in `path_deploy() <pywebio.platform.path_deploy>`, you can access the PyWebIO application in ``b.py`` by using URL ``http://<host>:<port>/A/b``.
  456. And if the files have been modified after run `path_deploy() <pywebio.platform.path_deploy>`, you can use ``reload`` URL parameter to reload application in the file: ``http://<host>:<port>/A/b?reload``
  457. You can also use the command ``pywebio-path-deploy`` to start a server just like using `path_deploy() <pywebio.platform.path_deploy>`. For more information, refer ``pywebio-path-deploy --help``
  458. In Server mode, you can use `pywebio.platform.seo()` to set the `SEO <https://en.wikipedia.org/wiki/Search_engine_optimization>`_ information. 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.
  459. .. attention::
  460. Note that in Server mode, all functions from ``input``, ``output`` and ``session`` modules can only be called in the context of task functions. For example, the following code is **not allowed**::
  461. import pywebio
  462. from pywebio.input import input
  463. port = input('Input port number:') # ❌ error
  464. pywebio.start_server(my_task_func, port=int(port))
  465. **Script mode**
  466. In Script mode, PyWebIO input and output functions can be called anywhere.
  467. If the user closes the browser before the end of the session, then calls to PyWebIO input and output functions in the session will cause a `SessionException <pywebio.exceptions.SessionException>` exception.
  468. .. _thread_in_server_mode:
  469. Concurrent
  470. ^^^^^^^^^^^^^^
  471. PyWebIO can be used in a multi-threading environment.
  472. **Script mode**
  473. In Script mode, you can freely start new thread and call PyWebIO interactive functions in it. When all `non-daemonic <https://docs.python.org/3/library/threading.html#thread-objects>`_ threads finish running, the script exits.
  474. **Server mode**
  475. In Server mode, if you need to use PyWebIO interactive functions in new thread, you need to use `register_thread(thread) <pywebio.session.register_thread>` to register the new thread (so that PyWebIO can know which session the thread belongs to). If the PyWebIO interactive function is not used in the new thread, no registration is required. Threads that are not registered with `register_thread(thread) <pywebio.session.register_thread>` calling PyWebIO's interactive functions will cause `SessionNotFoundException <pywebio.exceptions.SessionNotFoundException>`. When both the task function of the session and the thread registered through `register_thread(thread) <pywebio.session.register_thread>` in the session have finished running, the session is closed.
  476. Example of using multi-threading in Server mode::
  477. def show_time():
  478. while True:
  479. with use_scope(name='time', clear=True):
  480. put_text(datetime.datetime.now())
  481. time.sleep(1)
  482. def app():
  483. t = threading.Thread(target=show_time)
  484. register_thread(t)
  485. put_markdown('## Clock')
  486. t.start() # run `show_time()` in background
  487. # ❌ this thread will cause `SessionNotFoundException`
  488. threading.Thread(target=show_time).start()
  489. put_text('Background task started.')
  490. start_server(app, port=8080, debug=True)
  491. .. _session_close:
  492. Close of session
  493. ^^^^^^^^^^^^^^^^^
  494. The close of session may also be caused by the user closing the browser page. After the browser page is closed, PyWebIO input function calls that have not yet returned in the current session will cause `SessionClosedException <pywebio.exceptions.SessionClosedException>`, and subsequent calls to PyWebIO interactive functions will cause `SessionNotFoundException <pywebio.exceptions.SessionNotFoundException>` or `SessionClosedException <pywebio.exceptions.SessionClosedException>`.
  495. You can use `defer_call(func) <pywebio.session.defer_call>` to set the function to be called when the session closes. Whether it is because the user closes the page or the task finishes to cause session closed, the function set by `defer_call(func) <pywebio.session.defer_call>` will be executed. `defer_call(func) <pywebio.session.defer_call>` can be used for resource cleaning. You can call `defer_call(func) <pywebio.session.defer_call>` multiple times in the session, and the set functions will be executed sequentially after the session closes.
  496. .. _integration_web_framework:
  497. Integration with web framework
  498. ---------------------------------
  499. The PyWebIO application can be integrated into an existing Python Web project, the PyWebIO application and the Web project share a web framework. PyWebIO currently supports integration with Flask, Tornado, Django, aiohttp and FastAPI(Starlette) web frameworks.
  500. The integration methods of those web frameworks are as follows:
  501. .. tabs::
  502. .. tab:: Tornado
  503. .. only:: latex
  504. **Tornado**
  505. Use `pywebio.platform.tornado.webio_handler()` to get the `WebSocketHandler <https://www.tornadoweb.org/en/stable/websocket.html#tornado.websocket.WebSocketHandler>`_ class for running PyWebIO applications in Tornado::
  506. import tornado.ioloop
  507. import tornado.web
  508. from pywebio.platform.tornado import webio_handler
  509. class MainHandler(tornado.web.RequestHandler):
  510. def get(self):
  511. self.write("Hello, world")
  512. if __name__ == "__main__":
  513. application = tornado.web.Application([
  514. (r"/", MainHandler),
  515. (r"/tool", webio_handler(task_func)), # `task_func` is PyWebIO task function
  516. ])
  517. application.listen(port=80, address='localhost')
  518. tornado.ioloop.IOLoop.current().start()
  519. In above code, we add a routing rule to bind the ``WebSocketHandler`` of the PyWebIO application to the ``/tool`` path.
  520. After starting the Tornado server, you can visit ``http://localhost/tool`` to open the PyWebIO application.
  521. .. attention::
  522. PyWebIO uses the WebSocket protocol to communicate with the browser in Tornado. If your Tornado application is behind a reverse proxy (such as Nginx), you may need to configure the reverse proxy to support the WebSocket protocol. :ref:`Here <nginx_ws_config>` is an example of Nginx WebSocket configuration.
  523. .. tab:: Flask
  524. .. only:: latex
  525. **Flask**
  526. Use `pywebio.platform.flask.webio_view()` to get the view function for running PyWebIO applications in Flask::
  527. from pywebio.platform.flask import webio_view
  528. from flask import Flask
  529. app = Flask(__name__)
  530. # `task_func` is PyWebIO task function
  531. app.add_url_rule('/tool', 'webio_view', webio_view(task_func),
  532. methods=['GET', 'POST', 'OPTIONS']) # need GET,POST and OPTIONS methods
  533. app.run(host='localhost', port=80)
  534. In above code, we add a routing rule to bind the view function of the PyWebIO application to the ``/tool`` path.
  535. After starting the Flask application, visit ``http://localhost/tool`` to open the PyWebIO application.
  536. .. tab:: Django
  537. .. only:: latex
  538. **Django**
  539. Use `pywebio.platform.django.webio_view()` to get the view function for running PyWebIO applications in Django::
  540. # urls.py
  541. from django.urls import path
  542. from pywebio.platform.django import webio_view
  543. # `task_func` is PyWebIO task function
  544. webio_view_func = webio_view(task_func)
  545. urlpatterns = [
  546. path(r"tool", webio_view_func),
  547. ]
  548. In above code, we add a routing rule to bind the view function of the PyWebIO application to the ``/tool`` path.
  549. After starting the Django server, visit ``http://localhost/tool`` to open the PyWebIO application
  550. .. tab:: aiohttp
  551. .. only:: latex
  552. **aiohttp**
  553. Use `pywebio.platform.aiohttp.webio_handler()` to get the `Request Handler <https://docs.aiohttp.org/en/stable/web_quickstart.html#aiohttp-web-handler>`_ coroutine for running PyWebIO applications in aiohttp::
  554. from aiohttp import web
  555. from pywebio.platform.aiohttp import webio_handler
  556. app = web.Application()
  557. # `task_func` is PyWebIO task function
  558. app.add_routes([web.get('/tool', webio_handler(task_func))])
  559. web.run_app(app, host='localhost', port=80)
  560. After starting the aiohttp server, visit ``http://localhost/tool`` to open the PyWebIO application
  561. .. attention::
  562. PyWebIO uses the WebSocket protocol to communicate with the browser in aiohttp. If your aiohttp server is behind a reverse proxy (such as Nginx), you may need to configure the reverse proxy to support the WebSocket protocol. :ref:`Here <nginx_ws_config>` is an example of Nginx WebSocket configuration.
  563. .. tab:: FastAPI/Starlette
  564. .. only:: latex
  565. **FastAPI/Starlette**
  566. Use `pywebio.platform.fastapi.webio_routes()` to get the FastAPI/Starlette routes for running PyWebIO applications.
  567. You can mount the routes to your FastAPI/Starlette app.
  568. FastAPI::
  569. from fastapi import FastAPI
  570. from pywebio.platform.fastapi import webio_routes
  571. app = FastAPI()
  572. @app.get("/app")
  573. def read_main():
  574. return {"message": "Hello World from main app"}
  575. # `task_func` is PyWebIO task function
  576. app.mount("/tool", FastAPI(routes=webio_routes(task_func)))
  577. Starlette::
  578. from starlette.applications import Starlette
  579. from starlette.responses import JSONResponse
  580. from starlette.routing import Route, Mount
  581. from pywebio.platform.fastapi import webio_routes
  582. async def homepage(request):
  583. return JSONResponse({'hello': 'world'})
  584. app = Starlette(routes=[
  585. Route('/', homepage),
  586. Mount('/tool', routes=webio_routes(task_func)) # `task_func` is PyWebIO task function
  587. ])
  588. After starting the server by using ``uvicorn <module>:app`` , visit ``http://localhost:8000/tool/`` to open the PyWebIO application
  589. See also: `FastAPI doc <https://fastapi.tiangolo.com/advanced/sub-applications/>`_ , `Starlette doc <https://www.starlette.io/routing/#submounting-routes>`_
  590. .. attention::
  591. PyWebIO uses the WebSocket protocol to communicate with the browser in FastAPI/Starlette. If your server is behind a reverse proxy (such as Nginx), you may need to configure the reverse proxy to support the WebSocket protocol. :ref:`Here <nginx_ws_config>` is an example of Nginx WebSocket configuration.
  592. .. _integration_web_framework_note:
  593. Notes
  594. ^^^^^^^^^^^
  595. **Deployment in production**
  596. In your production system, you may want to deploy the web applications with some WSGI/ASGI servers such as uWSGI, Gunicorn, and Uvicorn.
  597. Since PyWebIO applications store session state in memory of process, when you use HTTP-based sessions (Flask and Django) and spawn multiple workers to handle requests,
  598. the request may be dispatched to a process that does not hold the session to which the request belongs.
  599. So you can only start one worker to handle requests when using Flask or Django backend.
  600. If you still want to use multiple processes to increase concurrency, one way is to use Uvicorn+FastAPI, or you can also start multiple Tornado/aiohttp processes and add external load balancer (such as HAProxy or nginx) before them.
  601. Those backends use the WebSocket protocol to communicate with the browser in PyWebIO, so there is no the issue as described above.
  602. **Static resources Hosting**
  603. By default, the front-end of PyWebIO gets required static resources from CDN. If you want to deploy PyWebIO applications in an offline environment, you need to host static files by yourself, and set the ``cdn`` parameter of ``webio_view()`` or ``webio_handler()`` to ``False``.
  604. When setting ``cdn=False`` , you need to host the static resources in the same directory as the PyWebIO application.
  605. In addition, you can also pass a string to ``cdn`` parameter to directly set the URL of PyWebIO static resources directory.
  606. The path of the static file of PyWebIO is stored in ``pywebio.STATIC_PATH``, you can use the command ``python3 -c "import pywebio; print(pywebio.STATIC_PATH)"`` to print it out.
  607. .. note:: ``start_server()`` and ``path_deploy()`` also support ``cdn`` parameter, if it is set to ``False``, the static resource will be hosted in local server automatically, without manual hosting.
  608. .. _coroutine_based_session:
  609. Coroutine-based session
  610. -------------------------------
  611. This section will introduce the advanced features of PyWebIO --- coroutine-based session. In most cases, you don’t need it. All functions or methods in PyWebIO that are only used for coroutine sessions are specifically noted in the document.
  612. PyWebIO's session is based on thread by default. Each time a user opens a session connection to the server, PyWebIO will start a thread to run the task function. In addition to thread-based sessions, PyWebIO also provides coroutine-based sessions. Coroutine-based sessions accept coroutine functions as task functions.
  613. The session based on the coroutine is a single-thread model, which means that all sessions run in a single thread. For IO-bound tasks, coroutines take up fewer resources than threads and have performance comparable to threads. In addition, the context switching of the coroutine is predictable, which can reduce the need for program synchronization and locking, and can effectively avoid most critical section problems.
  614. Using coroutine session
  615. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  616. To use coroutine-based session, you need to use the ``async`` keyword to declare the task function as a coroutine function, and use the ``await`` syntax to call the PyWebIO input function:
  617. .. code-block:: python
  618. :emphasize-lines: 5,6
  619. from pywebio.input import *
  620. from pywebio.output import *
  621. from pywebio import start_server
  622. async def say_hello():
  623. name = await input("what's your name?")
  624. put_text('Hello, %s' % name)
  625. start_server(say_hello, auto_open_webbrowser=True)
  626. In the coroutine task function, you can also use ``await`` to call other coroutines or ( `awaitable objects <https://docs.python.org/3/library/asyncio-task.html#asyncio-awaitables>`_ ) in the standard library `asyncio <https://docs.python.org/3/library/asyncio.html>`_:
  627. .. code-block:: python
  628. :emphasize-lines: 6,10
  629. import asyncio
  630. from pywebio import start_server
  631. async def hello_word():
  632. put_text('Hello ...')
  633. await asyncio.sleep(1) # await awaitable objects in asyncio
  634. put_text('... World!')
  635. async def main():
  636. await hello_word() # await coroutine
  637. put_text('Bye, bye')
  638. start_server(main, auto_open_webbrowser=True)
  639. .. attention::
  640. In coroutine-based session, all input functions defined in the :doc:`pywebio.input </input>` module need to use ``await`` syntax to get the return value. Forgetting to use ``await`` will be a common error when using coroutine-based session.
  641. Other functions that need to use ``await`` syntax in the coroutine session are:
  642. * `pywebio.session.run_asyncio_coroutine(coro_obj) <pywebio.session.run_asyncio_coroutine>`
  643. * `pywebio.session.eval_js(expression) <pywebio.session.eval_js>`
  644. * `pywebio.session.hold() <pywebio.session.hold>`
  645. .. warning::
  646. Although the PyWebIO coroutine session is compatible with the ``awaitable objects`` in the standard library ``asyncio``, the ``asyncio`` library is not compatible with the ``awaitable objects`` in the PyWebIO coroutine session.
  647. That is to say, you can't pass PyWebIO ``awaitable objects`` to the ``asyncio`` functions that accept ``awaitable objects``. For example, the following calls are **not supported** ::
  648. await asyncio.shield(pywebio.input())
  649. await asyncio.gather(asyncio.sleep(1), pywebio.session.eval_js('1+1'))
  650. task = asyncio.create_task(pywebio.input())
  651. .. _coroutine_based_concurrency:
  652. Concurrency in coroutine-based sessions
  653. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  654. In coroutine-based session, you can start new thread, but you cannot call PyWebIO interactive functions in it (`register_thread() <pywebio.session.register_thread>` is not available in coroutine session). But you can use `run_async(coro) <pywebio.session.run_async>` to execute a coroutine object asynchronously, and PyWebIO interactive functions can be used in the new coroutine:
  655. .. code-block:: python
  656. :emphasize-lines: 10
  657. from pywebio import start_server
  658. from pywebio.session import run_async
  659. async def counter(n):
  660. for i in range(n):
  661. put_text(i)
  662. await asyncio.sleep(1)
  663. async def main():
  664. run_async(counter(10))
  665. put_text('Main coroutine function exited.')
  666. start_server(main, auto_open_webbrowser=True)
  667. `run_async(coro) <pywebio.session.run_async>` returns a `TaskHandler <pywebio.session.coroutinebased.TaskHandler>`, which can be used to query the running status of the coroutine or close the coroutine.
  668. Close of session
  669. ^^^^^^^^^^^^^^^^^^^
  670. Similar to thread-based session, in coroutine-based session, when the task function and the coroutine running through `run_async() <pywebio.session.run_async>` in the session are all finished, the session is closed.
  671. If the close of the session is caused by the user closing the browser, the behavior of PyWebIO is the same as :ref:`Thread-based session <session_close>`: After the browser page closed, PyWebIO input function calls that have not yet returned in the current session will cause `SessionClosedException <pywebio.exceptions.SessionClosedException>`, and subsequent calls to PyWebIO interactive functions will cause `SessionNotFoundException <pywebio.exceptions.SessionNotFoundException>` or `SessionClosedException <pywebio.exceptions.SessionClosedException>`.
  672. `defer_call(func) <pywebio.session.defer_call>` also available in coroutine session.
  673. .. _coroutine_web_integration:
  674. Integration with Web Framework
  675. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  676. The PyWebIO application that using coroutine-based session can also be integrated to the web framework.
  677. However, there are some limitations when using coroutine-based sessions to integrate into Flask or Django:
  678. First, when ``await`` the coroutine objects/awaitable objects in the ``asyncio`` module, you need to use `run_asyncio_coroutine() <pywebio.session.run_asyncio_coroutine>` to wrap the coroutine object.
  679. Secondly, you need to start a new thread to run the event loop before starting a Flask/Django server.
  680. Example of coroutine-based session integration into Flask:
  681. .. code-block:: python
  682. :emphasize-lines: 12,20
  683. import asyncio
  684. import threading
  685. from flask import Flask, send_from_directory
  686. from pywebio import STATIC_PATH
  687. from pywebio.output import *
  688. from pywebio.platform.flask import webio_view
  689. from pywebio.platform import run_event_loop
  690. from pywebio.session import run_asyncio_coroutine
  691. async def hello_word():
  692. put_text('Hello ...')
  693. await run_asyncio_coroutine(asyncio.sleep(1)) # can't just "await asyncio.sleep(1)"
  694. put_text('... World!')
  695. app = Flask(__name__)
  696. app.add_url_rule('/hello', 'webio_view', webio_view(hello_word),
  697. methods=['GET', 'POST', 'OPTIONS'])
  698. # thread to run event loop
  699. threading.Thread(target=run_event_loop, daemon=True).start()
  700. app.run(host='localhost', port=80)
  701. Finally, coroutine-based session is not available in the script mode. You always need to use ``start_server()`` to run coroutine task function or integrate it to a web framework.
  702. Last but not least
  703. ---------------------
  704. This is all features of PyWebIO, you can continue to read the rest of the documents, or start writing your PyWebIO applications now.
  705. Finally, please allow me to provide one more suggestion. When you encounter a design problem when using PyWebIO, you can ask yourself a question: What would I do if it is in a terminal program?
  706. If you already have the answer, it can be done in the same way with PyWebIO. If the problem persists or the solution is not good enough, you can consider the :doc:`pin <./pin>` module.
  707. OK, Have fun with PyWebIO!