diff --git a/.drone.yml b/.drone.yml
index b4606536..36cad6e8 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -2,27 +2,23 @@ kind: pipeline
type: exec
name: default
-clone:
- disable: true
-
trigger:
+ branch:
+ - dev-release
event:
- - push
+ - push
steps:
- - name: clone
- commands:
- - sleep 300 # wait aliyun repo to sync
- - git init
- - git remote add aliyun "https://code.aliyun.com/wang0618/pywebio.git"
- - git fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 aliyun $DRONE_BRANCH
- - git checkout --progress --force -B $DRONE_BRANCH aliyun/$DRONE_BRANCH
- - git log -1
- name: deploy demos
commands:
+ - | # https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#pipe-dockerfile-through-stdin
+ docker build -t pywebio -f- . <
- docker run --restart=always --name=pywebio-demos -v $PWD:/app_tmp
- --label="traefik.http.services.pywebiodemos.loadbalancer.server.port=80"
- -d python:3 bash -c "cp -r /app_tmp /app && cd /app && pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple . && python3 -m demos --port=80"
- - sleep 5 # wait container start
\ No newline at end of file
+ - docker run --restart=always --name=pywebio-demos -d pywebio
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index ef7b66f9..36ee4617 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -1,18 +1,21 @@
---
-name: Bug report
-about: Create a report to help us improve
-title: ''
-labels: bug
-assignees: ''
+name: Bug report about: Create a bug report to help us improve title: ''
+labels: bug assignees: ''
---
+Note: For inquiries while using PyWebIO or questions that might be helpful to others, please consider moving
+to [Discussions](https://github.com/wang0618/PyWebIO/discussions) for posting. You can
+try [PyWebIO QA Bot](https://github.com/pywebio/PyWebIO/discussions/596) to let AI answer the questions you encounter.
-**BUG描述**
-描述BUG表现以及复现方式。
-如果浏览器控制台有报错以及脚本抛出异常也请将报错信息附上
+**BUG Description**
-**环境信息**
- - 操作系统:
- - 浏览器及版本:
- - Python版本: 使用 `python3 --version` 查看
- - PyWebIO版本: 使用 `python3 -c "import pywebio;print(pywebio.__version__)"` 查看
+A clear and concise description of what the bug is and how to reproduce it.
+
+If the browser console reports an error or the script throws an exception, please also report them.
+
+**Environment Information**
+
+- OS and Version:
+- Browser and Version:
+- Python Version: Use `python3 --version` to view
+- PyWebIO Version: Use `python3 -c "import pywebio;print(pywebio.__version__)"` to view
diff --git a/.github/ISSUE_TEMPLATE/bug_report_zh.md b/.github/ISSUE_TEMPLATE/bug_report_zh.md
new file mode 100644
index 00000000..e9323053
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report_zh.md
@@ -0,0 +1,20 @@
+---
+name: Bug报告
+about: 使用中文进行Bug报告
+title: ''
+labels: bug, zh
+assignees: ''
+
+---
+注: 对于PyWebIO使用咨询或对于其他人也可能有帮助的问题,请考虑移至 [Discussions](https://github.com/wang0618/PyWebIO/discussions) 进行发帖。
+另,可使用[PyWebIO QA Bot](https://github.com/pywebio/PyWebIO/discussions/596) 来让AI对你遇到的问题进行解答。
+
+**BUG描述**
+描述BUG表现以及复现方式。
+如果浏览器控制台有报错以及脚本抛出异常也请将报错信息附上
+
+**环境信息**
+ - 操作系统及版本:
+ - 浏览器及版本:
+ - Python版本: 使用 `python3 --version` 查看
+ - PyWebIO版本: 使用 `python3 -c "import pywebio;print(pywebio.__version__)"` 查看
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 00000000..05714e7e
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,11 @@
+---
+name: Feature Request
+about: Create a feature request
+title: '[Feature Request]'
+labels: enhancement
+assignees: ''
+
+---
+
+Please move to [Discussions - Feature Request](https://github.com/wang0618/PyWebIO/discussions/categories/feature-request) for posting.
+
diff --git a/.github/workflows/build_dev.yml b/.github/workflows/build_dev.yml
new file mode 100644
index 00000000..7e23d453
--- /dev/null
+++ b/.github/workflows/build_dev.yml
@@ -0,0 +1,56 @@
+name: build dev version
+on:
+ push:
+ branches:
+ - dev
+ repository_dispatch:
+
+jobs:
+ build_dev:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@master
+ - name: Set up Python 3
+ uses: actions/setup-python@v1
+ with:
+ python-version: 3.12
+ - name: Set up Node.js
+ uses: actions/setup-node@v2
+ with:
+ node-version: '22'
+ - name: Build frontend
+ working-directory: ./webiojs
+ run: |
+ npm install
+ npx gulp
+ cp dist/pywebio.min.* ../pywebio/html/js
+ - name: Build doc demos
+ run: |
+ pip3 install -e ".[all]"
+ pip3 install -r requirements.txt
+ cd docs && CODE_EXPORT_PATH=../demos/doc_demos make clean html
+ - name: Set dev version
+ run: python3 tools/build_dev_version.py
+ - name: Release dev version
+ run: |
+ git config --global user.email "$(git log -n 1 --pretty=format:%ae)"
+ git config --global user.name "${{ github.actor }}"
+
+ # ref: https://stackoverflow.com/questions/8536732/can-i-hold-git-credentials-in-environment-variables
+ git config --global credential.helper '!f() { sleep 1; echo "username=${{ github.actor }}"; echo "password=${GH_TOKEN}"; }; f'
+
+ git fetch --unshallow origin
+ git branch -D dev-release || true
+ git checkout -b dev-release
+
+ rm .gitignore
+ git add pywebio/__version__.py
+ git add pywebio/html/js
+ git add demos/doc_demos
+
+ git commit -m "Build at `date`"
+ git push -f -u origin dev-release
+ env:
+ # This token is provided by Actions, you do not need to create your own token
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 2c40be42..9a4ac56a 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -2,29 +2,31 @@
name: Python lint
-on: [push, pull_request]
+on: [ push, pull_request ]
jobs:
lint:
runs-on: ubuntu-latest
strategy:
matrix:
- python-version: [3.5, 3.6, 3.7, 3.8]
+ python-version: ["3.9", "3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v2
+ - uses: codespell-project/actions-codespell@master
+ with:
+ ignore_words_list: datas
+ skip: "*.js,*.po,i18n.ts,*.json"
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v1
+ uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
- pip install -r requirements.txt
+ pip install -e ".[all]"
- name: Lint with flake8
run: |
pip install flake8
- # stop the build if there are Python syntax errors or undefined names
- flake8 pywebio --count --select=E9,F63,F7,F82 --show-source --statistics
- # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
- flake8 pywebio --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
\ No newline at end of file
+ flake8 --ignore=E126,E127,E128,E131,E226,E402,E731,F401,F403,F405,W291,W292,W293 \
+ --max-complexity=32 --max-line-length=525 --show-source --statistics .
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index dcb27b4e..7c4083d6 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,7 +1,8 @@
-name: Release to PyPi
+name: Release
+# https://github.community/t/how-to-run-github-actions-workflow-only-for-new-tags/16075/22
on:
- create:
+ push:
tags:
- v*
@@ -14,22 +15,42 @@ jobs:
- name: Set up Python 3
uses: actions/setup-python@v1
with:
- python-version: 3.7
- - name: Set up Node.js v13.5
- uses: actions/setup-node@v1
+ python-version: 3.12
+ - name: Set up Node.js
+ uses: actions/setup-node@v2
with:
- node-version: 13.5
+ node-version: '22'
- name: Build frontend
working-directory: ./webiojs
run: |
npm install
- gulp
+ npx gulp
cp dist/pywebio.min.js ../pywebio/html/js
- - run: |
- pip3 install twine
+ - name: PyPi Upload
+ run: |
+ pip3 install twine setuptools
python3 setup.py sdist
twine upload --username "__token__" --disable-progress-bar --verbose dist/*
env:
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
+ - name: Set tag
+ run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
+ - name: Push asset
+ run: |
+ git config --global user.email "wang0.618@qq.com"
+ git config --global user.name "${{ github.actor }}"
+ git config --global credential.helper '!f() { sleep 1; echo "username=${{ github.actor }}"; echo "password=${GH_TOKEN}"; }; f'
+ git clone https://github.com/wang0618/pywebio-assets.git
+ rm -rf pywebio-assets/*
+ cp -r pywebio/html/* pywebio-assets
+ cd pywebio-assets
+ git add .
+ git commit -m "Build from https://github.com/wang0618/PyWebIO/commit/$GITHUB_SHA" || true
+ git tag $RELEASE_VERSION
+
+ git push -u origin --tags
+ git push -u origin || true
+ env:
+ GH_TOKEN: ${{ secrets.ASSET_REPO_TOKEN }}
diff --git a/.github/workflows/sync_repo.yml b/.github/workflows/sync_repo.yml
deleted file mode 100644
index 722c835c..00000000
--- a/.github/workflows/sync_repo.yml
+++ /dev/null
@@ -1,42 +0,0 @@
-name: Sync with mirror repo
-on: push
-jobs:
- sync:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@master
- - name: Set up Python 3.7
- uses: actions/setup-python@v1
- with:
- python-version: 3.7
- - name: Set up Node.js v13.5
- uses: actions/setup-node@v1
- with:
- node-version: 13.5
- - name: Build frontend
- working-directory: ./webiojs
- run: |
- npm install
- DEV=1 gulp
- cp dist/pywebio.min.* ../pywebio/html/js
- - name: Build doc demos
- run: |
- pip3 install -e ".[all]"
- cd docs && CODE_EXPORT_PATH=../demos/doc_domes make clean text
- - name: Push
- run: |
- git fetch --unshallow origin
- git remote add aliyun "https://code.aliyun.com/wang0618/pywebio.git"
- git config credential.helper '!f() { sleep 1; echo "username=${ALIYUN_GIT_USER}"; echo "password=${ALIYUN_GIT_PASSWORD}"; }; f'
- rm .gitignore
- git add pywebio/html/js
- git add demos/doc_domes
- git config user.email "${ALIYUN_GIT_USER}"
- git config user.name "${ALIYUN_GIT_USER}"
- git commit --amend --no-edit
- git push -f -u aliyun --tags || exit 0
- git push -f -u aliyun || exit 0
- env:
- ALIYUN_GIT_USER: ${{ secrets.ALIYUN_GIT_USER }}
- ALIYUN_GIT_PASSWORD: ${{ secrets.ALIYUN_GIT_PASSWORD }}
\ No newline at end of file
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 78ae873b..f9dfba49 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -6,41 +6,40 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@master
- - name: Set up Python 3.7
+ - name: Set up Python 3
uses: actions/setup-python@v1
with:
- python-version: 3.7
- - name: Set up Node.js v13.5
- uses: actions/setup-node@v1
+ python-version: 3.12
+ - name: Set up Node.js
+ uses: actions/setup-node@v2
with:
- node-version: 13.5
+ node-version: '22'
- name: Build frontend
working-directory: ./webiojs
run: |
npm install
- gulp
+ npx gulp
cp dist/pywebio.min.* ../pywebio/html/js
- - name: Install Test JS deps
- run: npm install -D @percy/agent
- name: Install package
- run: pip3 install -e ".[all]"
+ run: pip3 install ".[all]"
- name: Install dev dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- - name: Install env
- run: echo "PERCY_TOKEN=${{ secrets.PERCY_TOKEN }}" >> $GITHUB_ENV
+ - run: npm install --save-dev @percy/cli
- name: Percy Test
- uses: percy/exec-action@v0.3.1
- with:
- working-directory: ./test
- command: "./run_all.sh"
+ run: npx percy exec -- bash ./run_all.sh
+ working-directory: ./test
+ env:
+ PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}
- name: Upload test output
uses: actions/upload-artifact@v1
if: failure()
with:
name: test output
path: test/output
- - name: Upload Codecov Report
- working-directory: ./test
- run: bash <(curl -s https://codecov.io/bash)
+ - name: Upload test output
+ uses: codecov/codecov-action@v3
+ with:
+ working-directory: ./test
+ verbose: true # optional (default = false)
diff --git a/.gitignore b/.gitignore
index 6ddca755..3f43dac6 100755
--- a/.gitignore
+++ b/.gitignore
@@ -11,4 +11,5 @@ pywebio/html/js/pywebio.min.*
/build
/dist
/*.egg-info
-/docs/_build
\ No newline at end of file
+/docs/_build
+/archive
diff --git a/.readthedocs.yml b/.readthedocs.yml
index 16d5d9fa..905a6cfa 100644
--- a/.readthedocs.yml
+++ b/.readthedocs.yml
@@ -5,6 +5,12 @@
# Required
version: 2
+# Set the OS, Python version and other tools you might need
+build:
+ os: ubuntu-lts-latest
+ tools:
+ python: "3.9"
+
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/conf.py
@@ -14,11 +20,11 @@ sphinx:
# configuration: mkdocs.yml
# Optionally build your docs in additional formats such as PDF and ePub
-formats: all
+formats:
+ - pdf
# Optionally set the version of Python and requirements required to build your docs
python:
- version: 3.7
install:
- requirements: requirements.txt
- method: pip
diff --git a/MANIFEST.in b/MANIFEST.in
index 121ee7ca..43262f9b 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,3 +1,4 @@
recursive-include demos *.py
prune docs
-graft pywebio/html
\ No newline at end of file
+graft pywebio/html
+graft pywebio/platform/tpl
\ No newline at end of file
diff --git a/Procfile b/Procfile
deleted file mode 100644
index c32b8756..00000000
--- a/Procfile
+++ /dev/null
@@ -1 +0,0 @@
-web: python -m demos --port=$PORT
\ No newline at end of file
diff --git a/README-zh.md b/README-zh.md
new file mode 100644
index 00000000..5ff7652e
--- /dev/null
+++ b/README-zh.md
@@ -0,0 +1,155 @@
+PyWebIO
+
+ Write interactive web app in script way.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ [Document] | [Demos] | [Playground] | [Why PyWebIO?]
+
+
+[English](README.md) | [中文](README-zh.md)
+
+PyWebIO提供了一系列命令式的交互函数来在浏览器上获取用户输入和进行输出,将浏览器变成了一个“富文本终端”,可以用于构建简单的Web应用或基于浏览器的GUI应用。
+PyWebIO还可以方便地整合进现有的Web服务,让你不需要编写HTML和JS代码,就可以构建出具有良好可用性的应用。
+
+
+
+
+
+
+
+功能特性:
+
+- 使用同步而不是基于回调的方式获取输入,代码编写逻辑更自然
+- 非声明式布局,布局方式简单高效
+- 代码侵入性小,旧脚本代码仅需修改输入输出逻辑便可改造为Web服务
+- 支持整合到现有的Web服务,目前支持与Flask、Django、Tornado、aiohttp、FastAPI框架集成
+- 同时支持基于线程的执行模型和基于协程的执行模型
+- 支持结合第三方库实现数据可视化
+
+## Installation
+
+稳定版安装:
+
+```bash
+pip3 install -U pywebio
+```
+
+开发版安装:
+```bash
+pip3 install -U https://github.com/pywebio/PyWebIO/archive/dev-release.zip
+```
+
+**系统要求**: PyWebIO要求 Python 版本在 3.5.2 及以上
+
+## Quickstart
+
+**Hello, world**
+
+这是一个使用PyWebIO计算 [BMI指数](https://en.wikipedia.org/wiki/Body_mass_index) 的脚本:
+
+```python
+from pywebio.input import input, FLOAT
+from pywebio.output import put_text
+
+def bmi():
+ height = input("请输入你的身高(cm):", type=FLOAT)
+ weight = input("请输入你的体重(kg):", type=FLOAT)
+
+ BMI = weight / (height / 100) ** 2
+
+ top_status = [(14.9, '极瘦'), (18.4, '偏瘦'),
+ (22.9, '正常'), (27.5, '过重'),
+ (40.0, '肥胖'), (float('inf'), '非常肥胖')]
+
+ for top, status in top_status:
+ if BMI <= top:
+ put_text('你的 BMI 值: %.1f,身体状态:%s' % (BMI, status))
+ break
+
+if __name__ == '__main__':
+ bmi()
+```
+
+
+如果没有使用PyWebIO,这只是一个非常简单的脚本,而通过使用PyWebIO提供的输入输出函数,你可以在浏览器中与代码进行交互 [[demo]](http://pywebio-demos.pywebio.online/bmi):
+
+
+
+
+
+
+
+**作为Web服务提供**
+
+上文BMI程序会在计算完毕后立刻退出,可以使用 [`pywebio.start_server()`](https://pywebio.readthedocs.io/zh_CN/latest/platform.html#pywebio.platform.tornado.start_server) 将 `bmi()` 函数作为Web服务提供:
+
+```python
+from pywebio import start_server
+from pywebio.input import input, FLOAT
+from pywebio.output import put_text
+
+def bmi(): # bmi() 函数内容不变
+ ...
+
+if __name__ == '__main__':
+ start_server(bmi, port=80)
+```
+
+**与现有Web框架整合**
+
+Tornado应用整合:仅需在现有的Tornado应用中添加一个 `RequestHandler` ,就可以将PyWebIO应用整合进Tornado Web服务中
+
+```python
+import tornado.ioloop
+import tornado.web
+from pywebio.platform.tornado import webio_handler
+
+class MainHandler(tornado.web.RequestHandler):
+ def get(self):
+ self.write("Hello, world")
+
+if __name__ == "__main__":
+ application = tornado.web.Application([
+ (r"/", MainHandler),
+ (r"/bmi", webio_handler(bmi)), # bmi 即为上文计算BMI指数的函数
+ ])
+ application.listen(port=80, address='localhost')
+ tornado.ioloop.IOLoop.current().start()
+```
+
+在 `http://localhost/bmi` 页面上就可以计算BMI了。
+
+与其他Web框架整合请见[文档](https://pywebio.readthedocs.io/zh_CN/latest/advanced.html#integration-with-web-framework)
+
+## Demos
+
+ - [基本demo](http://pywebio-demos.pywebio.online/) : 包含PyWebIO基本输入输出演示和使用PyWebIO编写的小应用
+ - [数据可视化demo](http://pywebio-charts.pywebio.online/) : 使用 bokeh、plotly、pyecharts 等库进行数据可视化
+
+## Links
+
+* 使用手册和实现文档见 [pywebio.readthedocs.io](https://pywebio.readthedocs.io/zh_CN/latest/)
+* [PyWebIO Playground](https://play.pywebio.online/): 在线编辑、运行和分享PyWebIO代码
diff --git a/README.md b/README.md
index c6f3006c..ae0a1967 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-PyWebIO
+PyWebIO
Write interactive web app in script way.
@@ -6,8 +6,11 @@
-
-
+
+
+
+
+
@@ -16,24 +19,19 @@
-
+
-
-
-
-
-
-
- [Document] | [Demos]
+ [Document] | [Demos] | [Playground] | [Why PyWebIO?]
-PyWebIO提供了一系列命令式的交互函数来在浏览器上获取用户输入和进行输出,将浏览器变成了一个“富文本终端”,可以用于构建简单的Web应用或基于浏览器的GUI应用。
-PyWebIO还可以方便地整合进现有的Web服务,让你不需要编写HTML和JS代码,就可以构建出具有良好可用性的应用。
+[English](README.md) | [中文](README-zh.md)
+
+PyWebIO provides a series of imperative functions to obtain user input and output on the browser, turning the browser into a "rich text terminal", and can be used to build simple web applications or browser-based GUI applications without the need to have knowledge of HTML and JS. PyWebIO can also be easily integrated into existing Web services. PyWebIO is very suitable for quickly building applications that do not require complex UI.
@@ -41,93 +39,91 @@ PyWebIO还可以方便地整合进现有的Web服务,让你不需要编写HTML
-功能特性:
+Features:
-- 使用同步而不是基于回调的方式获取输入,代码编写逻辑更自然
-- 非声明式布局,布局方式简单高效
-- 代码侵入性小,旧脚本代码仅需修改输入输出逻辑便可改造为Web服务
-- 支持整合到现有的Web服务,目前支持与Flask、Django、Tornado、aiohttp框架集成
-- 同时支持基于线程的执行模型和基于协程的执行模型
-- 支持结合第三方库实现数据可视化
+- Use synchronization instead of a callback-based method to get input
+- Non-declarative layout, simple and efficient
+- Less intrusive: old script code can be transformed into a Web application only by modifying the input and output operation
+- Support integration into existing web services, currently supports Flask, Django, Tornado, aiohttp, FastAPI framework
+- Support for ``asyncio`` and coroutine
+- Support data visualization with third-party libraries, e.g., `plotly`, `bokeh`, `pyecharts`.
-## Install
+## Installation
-稳定版安装:
+Stable version:
```bash
pip3 install -U pywebio
```
-开发版安装:
+Development version:
```bash
-pip3 install -U --force-reinstall https://code.aliyun.com/wang0618/pywebio/repository/archive.zip
+pip3 install -U https://github.com/pywebio/PyWebIO/archive/dev-release.zip
```
-**系统要求**: PyWebIO要求 Python 版本在 3.5.2 及以上
+**Prerequisites**: PyWebIO requires Python 3.5.2 or newer
-## Quick start
+## Quickstart
**Hello, world**
-这是一个使用PyWebIO计算 [BMI指数](https://en.wikipedia.org/wiki/Body_mass_index>) 的脚本:
+Here is a simple PyWebIO script to calculate the [BMI](https://en.wikipedia.org/wiki/Body_mass_index):
```python
from pywebio.input import input, FLOAT
from pywebio.output import put_text
def bmi():
- height = input("请输入你的身高(cm):", type=FLOAT)
- weight = input("请输入你的体重(kg):", type=FLOAT)
+ height = input("Your Height(cm):", type=FLOAT)
+ weight = input("Your Weight(kg):", type=FLOAT)
BMI = weight / (height / 100) ** 2
- top_status = [(14.9, '极瘦'), (18.4, '偏瘦'),
- (22.9, '正常'), (27.5, '过重'),
- (40.0, '肥胖'), (float('inf'), '非常肥胖')]
+ top_status = [(14.9, 'Severely underweight'), (18.4, 'Underweight'),
+ (22.9, 'Normal'), (27.5, 'Overweight'),
+ (40.0, 'Moderately obese'), (float('inf'), 'Severely obese')]
for top, status in top_status:
if BMI <= top:
- put_text('你的 BMI 值: %.1f,身体状态:%s' % (BMI, status))
+ put_text('Your BMI: %.1f, category: %s' % (BMI, status))
break
if __name__ == '__main__':
bmi()
```
-
-如果没有使用PyWebIO,这只是一个非常简单的脚本,而通过使用PyWebIO提供的输入输出函数,你可以在浏览器中与代码进行交互 [[demo]](http://pywebio-demos.demo.wangweimin.site/?pywebio_api=bmi):
+This is just a very simple script if you ignore PyWebIO, but using the input and output functions provided by PyWebIO, you can interact with the code in the browser [[demo]](http://pywebio-demos.pywebio.online/bmi):
-
+
-**作为Web服务提供**
+**Serve as web service**
-上文对使用PyWebIO进行改造的程序,运行模式还是脚本,程序计算完毕后立刻退出。可以使用 [`pywebio.start_server()`](https://pywebio.readthedocs.io/zh_CN/latest/platform.html#pywebio.platform.tornado.start_server) 将 `bmi()` 函数作为Web服务提供:
+The above BMI program will exit immediately after the calculation, you can use [`pywebio.start_server()`](https://pywebio.readthedocs.io/en/latest/platform.html#pywebio.platform.tornado.start_server) to publish the `bmi()` function as a web application:
```python
from pywebio import start_server
from pywebio.input import input, FLOAT
from pywebio.output import put_text
-def bmi():
- ... # bmi() 函数内容不变
+def bmi(): # bmi() keep the same
+ ...
if __name__ == '__main__':
start_server(bmi, port=80)
```
-**与现有Web框架整合**
+**Integration with web framework**
-Tornado应用整合:仅需在现有的Tornado应用中加入加入两个 `RequestHandler` ,就可以将使用PyWebIO编写的函数整合进Tornado应用中
+To integrate a PyWebIO application into Tornado, all you need is to add a `RequestHandler` to the existing Tornado application:
```python
import tornado.ioloop
import tornado.web
from pywebio.platform.tornado import webio_handler
-from pywebio import STATIC_PATH
class MainHandler(tornado.web.RequestHandler):
def get(self):
@@ -136,23 +132,22 @@ class MainHandler(tornado.web.RequestHandler):
if __name__ == "__main__":
application = tornado.web.Application([
(r"/", MainHandler),
- (r"/bmi/io", webio_handler(bmi)), # bmi 即为上文计算BMI指数的函数
- (r"/bmi/(.*)", tornado.web.StaticFileHandler, {"path": STATIC_PATH, 'default_filename': 'index.html'})
+ (r"/bmi", webio_handler(bmi)), # bmi is the same function as above
])
application.listen(port=80, address='localhost')
tornado.ioloop.IOLoop.current().start()
```
-在 `http://localhost/bmi/` 页面上就可以计算BMI了。
+Now, you can open `http://localhost/bmi` for BMI calculation.
-与其他Web框架整合请见[文档](https://pywebio.readthedocs.io/zh_CN/latest/guide.html#web)
+For integration with other web frameworks, please refer to [document](https://pywebio.readthedocs.io/en/latest/advanced.html#integration-with-web-framework).
## Demos
- - [基本demo](http://pywebio-demos.demo.wangweimin.site/) : 包含PyWebIO基本输入输出演示和使用PyWebIO编写的小应用
- - [数据可视化demo](http://pywebio-charts.demo.wangweimin.site/) : 使用 bokeh、plotly、pyecharts 等库进行数据可视化
-
-## Document
+ - [Basic demo](http://pywebio-demos.pywebio.online/) : PyWebIO basic input and output demos and some small applications written using PyWebIO.
+ - [Data visualization demo](http://pywebio-charts.pywebio.online/) : Data visualization with the third-party libraries, e.g., `plotly`, `bokeh`, `pyecharts`.
-使用手册和实现文档见 [https://pywebio.readthedocs.io](https://pywebio.readthedocs.io)
+## Links
+* Document [pywebio.readthedocs.io](https://pywebio.readthedocs.io)
+* [PyWebIO Playground](https://play.pywebio.online/): Edit, Run, Share PyWebIO Code Online
\ No newline at end of file
diff --git a/demos/__init__.py b/demos/__init__.py
index 55158619..e69de29b 100644
--- a/demos/__init__.py
+++ b/demos/__init__.py
@@ -1,6 +0,0 @@
-r"""
-.. automodule:: demos.bmi
-.. automodule:: demos.input_usage
-.. automodule:: demos.output_usage
-.. automodule:: demos.chat_room
-"""
\ No newline at end of file
diff --git a/demos/__main__.py b/demos/__main__.py
index e6f627aa..8cc5b292 100644
--- a/demos/__main__.py
+++ b/demos/__main__.py
@@ -1,83 +1,12 @@
-import tornado.ioloop
-import tornado.web
-
-from demos.bmi import main as bmi
-from demos.chat_room import main as chat_room
-from demos.input_usage import main as input_usage
-from demos.output_usage import main as output_usage
-from demos.config import charts_demo_host
-from demos.doc_demo import get_app as get_doc_demo_app
-from demos.set_env_demo import main as set_env_demo
-
-from pywebio import STATIC_PATH
-from pywebio.output import put_markdown, put_row, put_html, style
-from pywebio.platform.tornado import webio_handler
-from tornado.options import define, options
-
-index_md = r"""### 基本demo
-
- - [BMI计算](./?pywebio_api=bmi): 根据身高体重计算BMI指数 [源码](https://github.com/wang0618/PyWebIO/blob/dev/demos/bmi.py)
- - [聊天室](./?pywebio_api=chat_room): 和当前所有在线的人聊天 [源码](https://github.com/wang0618/PyWebIO/blob/dev/demos/chat_room.py)
- - [输入演示](./?pywebio_api=input_usage): 演示PyWebIO输入模块的用法 [源码](https://github.com/wang0618/PyWebIO/blob/dev/demos/input_usage.py)
- - [输出演示](./?pywebio_api=output_usage): 演示PyWebIO输出模块的用法 [源码](https://github.com/wang0618/PyWebIO/blob/dev/demos/output_usage.py)
- - 更多Demo请见[文档](https://pywebio.readthedocs.io)中示例代码的在线Demo
-
-### 数据可视化demo
-PyWebIO还支持使用第三方库进行数据可视化
-
- - 使用`bokeh`进行数据可视化 [**demos**]({charts_demo_host}/?app=bokeh)
- - 使用`plotly`进行数据可视化 [**demos**]({charts_demo_host}/?app=plotly)
- - 使用`pyecharts`创建基于Echarts的图表 [**demos**]({charts_demo_host}/?app=pyecharts)
- - 使用`cutecharts.py`创建卡通风格图表 [**demos**]({charts_demo_host}/?app=cutecharts)
-
-**数据可视化demo截图**
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-### Links
-* PyWebIO Github [github.com/wang0618/PyWebIO](https://github.com/wang0618/PyWebIO)
-* 使用手册和实现文档见 [pywebio.readthedocs.io](https://pywebio.readthedocs.io)
-
-""".format(charts_demo_host=charts_demo_host)
-
-
-def index():
- style(put_row([
- put_markdown('# PyWebIO demos'),
- put_html('Star')
- ], size='1fr auto'), 'align-items:center')
- put_html('')
-
- put_markdown(index_md)
+import argparse
+from os import path
+from pywebio.platform import path_deploy
if __name__ == "__main__":
- define("port", default=8080, help="run on the given port", type=int)
- tornado.options.parse_command_line()
+ parser = argparse.ArgumentParser()
+ parser.add_argument("-p", "--port", default=8080, help="run on the given port", type=int)
+ args = parser.parse_args()
- application = tornado.web.Application([
- (r"/io", webio_handler(index)),
- (r"/bmi", webio_handler(bmi)),
- (r"/chat_room", webio_handler(chat_room)),
- (r"/input_usage", webio_handler(input_usage)),
- (r"/output_usage", webio_handler(output_usage)),
- (r"/doc_demo", webio_handler(get_doc_demo_app())),
- (r"/set_env_demo", webio_handler(set_env_demo)),
- (r"/(.*)", tornado.web.StaticFileHandler, {"path": STATIC_PATH, 'default_filename': 'index.html'})
- ])
- application.listen(port=options.port)
- tornado.ioloop.IOLoop.current().start()
+ here_dir = path.dirname(path.abspath(__file__))
+ path_deploy(here_dir, port=args.port)
diff --git a/demos/bmi.py b/demos/bmi.py
index 9ed57e20..df09bd40 100644
--- a/demos/bmi.py
+++ b/demos/bmi.py
@@ -1,29 +1,47 @@
-"""
-BMI指数计算
-^^^^^^^^^^^
-
-计算 `BMI指数 `_ 的简单应用
-
-:demo_host:`Demo地址 ?pywebio_api=bmi>` `源码 `_
-"""
from pywebio import start_server
from pywebio.input import *
from pywebio.output import *
-from pywebio.session import set_env
+from pywebio.session import info as session_info
+
+
+def t(eng, chinese):
+ """return English or Chinese text according to the user's browser language"""
+ return chinese if 'zh' in session_info.user_language else eng
def main():
- set_env(title="BMI Calculation")
+ """BMI Calculation
+
+ Simple application for calculating Body Mass Index.
+ 计算BMI指数的简单应用
+ """
- put_markdown("""# BMI指数
+ put_markdown(t("""# Body Mass Index
+
+ [Body mass index](https://en.wikipedia.org/wiki/Body_mass_index) (BMI) is a measure of body fat based on height and weight that applies to adult men and women.
+
+ BMI Categories:
+
+ | Category | BMI |
+ | -------------------- | ------------- |
+ | Severely underweight | BMI<14.9 |
+ | Underweight | 14.9≤BMI<18.4 |
+ | Normal | 18.4≤BMI<22.9 |
+ | Overweight | 22.9≤BMI<27.5 |
+ | Moderately obese | 27.5≤BMI<40 |
+ | Severely obese | BMI≥40 |
+
+ ## BMI calculation
+ The source code of this application is [here](https://github.com/wang0618/PyWebIO/blob/dev/demos/bmi.py)
+ """, """# BMI指数
[`BMI指数`](https://baike.baidu.com/item/%E4%BD%93%E8%B4%A8%E6%8C%87%E6%95%B0/1455733)(Body Mass Index,BMI),是用体重千克数除以身高米数的平方得出的数字,是国际上常用的衡量人体胖瘦程度以及是否健康的一个标准。
成年人的BMI值处于以下阶段
| 体形分类 | BMI值范围 |
- | -------- | --------- |
- | 极瘦 | BMI<14.9 |
+ | ------ | -------- |
+ | 极瘦 | BMI<14.9 |
| 偏瘦 | 14.9≤BMI<18.4 |
| 正常 | 18.4≤BMI<22.9 |
| 过重 | 22.9≤BMI<27.5 |
@@ -33,22 +51,22 @@ def main():
## BMI指数计算器
本程序的源代码[链接](https://github.com/wang0618/PyWebIO/blob/dev/demos/bmi.py)
- """, strip_indent=4)
+ """))
- info = input_group('计算BMI:', [
- input("请输入你的身高(cm)", name="height", type=FLOAT),
- input("请输入你的体重(kg)", name="weight", type=FLOAT),
+ info = input_group(t('BMI calculation', '计算BMI:'), [
+ input(t("Your Height(cm)", "请输入你的身高(cm)"), name="height", type=FLOAT),
+ input(t("Your Weight(kg)", "请输入你的体重(kg)"), name="weight", type=FLOAT),
])
BMI = info['weight'] / (info['height'] / 100) ** 2
- top_status = [(14.9, '极瘦'), (18.4, '偏瘦'),
- (22.9, '正常'), (27.5, '过重'),
- (40.0, '肥胖'), (float('inf'), '非常肥胖')]
+ top_status = [(14.9, t('Severely underweight', '极瘦')), (18.4, t('Underweight', '偏瘦')),
+ (22.9, t('Normal', '正常')), (27.5, t('Overweight', '过重')),
+ (40.0, t('Moderately obese', '肥胖')), (float('inf'), t('Severely obese', '非常肥胖'))]
for top, status in top_status:
if BMI <= top:
- put_markdown('你的 BMI 值: `%.1f`,身体状态:`%s`' % (BMI, status))
+ put_markdown(t('Your BMI: `%.1f`, Category: `%s`', '你的 BMI 值: `%.1f`,身体状态: `%s`') % (BMI, status))
break
diff --git a/demos/bokeh_app.py b/demos/bokeh_app.py
index 64d13e2c..d06424a5 100644
--- a/demos/bokeh_app.py
+++ b/demos/bokeh_app.py
@@ -7,6 +7,7 @@
from pywebio import start_server
from pywebio.output import *
+from pywebio.session import info as session_info
def bkapp(doc):
@@ -34,15 +35,24 @@ def callback(attr, old, new):
def main():
output_notebook(verbose=False, notebook_type='pywebio')
- put_markdown("""# Bokeh Applications in PyWebIO
-
- [Bokeh Applications](https://docs.bokeh.org/en/latest/docs/user_guide/server.html) 支持向图表的添加按钮、输入框等交互组件,并向组件添加Python回调,从而创建可以与Python代码交互的可视化图表。
-
- 在PyWebIO中,你也可以使用 `bokeh.io.show()` 来显示一个Bokeh App,和输出普通图表一样,只需要在会话开始时调用 `bokeh.io.output_notebook(notebook_type='pywebio')` 来设置PyWebIO输出环境。
-
- 以下为一个 Bokeh App demo:
-
- """, lstrip=True)
+ if 'zh' in session_info.user_language:
+ put_markdown("""# Bokeh Applications in PyWebIO
+ [Bokeh Applications](https://docs.bokeh.org/en/latest/docs/user_guide/server.html) 支持向图表的添加按钮、输入框等交互组件,并向组件添加Python回调,从而创建可以与Python代码交互的可视化图表。
+
+ 在PyWebIO中,你也可以使用 `bokeh.io.show()` 来显示一个Bokeh App,和输出普通图表一样,只需要在会话开始时调用 `bokeh.io.output_notebook(notebook_type='pywebio')` 来设置PyWebIO输出环境。
+
+ 以下为一个 Bokeh App demo:
+ """)
+ else:
+ put_markdown("""# Bokeh Applications in PyWebIO
+ [Bokeh Applications](https://docs.bokeh.org/en/latest/docs/user_guide/server.html) can be built by starting the Bokeh server. The purpose of the Bokeh server is to make it easy for Python users to create interactive web applications that can connect front-end UI events to real, running Python code.
+
+ In PyWebIO, you can also use bokeh.io.show() to display a Bokeh App.
+
+ You can use `bokeh.io.output_notebook(notebook_type='pywebio')` in the PyWebIO session to setup Bokeh environment. Then you can use `bokeh.io.show()` to output a boken application.
+
+ This is a demo of Bokeh App:
+ """)
show(bkapp)
diff --git a/demos/chat_room.py b/demos/chat_room.py
index 349a2e20..afe6e9ac 100644
--- a/demos/chat_room.py
+++ b/demos/chat_room.py
@@ -1,39 +1,32 @@
-"""
-聊天室
-^^^^^^^^^^^
-和当前所有在线的人聊天
-
-:demo_host:`Demo地址 ?pywebio_api=chat_room>` `源码 `_
-
-* 使用基于协程的会话
-* 使用 `run_async() ` 启动后台协程
-"""
import asyncio
-from pywebio import start_server, run_async
+from pywebio import start_server
from pywebio.input import *
from pywebio.output import *
-from pywebio.session import defer_call, set_env, run_js
+from pywebio.session import defer_call, info as session_info, run_async
-# 最大消息记录保存
MAX_MESSAGES_CNT = 10 ** 4
-chat_msgs = [] # 聊天记录 (name, msg)
-online_users = set() # 在线用户
+chat_msgs = [] # The chat message history. The item is (name, message content)
+online_users = set()
+
+
+def t(eng, chinese):
+ """return English or Chinese text according to the user's browser language"""
+ return chinese if 'zh' in session_info.user_language else eng
-async def refresh_msg(my_name, msg_box):
- """刷新聊天消息"""
+async def refresh_msg(my_name):
+ """send new message to current session"""
global chat_msgs
last_idx = len(chat_msgs)
while True:
await asyncio.sleep(0.5)
for m in chat_msgs[last_idx:]:
- if m[0] != my_name: # 仅刷新其他人的新信息
- msg_box.append(put_markdown('`%s`: %s' % m))
- run_js('$("#pywebio-scope-msg-container>div").animate({ scrollTop: $("#pywebio-scope-msg-container>div").prop("scrollHeight")}, 1000)') # hack: to scroll bottom
+ if m[0] != my_name: # only refresh message that not sent by current user
+ put_markdown('`%s`: %s' % m, sanitize=True, scope='msg-box')
- # 清理聊天记录
+ # remove expired message
if len(chat_msgs) > MAX_MESSAGES_CNT:
chat_msgs = chat_msgs[len(chat_msgs) // 2:]
@@ -41,43 +34,42 @@ async def refresh_msg(my_name, msg_box):
async def main():
- global chat_msgs
+ """PyWebIO chat room
- set_env(title="PyWebIO Chat Room")
+ You can chat with everyone currently online.
+ """
+ global chat_msgs
- put_markdown("##PyWebIO聊天室\n欢迎来到聊天室,你可以和当前所有在线的人聊天。你可以在浏览器的多个标签页中打开本页面来测试聊天效果。"
- "本应用使用不到80行代码实现,源代码[链接](https://github.com/wang0618/PyWebIO/blob/dev/demos/chat_room.py)", lstrip=True)
+ put_markdown(t("## PyWebIO chat room\nWelcome to the chat room, you can chat with all the people currently online. You can open this page in multiple tabs of your browser to simulate a multi-user environment. This application uses less than 90 lines of code, the source code is [here](https://github.com/wang0618/PyWebIO/blob/dev/demos/chat_room.py)", "## PyWebIO聊天室\n欢迎来到聊天室,你可以和当前所有在线的人聊天。你可以在浏览器的多个标签页中打开本页面来测试聊天效果。本应用使用不到90行代码实现,源代码[链接](https://github.com/wang0618/PyWebIO/blob/dev/demos/chat_room.py)"))
- msg_box = output()
- with use_scope('msg-container'):
- style(put_scrollable(msg_box, max_height=300), 'height:300px')
- nickname = await input("请输入你的昵称", required=True, validate=lambda n: '昵称已被使用' if n in online_users or n == '📢' else None)
+ put_scrollable(put_scope('msg-box'), height=300, keep_bottom=True)
+ nickname = await input(t("Your nickname", "请输入你的昵称"), required=True, validate=lambda n: t('This name is already been used', '昵称已被使用') if n in online_users or n == '📢' else None)
online_users.add(nickname)
- chat_msgs.append(('📢', '`%s`加入聊天室. 当前在线人数 %s' % (nickname, len(online_users))))
- msg_box.append(put_markdown('`📢`: `%s`加入聊天室. 当前在线人数 %s' % (nickname, len(online_users))))
+ chat_msgs.append(('📢', '`%s` joins the room. %s users currently online' % (nickname, len(online_users))))
+ put_markdown('`📢`: `%s` join the room. %s users currently online' % (nickname, len(online_users)), sanitize=True, scope='msg-box')
@defer_call
def on_close():
online_users.remove(nickname)
- chat_msgs.append(('📢', '`%s`退出聊天室. 当前在线人数 %s' % (nickname, len(online_users))))
+ chat_msgs.append(('📢', '`%s` leaves the room. %s users currently online' % (nickname, len(online_users))))
- refresh_task = run_async(refresh_msg(nickname, msg_box))
+ refresh_task = run_async(refresh_msg(nickname))
while True:
- data = await input_group('发送消息', [
- input(name='msg', help_text='消息内容支持Markdown 语法', required=True),
- actions(name='cmd', buttons=['发送', {'label': '退出', 'type': 'cancel'}])
- ])
+ data = await input_group(t('Send message', '发送消息'), [
+ input(name='msg', help_text=t('Message content supports inline Markdown syntax', '消息内容支持行内Markdown语法')),
+ actions(name='cmd', buttons=[t('Send', '发送'), t('Multiline Input', '多行输入'), {'label': t('Exit', '退出'), 'type': 'cancel'}])
+ ], validate=lambda d: ('msg', 'Message content cannot be empty') if d['cmd'] == t('Send', '发送') and not d['msg'] else None)
if data is None:
break
-
- msg_box.append(put_markdown('`%s`: %s' % (nickname, data['msg'])))
- run_js('$("#pywebio-scope-msg-container>div").animate({ scrollTop: $("#pywebio-scope-msg-container>div").prop("scrollHeight")}, 1000)') # hack: to scroll bottom
+ if data['cmd'] == t('Multiline Input', '多行输入'):
+ data['msg'] = '\n' + await textarea('Message content', help_text=t('Message content supports Markdown syntax', '消息内容支持Markdown语法'))
+ put_markdown('`%s`: %s' % (nickname, data['msg']), sanitize=True, scope='msg-box')
chat_msgs.append((nickname, data['msg']))
refresh_task.close()
- toast("你已经退出聊天室")
+ toast("You have left the chat room")
if __name__ == '__main__':
diff --git a/demos/chatgpt.py b/demos/chatgpt.py
new file mode 100644
index 00000000..d25c3b6c
--- /dev/null
+++ b/demos/chatgpt.py
@@ -0,0 +1,211 @@
+import json
+import time
+from typing import Dict, List
+
+from openai import OpenAI, Stream
+from openai.types.chat import ChatCompletionChunk
+
+import pywebio_battery
+from pywebio.input import *
+from pywebio.output import *
+from pywebio.pin import *
+from pywebio.session import set_env, download
+
+
+class ChatGPTStreamResponse:
+ """
+ A wrapper to Stream[ChatCompletionChunk], add a `result()` method to get the final result.
+ """
+ def __init__(self, response: Stream[ChatCompletionChunk]):
+ self.response = response
+ self.yielded = []
+ self.finish_reason = None
+
+ def __next__(self):
+ chunk = next(self.response)
+ self.finish_reason = chunk.choices[0].finish_reason
+ delta = chunk.choices[0].delta
+ if delta.content:
+ self.yielded.append(delta.content)
+ return delta.content
+
+ def __iter__(self):
+ return self
+
+ def result(self):
+ return ''.join(self.yielded)
+
+
+class ChatGPT:
+
+ def __init__(self, messages: List[Dict] = None, model: str = "gpt-3.5-turbo", client: OpenAI = None, **model_kwargs):
+ """
+ Create a chatgpt client
+
+ :param messages: A list of messages comprising the conversation so far.
+ Each message is a dict with keys "role" and "content".
+ See: https://platform.openai.com/docs/api-reference/chat/create#chat/create-messages
+ :param model: The model to use.
+ :param OpenAI client: The openai client to use. If not provided, a new client will be created.
+ :param model_kwargs: Other parameters to pass to model,
+ See https://platform.openai.com/docs/api-reference/chat
+ """
+ self._client = client or OpenAI()
+ self._messages = list(messages or [])
+ self.model_kwargs = dict(model=model, **model_kwargs)
+
+ self.pending_stream_reply: ChatGPTStreamResponse = None
+ self.latest_nonstream_finish_reason = None
+
+ def set_model(self, model: str):
+ """Set the model to use"""
+ self.model_kwargs['model'] = model
+
+ def _ask(self, message: str, stream=True, **model_kwargs):
+ if self.pending_stream_reply:
+ self._messages.append({"role": "assistant", "content": self.pending_stream_reply.result()})
+ self.pending_stream_reply = None
+
+ self._messages.append({"role": "user", "content": message})
+ resp = self._client.chat.completions.create(
+ **self.model_kwargs,
+ **model_kwargs,
+ messages=self._messages,
+ stream=stream,
+ )
+ return resp
+
+ def ask(self, message: str, **model_kwargs) -> str:
+ """
+ Send a message to chatgpt and get the reply in string
+
+ :param message: The message to send
+ :param model_kwargs: Other parameters to pass to openai.ChatCompletion.create()
+ :return: The reply from chatgpt
+ """
+ resp = self._ask(message, stream=False, **model_kwargs)
+ reply = resp['choices'][0]
+ reply_content = reply['message']['content']
+ self._messages.append({"role": "assistant", "content": reply_content})
+ self.latest_nonstream_finish_reason = reply['finish_reason']
+
+ return reply_content
+
+ def ask_stream(self, message: str, **model_kwargs) -> ChatGPTStreamResponse:
+ """
+ Send a message to chatgpt and get the reply in stream
+
+ :param message: The message to send
+ :param model_kwargs: Other parameters to pass to openai.ChatCompletion.create()
+ :return: A iterator that yields the reply from chatgpt.
+ The iterator will be exhausted when the reply is complete.
+ """
+ resp = self._ask(message, stream=True, **model_kwargs)
+ self.pending_stream_reply = ChatGPTStreamResponse(resp)
+ return self.pending_stream_reply
+
+ def latest_finish_reason(self) -> str:
+ """The finish reason for the latest reply of chatgpt.
+
+ The possible values for finish_reason are:
+ 'stop': API returned complete model output
+ 'length': Incomplete model output due to max_tokens parameter or token limit
+ 'content_filter': Omitted content due to a flag from our content filters
+ 'null': API response still in progress or incomplete
+
+ See: https://platform.openai.com/docs/guides/chat/response-format
+ """
+ if self.pending_stream_reply:
+ return self.pending_stream_reply.finish_reason
+ return self.latest_nonstream_finish_reason
+
+ def messages(self) -> List[Dict]:
+ """Get all messages of the conversation """
+ if self.pending_stream_reply:
+ self._messages.append({"role": "assistant", "content": self.pending_stream_reply.result()})
+ self.pending_stream_reply = None
+
+ return self._messages
+
+
+def get_openai_config():
+ openai_config = json.loads(pywebio_battery.get_localstorage('openai_config') or '{}')
+ if not openai_config:
+ openai_config = input_group('OpenAI API Config', [
+ input('API Key', name='api_key', type=TEXT, required=True,
+ help_text='Get your API key from https://platform.openai.com/account/api-keys'),
+ input('API Server', name='api_base', type=TEXT, value='https://api.openai.com', required=True),
+ ])
+ openai_config['api_base'] = openai_config['api_base'].removesuffix('/v1').strip('/') + '/v1'
+ pywebio_battery.set_localstorage('openai_config', json.dumps(openai_config))
+
+ put_button('Reset OpenAI API Key', reset_openai_config, link_style=True)
+ return openai_config
+
+
+def reset_openai_config():
+ pywebio_battery.set_localstorage('openai_config', json.dumps(None))
+ toast("Please refresh the page to take effect")
+
+
+def main():
+ """"""
+ set_env(input_panel_fixed=False, output_animation=False)
+ put_markdown("""
+ # ChatGPT
+ A ChatGPT client implemented with PyWebIO. [Source Code](https://github.com/pywebio/PyWebIO/blob/dev/demos/chatgpt.py)
+ TIPS: refresh page to open a new chat.
+ """)
+ put_select('model', ['gpt-3.5-turbo', 'gpt-4'], label='Model')
+
+ openai_config = get_openai_config()
+ client = OpenAI(api_key=openai_config['api_key'], base_url=openai_config['api_base'])
+
+ bot = ChatGPT(client=client, model=pin.model)
+ pin_on_change('model', lambda v: bot.set_model(v))
+ while True:
+ form = input_group('', [
+ input(name='msg', placeholder='Ask ChatGPT'),
+ actions(name='cmd', buttons=['Send', 'Multi-line Input', 'Save Chat'])
+ ])
+ if form['cmd'] == 'Multi-line Input':
+ form['msg'] = textarea(value=form['msg'])
+ elif form['cmd'] == 'Save Chat':
+ messages = [
+ msg['content'] if msg['role'] == 'user' else f"> {msg['content']}"
+ for msg in bot.messages()
+ ]
+ download(f"chatgpt_{time.strftime('%Y%m%d%H%M%S')}.md",
+ '\n\n'.join(messages).encode('utf8'))
+ continue
+
+ user_msg = form['msg']
+ if not user_msg:
+ continue
+
+ put_info(put_text(user_msg, inline=True))
+
+ with use_scope(f'reply-{int(time.time())}'):
+ put_loading('grow', 'info')
+ try:
+ reply_chunks = bot.ask_stream(user_msg)
+ except Exception as e:
+ popup('ChatGPT Error', put_error(e))
+ continue
+ finally:
+ clear() # clear loading
+ for chunk in reply_chunks:
+ put_text(chunk, inline=True)
+ clear() # clear above text
+ put_markdown(reply_chunks.result())
+
+ if bot.latest_finish_reason() == 'length':
+ put_error('Incomplete model output due to max_tokens parameter or token limit.')
+ elif bot.latest_finish_reason() == 'content_filter':
+ put_warning("Omitted content due to a flag from OpanAI's content filters.")
+
+
+if __name__ == '__main__':
+ from pywebio import start_server
+
+ start_server(main, port=8080, debug=True, cdn=False)
diff --git a/demos/config.py b/demos/config.py
index 0121da41..33d7f187 100644
--- a/demos/config.py
+++ b/demos/config.py
@@ -1,5 +1,5 @@
-# demos 模块的部署地址
-demo_host = 'http://pywebio-demos.demo.wangweimin.site'
+# The deployment address of the demos module
+demo_host = 'http://pywebio-demos.pywebio.online'
-# https://github.com/wang0618/pywebio-chart-gallery 的部署地址
-charts_demo_host = 'http://pywebio-charts.demo.wangweimin.site'
+# The deployment address of https://github.com/wang0618/pywebio-chart-gallery repo
+charts_demo_host = 'http://pywebio-charts.pywebio.online'
diff --git a/demos/doc_demo.py b/demos/doc_demo.py
index 9ddf9e73..281d201a 100644
--- a/demos/doc_demo.py
+++ b/demos/doc_demo.py
@@ -1,15 +1,45 @@
"""
-文档中示例代码在线运行
-^^^^^^^^^^^^^^^^
+Run the example code in the documentation online
"""
+import base64
+
+from functools import partial
+from os import path, listdir
from pywebio import start_server
+from pywebio.platform import config
+from pywebio.session import local as session_local, info as session_info
+import pywebio_battery
+
+##########################################
+# Pre-import modules for demo
+import time # lgtm [py/unused-import]
from pywebio.input import *
from pywebio.output import *
from pywebio.session import *
-from os import path, listdir
-from functools import partial
+from pywebio.pin import *
+from pywebio_battery import *
+
+##########################################
here_dir = path.dirname(path.abspath(__file__))
+playground_host = "https://play.pywebio.online"
+
+
+def t(eng, chinese):
+ """return English or Chinese text according to the user's browser language"""
+ return chinese if 'zh' in session_info.user_language else eng
+
+
+def playground(code):
+ pre_import = PRE_IMPORT
+ battery_apis = pywebio_battery.__all__
+ if 'pywebio_battery import' not in code and any(api in code for api in battery_apis):
+ pre_import += 'from pywebio_battery import *\n'
+
+ code = f"{pre_import}\n{code}"
+ encode = base64.b64encode(code.encode('utf8')).decode('utf8')
+ url = f"{playground_host}/#{encode}"
+ run_js('window.open(url)', url=url)
def gen_snippets(code):
@@ -21,44 +51,61 @@ def gen_snippets(code):
yield p.strip('\n')
-def run_code(code, scope, locals):
+def run_code(code, scope):
with use_scope(scope):
try:
- exec(code, globals(), locals)
+ """
+ Remember that at module level, globals and locals are the same dictionary.
+ If exec gets two separate objects as globals and locals,
+ the code will be executed as if it were embedded in a class definition.
+ https://docs.python.org/3/library/functions.html#exec
+ """
+ exec(code, session_local.globals)
except Exception as e:
- toast('代码产生异常:"%s:%s"' % (type(e).__name__, e), color='error')
+ toast('Exception occurred: "%s:%s"' % (type(e).__name__, e), color='error')
-IMPORT_CODE = """from pywebio.input import *
+PRE_IMPORT = """from pywebio.input import *
from pywebio.output import *
from pywebio.session import *
+from pywebio.pin import *
+from pywebio import start_server
+"""
+
+APP_TPL = f"""{PRE_IMPORT}
+def main():
+ %s
+
+start_server(main, port=8080, debug=True)
+"""
+CLIPBOARD_SETUP = """
+window.writeText = function(text) {
+ const input = document.createElement('textarea');
+ input.style.opacity = 0;
+ input.style.position = 'absolute';
+ input.style.left = '-100000px';
+ document.body.appendChild(input);
+
+ input.value = text;
+ input.select();
+ input.setSelectionRange(0, text.length);
+ document.execCommand('copy');
+ document.body.removeChild(input);
+ return true;
+}
"""
def copytoclipboard(code):
+ code = APP_TPL % code.replace('\n', '\n ')
run_js("writeText(text)", text=code)
- toast('已复制')
+ toast('The code has been copied to the clipboard')
def handle_code(code, title):
- run_js("""
- window.writeText = function(text) {
- const input = document.createElement('INPUT');
- input.style.opacity = 0;
- input.style.position = 'absolute';
- input.style.left = '-100000px';
- document.body.appendChild(input);
-
- input.value = text;
- input.select();
- input.setSelectionRange(0, text.length);
- document.execCommand('copy');
- document.body.removeChild(input);
- return true;
- }
- """)
- locals = {}
+ run_js(CLIPBOARD_SETUP)
+ session_local.globals = dict(globals())
if title:
put_markdown('## %s' % title)
@@ -66,43 +113,40 @@ def handle_code(code, title):
with use_scope() as scope:
put_code(p, 'python')
- put_buttons(['运行', '复制代码'], onclick=[
- partial(run_code, code=p, scope=scope, locals=locals),
- partial(copytoclipboard, code=IMPORT_CODE + p)
- ])
+ put_buttons(
+ [t('Run', '运行'),
+ t("Edit", '编辑'),
+ t("Copy to clipboard", '复制代码')],
+ onclick=[
+ partial(run_code, code=p, scope=scope),
+ partial(playground, code=p),
+ partial(copytoclipboard, code=p)
+ ]
+ )
put_markdown('----')
- hold()
-
def get_app():
+ """PyWebIO demos from document
+
+ Run the demos from the document online.
+ """
app = {}
try:
- demos = listdir(path.join(here_dir, 'doc_domes'))
+ demos = listdir(path.join(here_dir, 'doc_demos'))
except Exception:
demos = []
- demo_infos = []
for name in demos:
- code = open(path.join(here_dir, 'doc_domes', name)).read()
+ code = open(path.join(here_dir, 'doc_demos', name)).read()
title, code = code.split('\n\n', 1)
app[name] = partial(handle_code, code=code, title=title)
- demo_infos.append([name, title])
-
- index_html = ""
- for name, title in demo_infos:
- index_html += '''- {name}: {desc}
\n'''.format(
- name=name, desc=title)
- index_html += "
"
-
- def index():
- put_markdown('# PyWebIO Document Code Example Index')
- put_html(index_html)
+ app[name] = config(title=name, description=title)(app[name])
- app['index'] = index
return app
+main = get_app()
if __name__ == '__main__':
- start_server(get_app(), debug=True, port=8080)
+ start_server(main, debug=True, port=8080)
diff --git a/demos/gomoku_game.py b/demos/gomoku_game.py
new file mode 100644
index 00000000..0086d825
--- /dev/null
+++ b/demos/gomoku_game.py
@@ -0,0 +1,94 @@
+import time
+
+from pywebio import session, start_server
+from pywebio.output import *
+
+goboard_size = 15
+# -1 -> none, 0 -> black, 1 -> white
+goboard = [
+ [-1] * goboard_size
+ for _ in range(goboard_size)
+]
+
+
+def winner(): # return winner piece, return None if no winner
+ for x in range(2, goboard_size - 2):
+ for y in range(2, goboard_size - 2):
+ # check if (x,y) is the win center
+ if goboard[x][y] != -1 and any([
+ all(goboard[x][y] == goboard[m][n] for m, n in [(x - 2, y), (x - 1, y), (x + 1, y), (x + 2, y)]),
+ all(goboard[x][y] == goboard[m][n] for m, n in [(x, y - 2), (x, y - 1), (x, y + 1), (x, y + 2)]),
+ all(goboard[x][y] == goboard[m][n] for m, n in [(x - 2, y - 2), (x - 1, y - 1), (x + 1, y + 1), (x + 2, y + 2)]),
+ all(goboard[x][y] == goboard[m][n] for m, n in [(x - 2, y + 2), (x - 1, y + 1), (x + 1, y - 1), (x + 2, y - 2)]),
+ ]):
+ return ['⚫', '⚪'][goboard[x][y]]
+
+
+session_id = 0 # auto incremented id for each session
+current_turn = 0 # 0 for black, 1 for white
+player_count = [0, 0] # count of player for two roles
+
+
+def main():
+ """Online Shared Gomoku Game
+
+ A web based Gomoku (AKA GoBang, Five in a Row) game made with PyWebIO under 100 lines of Python code."""
+ global session_id, current_turn, goboard
+ if winner(): # The current game is over, reset game
+ goboard = [[-1] * goboard_size for _ in range(goboard_size)]
+ current_turn = 0
+
+ my_turn = session_id % 2
+ my_chess = ['⚫', '⚪'][my_turn]
+ session_id += 1
+ player_count[my_turn] += 1
+
+ @session.defer_call
+ def player_exit():
+ player_count[my_turn] -= 1
+
+ session.set_env(output_animation=False)
+ put_html("""""") # Custom styles to make the board more beautiful
+
+ put_markdown(f"""# Online Shared Gomoku Game
+ All online players are assigned to two groups (black and white) and share this game. You can open this page in multiple tabs of your browser to simulate multiple users. This application uses less than 100 lines of code, the source code is [here](https://github.com/wang0618/PyWebIO/blob/dev/demos/gomoku_game.py)
+ Currently online player: {player_count[0]} for ⚫, {player_count[1]} for ⚪. Your role is {my_chess}.
+ """)
+
+ def set_stone(pos):
+ global current_turn
+ if current_turn != my_turn:
+ toast("It's not your turn!!", color='error')
+ return
+ x, y = pos
+ goboard[x][y] = my_turn
+ current_turn = (current_turn + 1) % 2
+
+ @use_scope('goboard', clear=True)
+ def show_goboard():
+ table = [
+ [
+ put_buttons([dict(label=' ', value=(x, y), color='light')], onclick=set_stone) if cell == -1 else [' ⚫', ' ⚪'][cell]
+ for y, cell in enumerate(row)
+ ]
+ for x, row in enumerate(goboard)
+ ]
+ put_table(table)
+
+ show_goboard()
+ while not winner():
+ with use_scope('msg', clear=True):
+ current_turn_copy = current_turn
+ if current_turn_copy == my_turn:
+ put_text("It's your turn!")
+ else:
+ put_row([put_text("Your opponent's turn, waiting... "), put_loading().style('width:1.5em; height:1.5em')], size='auto 1fr')
+ while current_turn == current_turn_copy and not session.get_current_session().closed(): # wait for next move
+ time.sleep(0.2)
+ show_goboard()
+ with use_scope('msg', clear=True):
+ put_text('Game over. The winner is %s!\nRefresh page to start a new round.' % winner())
+
+
+if __name__ == '__main__':
+ start_server(main, debug=True, port=8080)
diff --git a/demos/index.py b/demos/index.py
new file mode 100644
index 00000000..856d9fc4
--- /dev/null
+++ b/demos/index.py
@@ -0,0 +1,119 @@
+from config import charts_demo_host
+
+from pywebio.output import put_markdown, put_row, put_html
+from pywebio.session import info as session_info
+
+index_md = r"""### Basic demo
+The source code of the demos can be found [here](https://github.com/pywebio/PyWebIO/tree/dev/demos).
+
+ - [BMI calculation](./bmi): Calculating Body Mass Index based on height and weight
+ - [Online chat room](./chat_room): Chat with everyone currently online (using less than 90 lines of code)
+ - [Markdown live preview](./markdown_previewer): The online markdown editor with live preview (using less than 40 lines of code)
+ - [Online Gomoku game](./gomoku_game): An online shared Gomoku game (using less than 100 lines of code)
+ - [Input demo](./input_usage): Demonstrate the usage of PyWebIO input module
+ - [Output demo](./output_usage): Demonstrate the usage of PyWebIO output module
+ - [ChatGPT](./chatgpt): A ChatGPT client implemented with PyWebIO
+ - [Wordle](./wordle): A wordle-like game implemented with PyWebIO
+ - [Theme preview](./theme): Demo page with various themes supported by PyWebIO
+
+### Data visualization demo
+PyWebIO supports for data visualization with the third-party libraries.
+
+ - Use `bokeh` for data visualization [**demos**]({charts_demo_host}/?app=bokeh)
+ - Use `plotly` for data visualization [**demos**]({charts_demo_host}/?app=plotly)
+ - Use `pyecharts` to create Echarts-based charts in Python [**demos**]({charts_demo_host}/?app=pyecharts)
+ - Use `pyg2plot` to create G2Plot-based charts in Python [**demos**]({charts_demo_host}/?app=pyg2plot)
+ - Use `cutecharts.py` to create hand drawing style charts [**demos**]({charts_demo_host}/?app=cutecharts)
+
+**Screenshots**
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+### Links
+* PyWebIO Github [github.com/wang0618/PyWebIO](https://github.com/wang0618/PyWebIO)
+* Document [pywebio.readthedocs.io](https://pywebio.readthedocs.io)
+* [PyWebIO Playground](https://play.pywebio.online/): Edit, Run, Share PyWebIO Code Online
+
+""".format(charts_demo_host=charts_demo_host)
+
+index_md_zh = r"""### 基本demo
+
+Demo源码[链接](https://github.com/pywebio/PyWebIO/tree/dev/demos)
+
+ - [BMI计算](./bmi): 根据身高体重计算BMI指数
+ - [聊天室](./chat_room): 和当前所有在线的人聊天 (不到90行代码实现)
+ - [Markdown实时预览](./markdown_previewer): 可以实时预览的在线Markdown编辑器 (不到40行代码实现)
+ - [在线五子棋游戏](./gomoku_game): 多人协作对战的五子棋游戏 (不到100行代码实现)
+ - [输入演示](./input_usage): 演示PyWebIO输入模块的用法
+ - [输出演示](./output_usage): 演示PyWebIO输出模块的用法
+ - [ChatGPT](./chatgpt): 使用PyWebIO编写的ChatGPT客户端
+ - [Wordle](./wordle): 使用PyWebIO编写的猜字游戏(wordle)
+ - [主题预览](./theme): 展示PyWebIO支持的各种主题
+ - 更多Demo请见[文档](https://pywebio.readthedocs.io)中示例代码的在线Demo
+
+### 数据可视化demo
+PyWebIO还支持使用第三方库进行数据可视化
+
+ - 使用`bokeh`进行数据可视化 [**demos**]({charts_demo_host}/?app=bokeh)
+ - 使用`plotly`进行数据可视化 [**demos**]({charts_demo_host}/?app=plotly)
+ - 使用`pyecharts`创建基于Echarts的图表 [**demos**]({charts_demo_host}/?app=pyecharts)
+ - 使用`pyg2plot`创建基于G2Plot的图表 [**demos**]({charts_demo_host}/?app=pyg2plot)
+ - 使用`cutecharts.py`创建卡通风格图表 [**demos**]({charts_demo_host}/?app=cutecharts)
+
+**数据可视化demo截图**
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+### Links
+* PyWebIO Github [github.com/wang0618/PyWebIO](https://github.com/wang0618/PyWebIO)
+* 使用手册和实现文档见 [pywebio.readthedocs.io](https://pywebio.readthedocs.io/zh_CN/latest/)
+* [PyWebIO Playground](https://play.pywebio.online/): 在线编辑、运行和分享PyWebIO代码
+
+""".format(charts_demo_host=charts_demo_host)
+
+
+def main():
+ """PyWebIO demos
+
+ Basic demo and data visualization demo of PyWebIO.
+ PyWebIO的基本demo和数据可视化demo
+ """
+ put_row([
+ put_markdown('# PyWebIO demos'),
+ put_html(
+ 'Star')
+ ], size='1fr auto').style('align-items:center')
+ put_html('')
+
+ if 'zh' in session_info.user_language:
+ put_markdown(index_md_zh)
+ else:
+ put_markdown(index_md)
diff --git a/demos/input_usage.py b/demos/input_usage.py
index 6bd83289..1a06475a 100644
--- a/demos/input_usage.py
+++ b/demos/input_usage.py
@@ -1,20 +1,41 @@
-"""
-输入演示
-^^^^^^^^^^^
-演示PyWebIO支持的各种输入形式
+import json
-:demo_host:`Demo地址 ?pywebio_api=input_usage>` `源码 `_
-"""
from pywebio import start_server
from pywebio.input import *
from pywebio.output import *
-from pywebio.session import set_env
+from pywebio.session import set_env, info as session_info
+
+
+def t(eng, chinese):
+ """return English or Chinese text according to the user's browser language"""
+ return chinese if 'zh' in session_info.user_language else eng
def main():
- set_env(title="PyWebIO输入演示", auto_scroll_bottom=True)
+ """PyWebIO Input Usage
- put_markdown("""# PyWebIO 输入演示
+ Demonstrate various input usage supported by PyWebIO.
+ 演示PyWebIO输入模块的使用
+ """
+ set_env(auto_scroll_bottom=True)
+
+ put_markdown(t("""# PyWebIO Input Example
+
+ You can get the source code of this demo in [here](https://github.com/wang0618/PyWebIO/blob/dev/demos/input_usage.py)
+
+ This demo only introduces part of the functions of the PyWebIO input module. For the complete features, please refer to the [User Guide](https://pywebio.readthedocs.io/zh_CN/latest/guide.html).
+
+ The input functions are all defined in the `pywebio.input` module and can be imported using `from pywebio.input import *`.
+
+ ### Basic input
+ Here are some basic types of input.
+
+ #### Text input
+ ```python
+ name = input("What's your name?")
+ ```
+ """,
+ """# PyWebIO 输入演示
在[这里](https://github.com/wang0618/PyWebIO/blob/dev/demos/input_usage.py)可以获取本Demo的源码。
@@ -29,13 +50,34 @@ def main():
```python
name = input("What's your name?")
```
- """, lstrip=True)
- put_text("这样一行代码的效果如下:",)
+ """))
+ put_text(t("The results of the above example are as follows:", "这样一行代码的效果如下:"))
name = input("What's your name?")
put_markdown("`name = %r`" % name)
# 其他类型的输入
- put_markdown("""PyWebIO的输入函数是同步的,在表单被提交之前,输入函数不会返回。
+ put_markdown(t("""
+ PyWebIO’s input functions is blocking and will not return until the form is successfully submitted.
+ #### Other types of input
+ Here are some other types of input functions:
+ ```python
+ # Password input
+ password = input("Input password", type=PASSWORD)
+
+ # Drop-down selection
+ gift = select('Which gift you want?', ['keyboard', 'ipad'])
+
+ # CheckBox
+ agree = checkbox("User Term", options=['I agree to terms and conditions'])
+
+ # Text Area
+ text = textarea('Text Area', rows=3, placeholder='Some text')
+
+ # File Upload
+ img = file_upload("Select a image:", accept="image/*")
+ ```
+ """, """
+ PyWebIO的输入函数是同步的,在表单被提交之前,输入函数不会返回。
#### 其他类型的输入:
```python
# 密码输入
@@ -53,35 +95,45 @@ def main():
# 文件上传
img = file_upload("Select a image:", accept="image/*")
```
- """, lstrip=True)
+ """))
password = input("Input password", type=PASSWORD)
put_markdown("`password = %r`" % password)
gift = select('Which gift you want?', ['keyboard', 'ipad'])
put_markdown("`gift = %r`" % gift)
- agree = checkbox("用户协议", options=['I agree to terms and conditions'])
+ agree = checkbox(t("User Term", "用户协议"), options=['I agree to terms and conditions'])
put_markdown("`agree = %r`" % agree)
text = textarea('Text Area', rows=3, placeholder='Some text')
put_markdown("`text = %r`" % text)
- img = file_upload("Select a image:", accept="image/*", help_text='可以直接选择"提交"')
- put_markdown("`img = %r`" % img)
+ img = file_upload("Select a image:", accept="image/*", help_text=t('You can just click "Submit" button', '可以直接选择"提交"'))
+ if img is None:
+ put_markdown("`img = %r`" % img)
+ else:
+ img['content'] = '...'
+ img.pop('dataurl', None)
+ put_code(json.dumps(img, indent=4, ensure_ascii=False).replace('"..."', '...'), 'json')
# 输入选项
- put_markdown("""#### 输入选项
+ put_markdown(t("""#### Parameter of input functions
+ There are many parameters that can be passed to the input function:
+ """, """#### 输入选项
输入函数可指定的参数非常丰富:
+ """))
+ put_markdown("""
```python
input('This is label', type=TEXT, placeholder='This is placeholder',
help_text='This is help text', required=True,
datalist=['candidate1', 'candidate2', 'candidate2'])
```
- """, strip_indent=4)
+ """)
input('This is label', type=TEXT, placeholder='This is placeholder',
help_text='This is help text', required=True,
datalist=['candidate1', 'candidate2', 'candidate2'])
# 校验函数
- put_markdown("""我们可以为输入指定校验函数,校验函数校验通过时返回None,否则返回错误消息:
+ put_markdown(t("""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:""", """我们可以为输入指定校验函数,校验函数校验通过时返回`None`,否则返回错误消息:"""), strip_indent=4)
+ put_markdown("""
```python
- def check_age(p): # 检验函数校验通过时返回None,否则返回错误消息
+ def check_age(p): # return None when the check passes, otherwise return the error message
if p < 10:
return 'Too young!!'
if p > 60:
@@ -89,7 +141,7 @@ def check_age(p): # 检验函数校验通过时返回None,否则返回错误
age = input("How old are you?", type=NUMBER, validate=check_age)
```
- """, strip_indent=4)
+ """)
def check_age(p): # 检验函数校验通过时返回None,否则返回错误消息
if p < 10:
@@ -97,18 +149,19 @@ def check_age(p): # 检验函数校验通过时返回None,否则返回错误
if p > 60:
return 'Too old!!'
- age = input("How old are you?", type=NUMBER, validate=check_age, help_text='尝试输入一些非法值,比如"8"、"65"')
+ age = input("How old are you?", type=NUMBER, validate=check_age, help_text=t('Try to input some illegal values, such as "8", "65"', '尝试输入一些非法值,比如"8"、"65"'))
put_markdown('`age = %r`' % age)
# Codemirror
- put_markdown(r"""PyWebIO 的 `textarea()` 输入函数还支持使用 [Codemirror](https://codemirror.net/) 实现代码风格的编辑区,只需使用 `code` 参数传入Codemirror支持的选项即可(最简单的情况是直接传入` code={}` 或 `code=True`):
+ put_markdown(t("""You can use `code` parameter in `pywebio.input.textarea()` to create a code editing textarea:""", """PyWebIO 的 `textarea()` 输入函数还支持使用 [Codemirror](https://codemirror.net/) 实现代码风格的编辑区,只需使用 `code` 参数传入Codemirror支持的选项即可(最简单的情况是直接传入` code={}` 或 `code=True`):"""), strip_indent=4)
+ put_markdown(r"""
```python
code = textarea('Code Edit', code={
- 'mode': "python", # 编辑区代码语言
- 'theme': 'darcula', # 编辑区darcula主题
+ 'mode': "python", # code language
+ 'theme': 'darcula', # Codemirror theme
}, value='import something\n# Write your python code')
```
- """, strip_indent=4)
+ """)
code = textarea('Code Edit', code={
'mode': "python", # 编辑区代码语言
@@ -118,28 +171,32 @@ def check_age(p): # 检验函数校验通过时返回None,否则返回错误
put_markdown("Your code:\n```python\n%s\n```" % code)
# 输入组
- put_markdown(r"""### 输入组
- `input_group()` 接受单项输入组成的列表作为参数,输入组中需要在每一项输入函数中提供 `name` 参数来用于在结果中标识不同输入项。输入组中同样支持设置校验函数,其接受整个表单数据作为参数。
-
+ put_markdown(t("""### Input Group
+ `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.
+ The input group also supports using `validate` parameter to set the validation function, which accepts the entire form data as parameter:""",
+ """### 输入组
+ `input_group()` 接受单项输入组成的列表作为参数,输入组中需要在每一项输入函数中提供 `name` 参数来用于在结果中标识不同输入项。输入组中同样支持设置校验函数,其接受整个表单数据作为参数。检验函数校验通过时返回None,否则返回 `(input name,错误消息)`
+ """))
+ put_markdown(r"""
```python
- def check_form(data): # 检验函数校验通过时返回None,否则返回 (input name,错误消息)
+ def check_form(data): # input group validation: return (input name, error msg) when validation fail
if len(data['name']) > 6:
- return ('name', '名字太长!')
+ return ('name', 'Name too long!')
if data['age'] <= 0:
- return ('age', '年龄不能为负数!')
+ return ('age', 'Age can not be negative!')
data = input_group("Basic info", [
input('Input your name', name='name'),
input('Input your age', name='age', type=NUMBER, validate=check_age)
], validate=check_form)
```
- """, strip_indent=4)
+ """)
- def check_form(data): # 检验函数校验通过时返回None,否则返回 (input name,错误消息)
+ def check_form(data): # input group validation: return (input name, error msg) when validation fail
if len(data['name']) > 6:
- return ('name', '名字太长!')
+ return ('name', 'Name too long!')
if data['age'] <= 0:
- return ('age', '年龄不能为负数!')
+ return ('age', 'Age can not be negative!')
data = input_group("Basic info", [
input('Input your name', name='name'),
@@ -148,9 +205,11 @@ def check_form(data): # 检验函数校验通过时返回None,否则返回 (i
put_markdown("`data = %r`" % data)
- put_markdown("""----
+ put_markdown(t("""----
+ For more information about input of PyWebIO, please visit PyWebIO [User Guide](https://pywebio.readthedocs.io/zh_CN/latest/guide.html) and [input module documentation](https://pywebio.readthedocs.io/zh_CN/latest/input.html).
+ """, """----
PyWebIO的输入演示到这里就结束了,更多内容请访问PyWebIO[用户指南](https://pywebio.readthedocs.io/zh_CN/latest/guide.html)和[input模块文档](https://pywebio.readthedocs.io/zh_CN/latest/input.html)。
- """, lstrip=True)
+ """))
if __name__ == '__main__':
diff --git a/demos/markdown_previewer.py b/demos/markdown_previewer.py
new file mode 100644
index 00000000..21ed728d
--- /dev/null
+++ b/demos/markdown_previewer.py
@@ -0,0 +1,28 @@
+from pywebio import start_server
+
+from pywebio.output import *
+from pywebio.pin import *
+from pywebio.session import set_env, download
+
+
+def main():
+ """Markdown Previewer"""
+ set_env(output_animation=False)
+
+ put_markdown("""# Markdown Live Preview
+ The online markdown editor with live preview. The source code of this application is [here](https://github.com/wang0618/PyWebIO/blob/dev/demos/markdown_previewer.py).
+ ## Write your Markdown
+ """)
+ put_textarea('md_text', rows=18, code={'mode': 'markdown'})
+
+ put_buttons(['Download content'], lambda _: download('saved.md', pin.md_text.encode('utf8')), small=True)
+
+ put_markdown('## Preview')
+ while True:
+ change_detail = pin_wait_change('md_text')
+ with use_scope('md', clear=True):
+ put_markdown(change_detail['value'], sanitize=False)
+
+
+if __name__ == '__main__':
+ start_server(main, port=8080, debug=True)
diff --git a/demos/output_usage.py b/demos/output_usage.py
index 1315d5f5..86ef1eae 100644
--- a/demos/output_usage.py
+++ b/demos/output_usage.py
@@ -1,16 +1,14 @@
-"""
-输出演示
-^^^^^^^^^^^
-演示PyWebIO支持的各种输出形式
-
-:demo_host:`Demo地址 ?pywebio_api=output_usage>` `源码 `_
-"""
from pywebio import start_server
from pywebio.output import *
-from pywebio.session import hold, set_env
+from pywebio.session import info as session_info
from functools import partial
+def t(eng, chinese):
+ """return English or Chinese text according to the user's browser language"""
+ return chinese if 'zh' in session_info.user_language else eng
+
+
def code_block(code, strip_indent=4):
if strip_indent:
lines = (
@@ -26,14 +24,25 @@ def run_code(code, scope):
with use_scope() as scope:
put_code(code, 'python')
- put_buttons([{'label': '运行', 'value': '', 'color': 'success'}],
+ put_buttons([{'label': t('Run', '运行'), 'value': '', 'color': 'success'}],
onclick=[partial(run_code, code=code, scope=scope)], small=True)
async def main():
- set_env(title="PyWebIO输出演示")
+ """PyWebIO Output Usage
- put_markdown("""# PyWebIO 输出演示
+ Demonstrate various output usage supported by PyWebIO.
+ 演示PyWebIO输出模块的使用
+ """
+ put_markdown(t("""# PyWebIO Output demo
+
+ You can get the source code of this demo in [here](https://github.com/wang0618/PyWebIO/blob/dev/demos/output_usage.py)
+
+ This demo only introduces part of the functions of the PyWebIO output module. For the complete features, please refer to the [User Guide](https://pywebio.readthedocs.io/zh_CN/latest/guide.html).
+
+ The output functions are all defined in the `pywebio.output` module and can be imported using `from pywebio.output import *`.
+
+ """, """# PyWebIO 输出演示
在[这里](https://github.com/wang0618/PyWebIO/blob/dev/demos/output_usage.py)可以获取本Demo的源码。
@@ -43,9 +52,25 @@ async def main():
### 基本输出
PyWebIO提供了一些便捷函数来输出表格、链接等格式:
- """, strip_indent=4)
+ """))
- code_block(r"""
+ code_block(t(r"""
+ # Text Output
+ put_text("Hello world!")
+
+ # Table Output
+ put_table([
+ ['Commodity', 'Price'],
+ ['Apple', '5.5'],
+ ['Banana', '7'],
+ ])
+
+ # Markdown Output
+ put_markdown('~~Strikethrough~~')
+
+ # File Output
+ put_file('hello_word.txt', b'hello word!')
+ """, r"""
# 文本输出
put_text("Hello world!")
@@ -61,22 +86,28 @@ async def main():
# 文件输出
put_file('hello_word.txt', b'hello word!')
- """)
+ """))
+
+ put_markdown(t(r"""For all output functions provided by PyWebIO, please refer to the [document](https://pywebio.readthedocs.io/en/latest/output.html#output-func-list).
+
+ ### Combined Output
+ The output functions whose name starts with `put_` can be combined with some output functions as part of the final output:
- put_markdown(r"""PyWebIO提供的全部输出函数请参考PyWebIO文档
+ You can pass `put_xxx()` calls to `put_table()` as cell content:
+ """, r"""PyWebIO提供的全部输出函数请参考[PyWebIO文档](https://pywebio.readthedocs.io/zh_CN/latest/output.html#output-func-list)
### 组合输出
函数名以 `put_` 开始的输出函数,可以与一些输出函数组合使用,作为最终输出的一部分。
比如`put_table()`支持以`put_xxx()`调用作为单元格内容:
- """, strip_indent=4)
+ """))
code_block(r"""
put_table([
['Type', 'Content'],
['html', put_html('X2')],
- ['text', '
'], # 等价于 ['text', put_text('
')]
+ ['text', '
'], # equal to ['text', put_text('
')]
['buttons', put_buttons(['A', 'B'], onclick=toast)],
['markdown', put_markdown('`Awesome PyWebIO!`')],
['file', put_file('hello.text', b'hello world')],
@@ -84,25 +115,32 @@ async def main():
])
""")
- put_markdown(r"""类似地,`popup()`也可以将`put_xxx()`调用作为弹窗内容:
-
- """, strip_indent=4)
+ put_markdown(t(r"Similarly, you can pass `put_xxx()` calls to `popup()` as the popup content:",
+ r"类似地,`popup()`也可以将`put_xxx()`调用作为弹窗内容:"))
code_block(r"""
popup('Popup title', [
put_html('Popup Content
'),
- 'plain html:
', # 等价于 put_text('plain html:
')
+ 'plain html:
', # equal to put_text('plain html:
')
put_table([['A', 'B'], ['C', 'D']]),
put_buttons(['close_popup()'], onclick=lambda _: close_popup())
])
""")
- put_markdown(r"更多接受`put_xxx()`作为参数的输出函数请参考函数文档。")
+ put_markdown(t(r"For more output functions that accept `put_xxx()` calls as parameters, please refer to the [document](https://pywebio.readthedocs.io/en/latest/output.html#output-func-list).",
+ r"更多接受`put_xxx()`作为参数的输出函数请参考[函数文档](https://pywebio.readthedocs.io/zh_CN/latest/output.html#output-func-list)。"))
- put_markdown(r"""### 事件回调
+ put_markdown(t(r"""### Callback
+ PyWebIO allows you to output some buttons and bind callbacks to them. The provided callback function will be executed when the button is clicked.
+
+ This is an example:%s
+ The call to `put_table()` will not block. When user clicks a button, the corresponding callback function will be invoked:
+ """, r"""### 事件回调
PyWebIO允许你输出一些控件,当控件被点击时执行提供的回调函数,就像编写GUI程序一样。
- 下面是一个例子:
+ 下面是一个例子:%s
+ `put_table()`的调用不会阻塞。当用户点击了某行中的按钮时,PyWebIO会自动调用相应的回调函数:
+ """) % """
```python
from functools import partial
@@ -116,9 +154,7 @@ def edit_row(choice, row):
[3, put_buttons(['edit', 'delete'], onclick=partial(edit_row, row=3))],
])
```
- `put_table()`的调用不会阻塞。当用户点击了某行中的按钮时,PyWebIO会自动调用相应的回调函数:
-
- """, strip_indent=4)
+ """)
from functools import partial
@@ -134,23 +170,58 @@ def edit_row(choice, row):
])
set_scope('table-callback')
- put_markdown(r"""当然,PyWebIO还支持单独的按钮控件:
+ put_markdown(t("Of course, PyWebIO also supports outputting individual buttons:", "当然,PyWebIO还支持单独的按钮控件:")+r"""
```python
def btn_click(btn_val):
put_markdown("> You click `%s` button" % btn_val)
- put_buttons(['A', 'B', 'C'], onclick=btn_click)
+ put_buttons(['A', 'B', 'C'], onclick=btn_click) # a group of buttons
+
+ put_button("Click me", onclick=lambda: toast("Clicked")) # single button
```
- """, strip_indent=4)
+ """)
@use_scope('button-callback')
def btn_click(btn_val):
put_markdown("> You click `%s` button" % btn_val)
put_buttons(['A', 'B', 'C'], onclick=btn_click)
+ put_button("Click me", onclick=lambda: toast("Clicked"))
set_scope('button-callback')
- put_markdown(r"""### 输出域Scope
+ put_markdown(t("In fact, all output can be bound to click events, not just buttons. You can call `onclick()` method after the output function (function name like `put_xxx()`) call:", "事实上,不仅是按钮,所有的输出都可以绑定点击事件。你可以在输出函数之后调用 `onclick()` 方法来绑定点击事件:")+r"""
+ ```python
+ put_image('some-image.png').onclick(lambda: toast('You click the image'))
+
+ # set onclick in combined output
+ put_table([
+ ['Commodity', 'Price'],
+ ['Apple', put_text('5.5').onclick(lambda: toast('You click the text'))],
+ ])
+ ```
+ """)
+
+ put_image('https://www.python.org/static/img/python-logo.png').onclick(lambda: toast('You click the image'))
+ # set onclick in combined output
+ put_table([
+ ['Commodity', 'Price'],
+ ['Apple', put_text('5.5').onclick(lambda: toast('You click the text'))],
+ ])
+
+ put_markdown(t("The return value of `onclick()` method is the object itself so it can be used in combined output.",
+ "`onclick()` 方法的返回值为对象本身,所以可以继续用于组合输出中。"))
+
+ put_markdown(t(r"""### Output Scope
+
+ 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.
+
+ The output domain is a container of output content, and each output domain is arranged vertically, and the output domains can also be nested.
+
+ 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.
+
+ You can use `use_scope()` to open and enter a new output scope, or enter an existing output scope: %s
+ The above code will generate the following Scope layout:
+ """, r"""### 输出域Scope
PyWebIO使用Scope模型来对内容输出的位置进行灵活地控制,PyWebIO的内容输出区可以划分出不同的输出域,PyWebIO将输出域称作`Scope`。
@@ -158,8 +229,9 @@ def btn_click(btn_val):
每个输出函数(函数名形如 `put_xxx()` )都会将内容输出到一个Scope,默认为”当前Scope”,”当前Scope”由运行时上下文确定,输出函数也可以手动指定输出到的Scope。Scope名在会话内唯一。
- 可以使用 `use_scope()` 开启并进入一个新的输出域,或进入一个已经存在的输出域:
-
+ 可以使用 `use_scope()` 开启并进入一个新的输出域,或进入一个已经存在的输出域: %s
+ 以上代码将会产生如下Scope布局:
+ """) % """
```python
with use_scope('A'):
put_text('Text in scope A')
@@ -170,8 +242,7 @@ def btn_click(btn_val):
with use_scope('C'):
put_text('Text in scope C')
```
- 以上代码将会产生如下Scope布局:
- """, strip_indent=4)
+ """)
with use_scope('A'):
put_text('Text in scope A')
@@ -187,37 +258,52 @@ def btn_click(btn_val):
#pywebio-scope-C {border: 1px solid green;margin-top:2px}
""")
- put_markdown(r"""
+ put_markdown(t(r"""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()`.
+
+ In addition, you can use the `scope` parameter of the output function to specify the destination scope to output:
+ """, r"""
输出函数(函数名形如 `put_xxx()` )在默认情况下,会将内容输出到”当前Scope”,可以通过 `use_scope()` 设置运行时上下文的”当前Scope”。
此外,也可以通过输出函数的 scope 参数指定输出的目的Scope:
- """, strip_indent=4)
+ """))
put_grid([
- [put_code("put_text('A', scope='A')", 'python'), None, put_buttons(['运行'], [lambda: put_text('A', scope='A')])],
- [put_code("put_text('B', scope='B')", 'python'), None, put_buttons(['运行'], [lambda: put_text('B', scope='B')])],
- [put_code("put_text('C', scope='C')", 'python'), None, put_buttons(['运行'], [lambda: put_text('C', scope='C')])],
+ [put_code("put_text('A', scope='A')", 'python'), None, put_buttons([t('Run', '运行')], [lambda: put_text('A', scope='A')])],
+ [put_code("put_text('B', scope='B')", 'python'), None, put_buttons([t('Run', '运行')], [lambda: put_text('B', scope='B')])],
+ [put_code("put_text('C', scope='C')", 'python'), None, put_buttons([t('Run', '运行')], [lambda: put_text('C', scope='C')])],
], cell_widths='1fr 10px auto')
- put_markdown(r"""输出函数可以使用position参数指定内容在Scope中输出的位置
+ put_markdown(t("The output content can be inserted into any positions of the target scope by using the `position` parameter of the output function.", "输出函数可以使用`position`参数指定内容在Scope中输出的位置") + """
```python
put_text(now(), scope='A', position=...)
```
- """, strip_indent=4)
+ """)
import datetime
put_buttons([('position=%s' % i, i) for i in [1, 2, 3, -1, -2, -3]],
lambda i: put_text(datetime.datetime.now(), position=i, scope='A'), small=True)
- put_markdown(r"除了 `use_scope()` , PyWebIO同样提供了以下scope控制函数: ")
+ put_markdown(t(r"In addition to `use_scope()`, PyWebIO also provides the following scope control functions:",
+ r"除了 `use_scope()` , PyWebIO同样提供了以下scope控制函数: "))
put_grid([
- [put_code("clear('B') # 清除Scope B中的内容", 'python'), None, put_buttons(['运行'], [lambda: clear('B')])],
- [put_code("remove('C') # 移除Scope C", 'python'), None, put_buttons(['运行'], [lambda: remove('C')])],
- [put_code("scroll_to('A') # 将页面滚动到Scope A处", 'python'), None, put_buttons(['运行'], [lambda: scroll_to('A')])],
+ [put_code("clear('B') # Clear content of Scope B", 'python'), None, put_buttons(['运行'], [lambda: clear('B')])],
+ [put_code("remove('C') # Remove Scope C", 'python'), None, put_buttons(['运行'], [lambda: remove('C')])],
+ [put_code("scroll_to('A') # Scroll the page to position of Scope A", 'python'), None, put_buttons(['运行'], [lambda: scroll_to('A')])],
], cell_widths='1fr 10px auto')
- put_markdown(r"""### 布局
+ put_markdown(t(r"""### Layout
+
+ In general, using the various output functions introduced above is enough to output what you want, but these outputs are arranged vertically. If you want to make 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.
+
+ The `pywebio.output` module provides 3 layout functions, and you can create complex layouts by combining them:
+
+ - `put_row()` : Use row layout to output content. The content is arranged horizontally
+ - `put_column()` : Use column layout to output content. The content is arranged vertically
+ - `put_grid()` : Output content using grid layout
+
+ Here is an example by combining `put_row()` and `put_column()`:
+ """, r"""### 布局
一般情况下,使用上文介绍的各种输出函数足以完成各种内容的展示,但直接调用输出函数产生的输出之间都是竖直排列的,如果想实现更复杂的布局(比如在页 面左侧显示一个代码块,在右侧显示一个图像),就需要借助布局函数。
`pywebio.output` 模块提供了3个布局函数,通过对他们进行组合可以完成各种复杂的布局:
@@ -227,14 +313,14 @@ def btn_click(btn_val):
- `put_grid()` : 使用网格布局输出内容
比如,通过通过组合 `put_row()` 和 `put_column()` 实现的布局:
- """, strip_indent=4)
+ """))
code_block(r"""
put_row([
put_column([
put_code('A'),
put_row([
- put_code('B1'), None, # None 表示输出之间的空白
+ put_code('B1'), None, # %s
put_code('B2'), None,
put_code('B3'),
]),
@@ -243,45 +329,33 @@ def btn_click(btn_val):
put_code('D'), None,
put_code('E')
])
- """)
+ """ % t('None represents the space between the output', 'None 表示输出之间的空白'))
+
+ put_markdown(t(r"""### Style
+ If you are familiar with CSS styles, you can use the `style()` method to set a custom style for the output.
- put_markdown(r"""
- ### 样式
+ You can set the CSS style for a single `put_xxx()` output:
+ """, r"""### 样式
- 如果你熟悉 CSS样式 ,你还可以使用 `style()` 函数给输出设定自定义样式。
+ 如果你熟悉 CSS样式 ,你还可以使用 `style()` 方法给输出设定自定义样式。
可以给单个的 `put_xxx()` 输出设定CSS样式,也可以配合组合输出使用:
- """, strip_indent=4)
+ """))
code_block(r"""
- style(put_text('Red'), 'color: red')
+ put_text('Red').style('color: red')
put_table([
['A', 'B'],
- ['C', style(put_text('Red'), 'color: red')],
+ ['C', put_text('Red').style('color: red')],
])
""", strip_indent=4)
- put_markdown(r"`style()` 也接受列表作为输入:")
-
- code_block(r"""
- style([
- put_text('Red'),
- put_markdown('~~del~~')
- ], 'color: red')
-
- put_collapse('title', style([
- put_text('text'),
- put_markdown('~~del~~'),
- ], 'margin-left: 20px'))
-
- """, strip_indent=4)
-
- put_markdown("""----
+ put_markdown(t("""----
+ For more information about output of PyWebIO, please visit PyWebIO [User Guide](https://pywebio.readthedocs.io/zh_CN/latest/guide.html) and [output module documentation](https://pywebio.readthedocs.io/zh_CN/latest/output.html).
+ """, """----
PyWebIO的输出演示到这里就结束了,更多内容请访问PyWebIO[用户指南](https://pywebio.readthedocs.io/zh_CN/latest/guide.html)和[output模块文档](https://pywebio.readthedocs.io/zh_CN/latest/output.html)。
- """, lstrip=True)
-
- await hold()
+ """))
if __name__ == '__main__':
diff --git a/demos/set_env_demo.py b/demos/set_env_demo.py
index c028178f..99cbc502 100644
--- a/demos/set_env_demo.py
+++ b/demos/set_env_demo.py
@@ -1,7 +1,3 @@
-"""
-`pywebio.session.set_env()` demo
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-"""
from pywebio import start_server
from pywebio.input import *
from pywebio.output import *
@@ -10,22 +6,31 @@
import asyncio
+def t(eng, chinese):
+ """return English or Chinese text according to the user's browser language"""
+ return chinese if 'zh' in info.user_language else eng
+
+
async def main():
+ """`pywebio.session.set_env()` demo"""
+
set_scope('time')
- put_markdown('> 可用于观察 `output_animation` 项的动画效果')
+ put_markdown(t('> Can be used to observe the animation effect of the `output_animation` setting',
+ '> 可用于观察 `output_animation` 项的动画效果'))
put_markdown('---')
async def bg_task():
while 1:
with use_scope('time', clear=True):
- put_text('当前时间:', datetime.datetime.now())
+ put_text('Current time:', datetime.datetime.now())
await asyncio.sleep(1)
run_async(bg_task())
- put_buttons(['输出文本'], [lambda: put_text(datetime.datetime.now())])
- put_markdown('> 可用于观察 `auto_scroll_bottom` 项的自动滚动效果')
+ put_buttons([t('Output some texts', '输出文本')], [lambda: put_text(datetime.datetime.now())])
+ put_markdown(t('> Can be used to observe the automatic scrolling effect of the `auto_scroll_bottom` setting',
+ '> 可用于观察 `auto_scroll_bottom` 项的自动滚动效果'))
put_markdown('---')
put_text('Some text.\n' * 10)
@@ -38,15 +43,15 @@ async def bg_task():
while 1:
curr_state_info = ', '.join('%s=%r' % (k, v) for k, v in state.items())
- key = await actions('选择要更改的会话环境设置项', list(state.keys()), help_text='当前状态:' + curr_state_info)
+ key = await actions(t('Select the setting item to be changed', '选择要更改的会话环境设置项'), list(state.keys()), help_text='Current state: ' + curr_state_info)
if key == 'title':
- state['title'] = await input('请输入标题', value=state['title'])
+ state['title'] = await input('Title', value=state['title'])
set_env(title=state['title'])
- toast('已将标题设置为%r' % state['title'])
+ toast('Title is set to %r' % state['title'])
elif key in state:
state[key] = not (state[key])
set_env(**{key: state[key]})
- toast('已将`%s`设置为%r' % (key, state[key]))
+ toast('`%s` is set to %r' % (key, state[key]))
if __name__ == '__main__':
diff --git a/demos/theme.py b/demos/theme.py
new file mode 100644
index 00000000..d4eeb779
--- /dev/null
+++ b/demos/theme.py
@@ -0,0 +1,395 @@
+from functools import partial
+
+from pywebio import start_server, config
+from pywebio.input import *
+from pywebio.output import *
+from pywebio.pin import *
+from pywebio.session import *
+
+
+def pin_widgets():
+ put_markdown("# Pin widget")
+ options = [
+ {
+ "label": "Option one",
+ "value": 1,
+ "selected": True,
+ },
+ {
+ "label": "Option two",
+ "value": 2,
+ },
+ {
+ "label": "Disabled option",
+ "value": 3,
+ "disabled": True
+ }
+ ]
+ put_input('input', label='Text input', placeholder="Enter email",
+ help_text="We'll never share your email with anyone else.")
+ put_input('valid_input', label="Valid input", value="correct value")
+ put_input('invalid_input', label="Invalid input", value="wrong value")
+ put_textarea('textarea', label='Textarea', rows=3, maxlength=10, minlength=20, value=None,
+ placeholder='This is placeholder message', readonly=False)
+ put_textarea('code', label='Code area', rows=4, code={'mode': 'python'},
+ value='import pywebio\npywebio.output.put_text("hello world")')
+ put_select('select', options=options, label='Select')
+ put_select('select_multiple', options=options, label='Multiple select', multiple=True, value=None)
+ put_checkbox('checkbox', options=options, label='Checkbox', inline=False, value=None)
+ put_checkbox('checkbox_inline', options=options, label='Inline checkbox', inline=True, value=None)
+ put_radio('radio', options=options, label='Radio', inline=False, value=None)
+ put_radio('radio_inline', options=options, label='Inline radio', inline=True, value='B')
+ put_slider('slider', label='Slider')
+ put_actions('actions', buttons=[
+ {'label': 'Submit', 'value': '1'},
+ {'label': 'Warning', 'value': '2', 'color': 'warning'},
+ {'label': 'Danger', 'value': '3', 'color': 'danger'},
+ ], label='Actions')
+
+ pin_update('valid_input', valid_status=True, valid_feedback="Success! You've done it.")
+ pin_update('invalid_input', valid_status=False, invalid_feedback="Sorry, that username's taken. Try another?")
+
+
+def form():
+ options = [
+ {
+ "label": "Option one",
+ "value": 1,
+ "selected": True,
+ },
+ {
+ "label": "Option two",
+ "value": 2,
+ },
+ {
+ "label": "Disabled option",
+ "value": 3,
+ "disabled": True
+ }
+ ]
+
+ input_group('Input group', [
+ input('Text', type=TEXT, datalist=['candidate-%s' % i for i in range(10)], name='text', required=True,
+ help_text='Required'),
+ input('Number', type=NUMBER, value="42", name='number'),
+ input('Float', type=FLOAT, name='float'),
+ input('Password', type=PASSWORD, name='password'),
+
+ textarea('Textarea', rows=3, maxlength=20, name='textarea',
+ placeholder="The maximum number of characters you can input is 20"),
+
+ textarea('Code', name='code', code={'mode': 'python'},
+ value='import pywebio\npywebio.output.put_text("hello world")'),
+
+ select('Multiple select', options, name='select-multiple', multiple=True),
+
+ select('Select', options, name='select'),
+
+ checkbox('Inline checkbox', options, inline=True, name='checkbox-inline'),
+
+ checkbox('Checkbox', options, name='checkbox'),
+
+ radio('Inline radio', options, inline=True, name='radio-inline'),
+
+ radio('Radio', options, inline=False, name='radio'),
+
+ file_upload('File upload', name='file_upload', max_size='10m'),
+
+ actions('Actions', [
+ {'label': 'Submit', 'value': 'submit'},
+ {'label': 'Disabled', 'disabled': True},
+ {'label': 'Reset', 'type': 'reset', 'color': 'warning'},
+ {'label': 'Cancel', 'type': 'cancel', 'color': 'danger'},
+ ], name='actions'),
+ ])
+
+
+def output_widgets():
+ ###########################################################################################
+ put_markdown("# Typography")
+ put_row([
+ put_markdown("""
+ ## Heading 2
+ ### Heading 3
+ #### Heading 4
+ ##### Heading 5
+
+ [PyWebIO](https://github.com/pywebio/PyWebIO) is awesome!
+
+ *This text will be italic*
+ **This text will be bold**
+ _You **can** combine them_
+ ~~Strikethrough~~
+ This is `inline code`
+
+ As Kanye West said:
+
+ > We're living the future so
+ > the present is our past.
+ """),
+
+ put_markdown("""
+ ### Lists
+ * Item 1
+ * Item 2
+ * Item 2a
+ * Item 2b
+
+ 1. Item 1
+ 1. Item 2
+ 1. Item 2a
+ 1. Item 2b
+
+ ### Task Lists
+ - [x] [links](), **formatting**, and tags supported
+ - [x] list syntax required (any unordered or ordered list supported)
+ - [x] this is a complete item
+ - [ ] this is an incomplete item
+ """)
+ ])
+
+ ###########################################################################################
+ put_markdown("""
+ # Code
+ ```python
+ from pywebio import *
+
+ def main(): # PyWebIO application function
+ name = input.input("what's your name")
+ output.put_text("hello", name)
+
+ start_server(main, port=8080, debug=True)
+ ```
+ """)
+ ###########################################################################################
+ put_markdown('# Image')
+ with use_scope('image'):
+ put_image(
+ "https://opengraph.githubassets.com/6bcea5272d0b5901f48a67d9d05da6c7a7c7c68da32a5327943070ff9c9a3dfb/pywebio/PyWebIO").style("""
+ max-height: 250px;
+ border: 2px solid #fff;
+ border-radius: 25px;
+ """)
+ ###########################################################################################
+ put_markdown("# Buttons")
+ # small=None, link_style=False, outline=False, group=False
+ put_buttons([
+ dict(label=i, value=i, color=i)
+ for i in ['primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark']
+ ], onclick=lambda b: toast(f'Clicked {b} button'))
+
+ put_buttons([
+ dict(label=i, value=i, color=i)
+ for i in ['primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark']
+ ], onclick=lambda b: toast(f'Clicked {b} button'), small=True)
+
+ put_buttons([
+ dict(label=i, value=i, color=i)
+ for i in ['primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark']
+ ], onclick=lambda b: toast(f'Clicked {b} button'), link_style=True)
+
+ put_buttons([
+ dict(label=i, value=i, color=i)
+ for i in ['primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark']
+ ], onclick=lambda b: toast(f'Clicked {b} button'), outline=True)
+ put_buttons([
+ dict(label=i, value=i, color=i)
+ for i in ['primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark']
+ ], onclick=lambda b: toast(f'Clicked {b} button'), group=True)
+ ###########################################################################################
+ put_markdown('# Tables')
+ put_markdown("""
+ First Header | Second Header
+ ------------ | -------------
+ Content from cell 1 | Content from cell 2
+ Content in the first column | Content in the second column
+ """)
+
+ put_table([
+ ['Type', 'Content'],
+ ['text', '
'],
+ ['html', put_html('X2')],
+ ['buttons', put_buttons(['A', 'B'], onclick=toast, small=True)],
+ ['markdown', put_markdown('`awesome PyWebIO!`\n - 1\n - 2\n - 3')],
+ ['file', put_file('hello.text', b'')],
+ ['table', put_table([
+ ['A', 'B'],
+ [put_markdown('`C`'), put_markdown('`D`')]
+ ])]
+ ])
+ ###########################################################################################
+ put_markdown('# Popup')
+
+ def show_popup():
+ popup('Popup title', [
+ 'Popup body text goes here.',
+ put_table([
+ ['Type', 'Content'],
+ ['html', put_html('X2')],
+ ['text', '
'],
+ ['buttons', put_buttons(['A', 'B'], onclick=toast)],
+ ['markdown', put_markdown('`Awesome PyWebIO!`')],
+ ['file', put_file('hello.text', b'')],
+ ['table', put_table([['A', 'B'], ['C', 'D']])]
+ ]),
+ put_button('Close', onclick=close_popup, outline=True)
+ ], size=PopupSize.NORMAL)
+
+ put_button("Click me to show a popup", onclick=show_popup)
+
+ ###########################################################################################
+ put_markdown('# Layout')
+ put_row([
+ put_column([
+ put_code('A'),
+ put_row([
+ put_code('B1'), None,
+ put_code('B2'), None,
+ put_code('B3'),
+ ]),
+ put_code('C'),
+ ]), None,
+ put_code('python'), None,
+ put_code('python\n' * 20).style('max-height:200px;'),
+ ])
+
+ ###########################################################################################
+ put_markdown('# Loading')
+ put_processbar('processbar', 0.3)
+ put_text()
+ put_grid([
+ [
+ put_loading(shape=shape, color=color)
+ for color in ('primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark')
+ ]
+ for shape in ('border', 'grow')
+ ], cell_width='50px', cell_height='50px')
+ ###########################################################################################
+ put_markdown('# Tabs')
+
+ put_tabs([
+ {'title': 'Text', 'content': 'Hello world'},
+ {'title': 'Markdown', 'content': put_markdown('~~Strikethrough~~')},
+ {'title': 'More content', 'content': [
+ put_table([
+ ['Commodity', 'Price'],
+ ['Apple', '5.5'],
+ ['Banana', '7'],
+ ]),
+ put_link('pywebio', 'https://github.com/wang0618/PyWebIO')
+ ]},
+ ])
+ ###########################################################################################
+ put_markdown('# Scrollable')
+
+ put_scrollable("Long text " * 200, height=200)
+ ###########################################################################################
+ put_markdown('# Collapse')
+ put_collapse('Click to expand', [
+ 'text',
+ put_markdown('~~Strikethrough~~'),
+ put_table([
+ ['Commodity', 'Price'],
+ ['Apple', '5.5'],
+ ])
+ ])
+ ###########################################################################################
+ put_markdown('# Message')
+
+ put_warning(
+ put_markdown('### Warning!'),
+ "Best check yo self, you're not looking too good. Nulla vitae elit libero, a pharetra augue. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.",
+ closable=True)
+ put_success("Well done! You successfully read this important alert message.")
+ put_info("Heads up! This alert needs your attention, but it's not super important.")
+ put_error("Oh snap! Change a few things up and try submitting again.")
+
+
+ALL_THEME = ('default', 'dark', 'sketchy', 'minty', 'yeti')
+
+THEME_SUMMARY = {'default': 'The default theme', 'dark': 'A theme for night',
+ 'sketchy': 'A hand-drawn look for mockups and mirth', 'minty': 'A fresh feel',
+ 'yeti': 'A friendly foundation'}
+
+style = """
+table img:hover {
+ transition-duration: 400ms;
+ transform: translateY(-2px);
+ box-shadow: 0px 2px 9px 2px rgb(0 0 0 / 27%), 0 30px 50px -30px rgb(0 0 0 / 30%)
+}
+.page-header h1 {
+ font-size: 3em;
+}
+#pywebio-scope-image img {
+ box-shadow: rgb(204 204 204) 3px 3px 13px;
+}
+.webio-theme-dark #pywebio-scope-image img {
+ box-shadow: none !important;
+}
+"""
+
+
+@config(css_style=style)
+def page():
+ """PyWebIO Theme Preview"""
+
+ theme = eval_js("new URLSearchParams(window.location.search).get('app')")
+ if theme not in ALL_THEME:
+ theme = 'default'
+
+ put_html(f"""
+
+ """)
+
+ put_markdown('# Switch Theme')
+ themes = [
+ put_image(f"https://fastly.jsdelivr.net/gh/wang0618/PyWebIO@dev/docs/assets/theme/{name}.png").onclick(
+ partial(go_app, name=name, new_window=False))
+ for name in ALL_THEME if name != theme
+ ]
+ if info.user_agent.is_mobile:
+ put_table([themes[:2], themes[2:]])
+ else:
+ put_table([themes])
+
+ if theme != 'default':
+ put_markdown(f"""
+ ### Usage
+ Use `pywebio.config()` to apply this theme:
+
+ ```python
+ @config(theme="{theme}")
+ def main():
+ put_text("hello world")
+
+ start_server(main, port=8080)
+ ```
+ """)
+
+ put_markdown("""
+ ### 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/).
+ """)
+
+ set_env(input_panel_min_height=100, input_panel_init_height=190)
+ output_widgets()
+ pin_widgets()
+ form()
+
+
+# bind each theme to the app
+main = {
+ theme: config(theme=theme, title=f"PyWebIO {theme} theme")(page)
+ for theme in ALL_THEME if theme != 'default'
+}
+main['index'] = page
+
+if __name__ == '__main__':
+ start_server(main, debug=True, port=8080)
diff --git a/demos/wordle.py b/demos/wordle.py
new file mode 100644
index 00000000..cc9b75f3
--- /dev/null
+++ b/demos/wordle.py
@@ -0,0 +1,110 @@
+import time
+from pywebio import start_server, config
+from pywebio.output import *
+from pywebio.session import run_js, local as session_local
+
+TODAY_WORD = 'PYWEBIO' # need to be uppercase
+
+MAX_TRY = 6
+WORD_LEN = len(TODAY_WORD)
+
+
+CSS = """
+.pywebio {padding-top: 0} .markdown-body table {display:table; width:250px; margin:10px auto;}
+.markdown-body table th, .markdown-body table td {font-weight:bold; padding:0; line-height:50px;}
+th>div,td>div {width:50px; height:50px}.btn-light {background-color:#d3d6da;}
+@media (max-width: 435px) {.btn{padding:0.375rem 0.5rem;}}
+@media (max-width: 355px) {.btn{padding:0.375rem 0.4rem;}}
+"""
+
+
+# To check if a user's input word is actually a legit word
+# We just implement a placeholder function in this example
+# If a guess word is UNHAPPY, toast a message
+def is_word(s):
+ return 'UNHAPPY' not in s
+
+
+def on_key_press(char):
+ if session_local.curr_row >= MAX_TRY or session_local.game_pass:
+ return
+
+ if char == '◀':
+ session_local.curr_word = session_local.curr_word[:-1]
+ return clear(f's-{session_local.curr_row}-{len(session_local.curr_word)}')
+
+ # show the char in grid
+ with use_scope(f's-{session_local.curr_row}-{len(session_local.curr_word)}', clear=True):
+ put_text(char)
+
+ session_local.curr_word += char
+ if len(session_local.curr_word) == WORD_LEN: # submit a word guess
+ if not is_word(session_local.curr_word):
+ toast('Not in word list!', color='error')
+ session_local.curr_word = ''
+ for i in range(WORD_LEN):
+ clear(f's-{session_local.curr_row}-{i}')
+ else:
+ for idx, c in enumerate(session_local.curr_word):
+ time.sleep(0.2)
+ if TODAY_WORD[idx] == c:
+ session_local.green_chars.add(c)
+ run_js('$("button:contains(%s)").css({"background-color":"#6aaa64", "color":"white"})' % c)
+ text_bg = '#6aaa64'
+ session_local.game_result += '🟩'
+ elif c in TODAY_WORD:
+ text_bg = '#c9b458'
+ session_local.game_result += '🟨'
+ if c not in session_local.green_chars:
+ run_js('$("button:contains(%s)").css({"background-color":"#c9b458", "color":"white"})' % c)
+ else:
+ text_bg = '#787c7e'
+ session_local.game_result += '⬜'
+ run_js('$("button:contains(%s)").css({"background-color":"#787c7e", "color":"white"})' % c)
+
+ with use_scope(f's-{session_local.curr_row}-{idx}', clear=True):
+ put_text(c).style(f'color:white;background:{text_bg}')
+
+ session_local.game_result += '\n'
+ if session_local.curr_word == TODAY_WORD:
+ toast('Genius', color='success')
+ session_local.game_pass = True
+
+ session_local.curr_row += 1
+ session_local.curr_word = ''
+
+ if session_local.game_pass:
+ message = f'Wordle {session_local.curr_row}/{MAX_TRY}\n' + session_local.game_result
+ with popup("Game Result", size='small'):
+ put_text(message).style('text-align: center')
+ put_button('Share', color='success', onclick=lambda: toast('Copied to clipboard') or run_js("""navigator.clipboard.write([new ClipboardItem({"text/plain":new Blob([text],{type:"text/plain"})})]);""", text=message)).style('text-align: center')
+
+
+@config(title="WORDLE with PyWebIO", description="A wordle-like game implemented with PyWebIO", css_style=CSS)
+def main():
+ put_markdown(
+ '# WORDLE \n A pure python implementation of a [Wordle-like game](https://en.wikipedia.org/wiki/Wordle), using PyWebIO library. '
+ '[Source code](https://github.com/pywebio/PyWebIO/blob/dev/demos/wordle.py)'
+ ).style('text-align:center')
+
+ grid = [
+ [put_scope(f's-{x}-{y}', content=put_text(' ')) for y in range(WORD_LEN)]
+ for x in range(MAX_TRY)
+ ]
+ put_table(grid).style('text-align: center')
+
+ keyboard = [
+ put_buttons([dict(label=c, value=c, color='light') for c in keys], on_key_press, serial_mode=True)
+ for keys in ['QWERTYUIOP', 'ASDFGHJKL', 'ZXCVBNM◀']
+ ]
+ put_column(keyboard).style('text-align: center')
+
+ session_local.curr_row = 0
+ session_local.curr_word = ''
+ session_local.green_chars = set()
+ session_local.game_pass = False
+ session_local.game_result = ''
+
+
+if __name__ == '__main__':
+ start_server(main, port=8080, cdn=False)
diff --git a/docs/FAQ.rst b/docs/FAQ.rst
index b9f8b8ee..7767ac1c 100644
--- a/docs/FAQ.rst
+++ b/docs/FAQ.rst
@@ -1,21 +1,27 @@
-常见问题
+FAQ
==========================
.. contents::
:local:
-如何让输入框在提交后不消失,并可以持续性地输入
+How to make the input form not disappear after submission, and can continue to receive input?
+----------------------------------------------------------------------------------------------
+
+You can consider the :doc:`pin <./pin>` module. It achieves persistent input by pinning input widgets to the page.
+
+
+How to output an input widget such as a search bar?
----------------------------------------------------------
-PyWebIO 的设计就是输入表单在成功提交后就销毁,因为 PyWebIO 的输入是阻塞式的,一旦提交表单,输入函数就返回了,此时表单还留在界面上是没有意义的。如果想实现持续性的输入,可以将接收输入以及后续操作放到一个 ``while`` 循环中。
+
+You can consider the :doc:`pin <./pin>` module.
-如何输出一个诸如搜索栏的输入框
+Why the callback of ``put_buttons()`` does not work?
----------------------------------------------------------
-很遗憾,PyWebIO并不支持将输入框作为一般性的内容输出到页面。因为这样就相当于又回到了基于回调获取输入的方式了,会导致应用开发的复杂性提高,PyWebIO不太推荐过多依赖回调机制,所以对此仅提供了非常少的支持。
-不过也可以使用另一种方式实现近似的效果:只需要在需要显示输入框的地方放置一个button( `put_buttons() ` ),然后在button的回调函数中调用输入函数来获取输入并进行后续操作。
+You might use the old version of PyWebIO, upgrade it to the latest version or see `the old document `_
-为什么 ``put_buttons()`` 的回调不起作用
+Why I cannot download the file using ``put_file()``?
----------------------------------------------------------
-一般情况下,在Server模式下,任务函数一旦返回(或在Script模式下,脚本运行结束),会话就结束了,此时事件回调也将不起作用,可以在任务函数(或脚本)末尾处使用 `pywebio.session.hold()` 函数来将会话保持,这样在用户关闭浏览器页面前,事件回调将一直可用。 参见 :ref:`Server模式与Script模式 `
+The reason is the same as above.
\ No newline at end of file
diff --git a/docs/_ext/codeblock.py b/docs/_ext/codeblock.py
index 23d56a59..6b966653 100644
--- a/docs/_ext/codeblock.py
+++ b/docs/_ext/codeblock.py
@@ -20,6 +20,9 @@ def md5(str_data):
return t.hexdigest()
def run(self):
+ if self.env.app.builder.name == 'gettext':
+ return super().run()
+
code_save_path = os.environ.get('CODE_EXPORT_PATH')
caption = self.options.get('summary', '')
@@ -30,8 +33,11 @@ def run(self):
if self.options.get('name', None) is None:
# 设置name属性,从而让生成的代码html块具有id属性
self.options.update({'name': 'demo-' + code_id})
+ else:
+ name = self.options.get('name', '')
+ self.options.update({'name': 'demo-' + name})
- name = self.options.get('name').replace('_','-')
+ name = self.options.get('name').replace('_', '-')
if name in type(self).names:
name += '-' + code_id
self.options.update({'name': name})
diff --git a/docs/advanced.rst b/docs/advanced.rst
new file mode 100644
index 00000000..d40681c5
--- /dev/null
+++ b/docs/advanced.rst
@@ -0,0 +1,412 @@
+Advanced topic
+===============
+
+This section will introduce the advanced features of PyWebIO.
+
+
+.. _multiple_app:
+
+Start multiple applications with start_server()
+-------------------------------------------------
+
+`start_server() ` accepts a function as PyWebIO application. In addition,
+`start_server() ` also accepts a list of application function or a dictionary
+of it to start multiple applications. You can use `pywebio.session.go_app() ` or
+`put_link() ` to jump between application::
+
+ def task_1():
+ put_text('task_1')
+ put_buttons(['Go task 2'], [lambda: go_app('task_2')])
+
+ def task_2():
+ put_text('task_2')
+ put_buttons(['Go task 1'], [lambda: go_app('task_1')])
+
+ def index():
+ put_link('Go task 1', app='task_1') # Use `app` parameter to specify the task name
+ put_link('Go task 2', app='task_2')
+
+ # equal to `start_server({'index': index, 'task_1': task_1, 'task_2': task_2})`
+ start_server([index, task_1, task_2])
+
+When the first parameter of `start_server() ` is a dictionary, whose key is
+application name and value is application function. When it is a list, PyWebIO will use function name as application name.
+
+You can select which application to access through the ``app`` URL parameter
+(for example, visit ``http://host:port/?app=foo`` to access the ``foo`` application),
+By default, the ``index`` application is opened when no ``app`` URL parameter provided.
+When the ``index`` application doesn't exist, PyWebIO will provide a default index application.
+
+
+.. _integration_web_framework:
+
+Integration with web framework
+---------------------------------
+
+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.
+
+The integration methods of those web frameworks are as follows:
+
+.. tabs::
+
+ .. tab:: Tornado
+
+ .. only:: latex
+
+ **Tornado**
+
+ Use `pywebio.platform.tornado.webio_handler()` to get the
+ `WebSocketHandler `_
+ class for running PyWebIO applications in Tornado::
+
+ import tornado.ioloop
+ import tornado.web
+ from pywebio.platform.tornado import webio_handler
+
+ class MainHandler(tornado.web.RequestHandler):
+ def get(self):
+ self.write("Hello, world")
+
+ if __name__ == "__main__":
+ application = tornado.web.Application([
+ (r"/", MainHandler),
+ (r"/tool", webio_handler(task_func)), # `task_func` is PyWebIO task function
+ ])
+ application.listen(port=80, address='localhost')
+ tornado.ioloop.IOLoop.current().start()
+
+
+ In above code, we add a routing rule to bind the ``WebSocketHandler`` of the PyWebIO application to the ``/tool`` path.
+ After starting the Tornado server, you can visit ``http://localhost/tool`` to open the PyWebIO application.
+
+ .. attention::
+
+ 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 ` is an example of Nginx WebSocket configuration.
+
+ .. tab:: Flask
+
+ .. only:: latex
+
+ **Flask**
+
+ Use `pywebio.platform.flask.webio_view()` to get the view function for running PyWebIO applications in Flask::
+
+ from pywebio.platform.flask import webio_view
+ from flask import Flask
+
+ app = Flask(__name__)
+
+ # `task_func` is PyWebIO task function
+ app.add_url_rule('/tool', 'webio_view', webio_view(task_func),
+ methods=['GET', 'POST', 'OPTIONS']) # need GET,POST and OPTIONS methods
+
+ app.run(host='localhost', port=80)
+
+
+ In above code, we add a routing rule to bind the view function of the PyWebIO application to the ``/tool`` path.
+ After starting the Flask application, visit ``http://localhost/tool`` to open the PyWebIO application.
+
+ .. tab:: Django
+
+ .. only:: latex
+
+ **Django**
+
+ Use `pywebio.platform.django.webio_view()` to get the view function for running PyWebIO applications in Django::
+
+ # urls.py
+
+ from django.urls import path
+ from pywebio.platform.django import webio_view
+
+ # `task_func` is PyWebIO task function
+ webio_view_func = webio_view(task_func)
+
+ urlpatterns = [
+ path(r"tool", webio_view_func),
+ ]
+
+
+ In above code, we add a routing rule to bind the view function of the PyWebIO application to the ``/tool`` path.
+ After starting the Django server, visit ``http://localhost/tool`` to open the PyWebIO application
+
+ .. tab:: aiohttp
+
+ .. only:: latex
+
+ **aiohttp**
+
+ Use `pywebio.platform.aiohttp.webio_handler()` to get the
+ `Request Handler `_ coroutine for
+ running PyWebIO applications in aiohttp::
+
+ from aiohttp import web
+ from pywebio.platform.aiohttp import webio_handler
+
+ app = web.Application()
+ # `task_func` is PyWebIO task function
+ app.add_routes([web.get('/tool', webio_handler(task_func))])
+
+ web.run_app(app, host='localhost', port=80)
+
+ After starting the aiohttp server, visit ``http://localhost/tool`` to open the PyWebIO application
+
+ .. attention::
+
+ 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 ` is an example of Nginx WebSocket configuration.
+
+
+ .. tab:: FastAPI/Starlette
+
+ .. only:: latex
+
+ **FastAPI/Starlette**
+
+ Use `pywebio.platform.fastapi.webio_routes()` to get the FastAPI/Starlette routes for running PyWebIO applications.
+ You can mount the routes to your FastAPI/Starlette app.
+
+ FastAPI::
+
+ from fastapi import FastAPI
+ from pywebio.platform.fastapi import webio_routes
+
+ app = FastAPI()
+
+ @app.get("/app")
+ def read_main():
+ return {"message": "Hello World from main app"}
+
+ # `task_func` is PyWebIO task function
+ app.mount("/tool", FastAPI(routes=webio_routes(task_func)))
+
+ Starlette::
+
+ from starlette.applications import Starlette
+ from starlette.responses import JSONResponse
+ from starlette.routing import Route, Mount
+ from pywebio.platform.fastapi import webio_routes
+
+ async def homepage(request):
+ return JSONResponse({'hello': 'world'})
+
+ app = Starlette(routes=[
+ Route('/', homepage),
+ Mount('/tool', routes=webio_routes(task_func)) # `task_func` is PyWebIO task function
+ ])
+
+ After starting the server by using ``uvicorn :app`` , visit ``http://localhost:8000/tool/`` to open the PyWebIO application
+
+ See also: `FastAPI doc `_ , `Starlette doc `_
+
+ .. attention::
+
+ 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 ` is an example of Nginx WebSocket configuration.
+
+
+.. _integration_web_framework_note:
+
+Notes
+^^^^^^^^^^^
+**Deployment in production**
+
+In your production system, you may want to deploy the web applications with some WSGI/ASGI servers such as uWSGI, Gunicorn, and Uvicorn.
+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, the request may be dispatched to a process that does not hold the session
+to which the request belongs. So you can only start one worker to handle requests when using Flask or Django backend.
+
+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.
+Those backends use the WebSocket protocol to communicate with the browser in PyWebIO, so there is no the issue as described above.
+
+**Static resources Hosting**
+
+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``.
+
+When setting ``cdn=False`` , you need to host the static resources in the same directory as the PyWebIO application.
+In addition, you can also pass a string to ``cdn`` parameter to directly set the URL of PyWebIO static resources directory.
+
+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.
+
+.. 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.
+
+
+.. _coroutine_based_session:
+
+Coroutine-based session
+-------------------------------
+In most cases, you don’t need the coroutine-based session. All functions or methods in PyWebIO that are only used for
+coroutine sessions are specifically noted in the document.
+
+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.
+
+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.
+
+Using coroutine session
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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:
+
+.. code-block:: python
+ :emphasize-lines: 5,6
+
+ from pywebio.input import *
+ from pywebio.output import *
+ from pywebio import start_server
+
+ async def say_hello():
+ name = await input("what's your name?")
+ put_text('Hello, %s' % name)
+
+ start_server(say_hello, auto_open_webbrowser=True)
+
+
+In the coroutine task function, you can also use ``await`` to call other coroutines or
+(`awaitable objects `_) in the standard
+library `asyncio `_:
+
+.. code-block:: python
+ :emphasize-lines: 6,10
+
+ import asyncio
+ from pywebio import start_server
+
+ async def hello_word():
+ put_text('Hello ...')
+ await asyncio.sleep(1) # await awaitable objects in asyncio
+ put_text('... World!')
+
+ async def main():
+ await hello_word() # await coroutine
+ put_text('Bye, bye')
+
+ start_server(main, auto_open_webbrowser=True)
+
+.. attention::
+
+ In coroutine-based session, all input functions defined in the :doc:`pywebio.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.
+
+ Other functions that need to use ``await`` syntax in the coroutine session are:
+
+ * `pywebio.session.run_asyncio_coroutine(coro_obj) `
+ * `pywebio.session.eval_js(expression) `
+
+.. warning::
+
+ 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.
+
+ 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** ::
+
+ await asyncio.shield(pywebio.input())
+ await asyncio.gather(asyncio.sleep(1), pywebio.session.eval_js('1+1'))
+ task = asyncio.create_task(pywebio.input())
+
+.. _coroutine_based_concurrency:
+
+Concurrency in coroutine-based sessions
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+In coroutine-based session, you can start new thread, but you cannot call PyWebIO interactive functions in it
+(`register_thread() ` is not available in coroutine session). But you can use
+`run_async(coro) ` to execute a coroutine object asynchronously, and PyWebIO interactive
+functions can be used in the new coroutine:
+
+.. code-block:: python
+ :emphasize-lines: 10
+
+ from pywebio import start_server
+ from pywebio.session import run_async
+
+ async def counter(n):
+ for i in range(n):
+ put_text(i)
+ await asyncio.sleep(1)
+
+ async def main():
+ run_async(counter(10))
+ put_text('Main coroutine function exited.')
+
+
+ start_server(main, auto_open_webbrowser=True)
+
+
+`run_async(coro) ` returns a `TaskHandler `,
+which can be used to query the running status of the coroutine or close the coroutine.
+
+Close of session
+^^^^^^^^^^^^^^^^^^^
+
+Similar to thread-based session, when user close the browser page, the session will be closed.
+
+After the browser page closed, PyWebIO input function calls that have not yet returned in the current session will
+cause `SessionClosedException `, and subsequent calls to PyWebIO interactive
+functions will cause `SessionNotFoundException ` or
+`SessionClosedException `.
+
+`defer_call(func) ` also available in coroutine session.
+
+.. _coroutine_web_integration:
+
+Integration with Web Framework
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The PyWebIO application that using coroutine-based session can also be integrated to the web framework.
+
+However, there are some limitations when using coroutine-based sessions to integrate into Flask or Django:
+
+First, when ``await`` the coroutine objects/awaitable objects in the ``asyncio`` module, you need to use
+`run_asyncio_coroutine() ` to wrap the coroutine object.
+
+Secondly, you need to start a new thread to run the event loop before starting a Flask/Django server.
+
+Example of coroutine-based session integration into Flask:
+
+.. code-block:: python
+ :emphasize-lines: 12,20
+
+ import asyncio
+ import threading
+ from flask import Flask, send_from_directory
+ from pywebio import STATIC_PATH
+ from pywebio.output import *
+ from pywebio.platform.flask import webio_view
+ from pywebio.platform import run_event_loop
+ from pywebio.session import run_asyncio_coroutine
+
+ async def hello_word():
+ put_text('Hello ...')
+ await run_asyncio_coroutine(asyncio.sleep(1)) # can't just "await asyncio.sleep(1)"
+ put_text('... World!')
+
+ app = Flask(__name__)
+ app.add_url_rule('/hello', 'webio_view', webio_view(hello_word),
+ methods=['GET', 'POST', 'OPTIONS'])
+
+ # thread to run event loop
+ threading.Thread(target=run_event_loop, daemon=True).start()
+ app.run(host='localhost', port=80)
+
+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.
diff --git a/docs/arch.rst b/docs/arch.rst
index e4697766..7b6b745e 100644
--- a/docs/arch.rst
+++ b/docs/arch.rst
@@ -4,5 +4,162 @@ Architecture
概念
------------
-`Session` 表示浏览器与程序交互产生的一次会话。PyWebIO在会话中运行 ``Task`` ,任务是
+``Session`` 表示浏览器访问PyWebIO应用产生的一次会话。其生命周期从浏览器打开PyWebIO应用开始,到用户关闭浏览器页面或PyWebIO应用逻辑运行结束为止。
+
+会话建立后,PyWebIO创建一个线程或协程来执行应用逻辑。这里的线程或协程在PyWebIO中被称为 ``Task`` (执行单元)。
+除了起始的执行单元(由PyWebIO框架启动),应用在会话中也可以自行启动新的执行单元,在新的执行单元中也可以进行输入输出。
+
+在浏览器端,相同会话中的不同的执行单元的输入是独立的,共享输出空间,但输出域的栈结构各自独立。
+
+若用户正在填写一个执行单元的表单,会话中的其他执行单元也开始向用户请求输入,此时用户正在填写的表单将会被新的表单覆盖,
+当用户填写完新表单并提交后,旧表单重新显示,之前在旧表单上的输入也会保留。
+
+在基于线程的会话中,会话中的每个执行单元都是一个线程;在基于协程的会话中,会话中的每个执行单元都是一个协程。
+
+架构
+------------
+
+会话内的每个执行单元使用唯一的task_id进行标识,由于会话内的输入需要区分执行单元,所以每个表单提交时,
+除了表单的内容以外,还会携带表单所在的执行单元的task_id,这样,后台会话才可以知道该将表单数据传递给哪个执行单元。
+
+
+.. image:: /assets/architecture.png
+
+PyWebIO会话是由事件驱动的,这些事件来自用户在页面上的操作,比如提交表单,点击按钮,这些事件会通过http请求或websocket连接发送到后端框架。
+
+后端框架维护有当前在线的Session实例,后端框架在收到用户提交的事件后,回调用相关Session实例的 ``send_client_event()`` 方法将事件发送至会话;
+
+一个会话内会拥有至少一个执行单元,执行单元在调用PyWebIO的输入函数后会临时挂起,当会话收到用户的输入提交后,会话便将执行单元恢复执行,并提供用户输入的值。
+执行单元内,任何输入输出的调用都会转换成一些命令序列发送给会话.
+
+当后端框架通过HTTP与用户浏览器通信时,用户浏览器是以轮训的方式获取指令,会话会保存由执行单元生成的、还未发送到浏览器的命令序列,等待下次轮训时由后端框架取走。
+
+当后端框架通过WebSocket与用户建立连接时,任何由执行单元发送到会话的命令都会立即发送到后端,并由后端通过WebSocket连接通知用户浏览器。
+
+实现
+------------
+
+后端与Session的交互
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+后端框架负责从Session会话中获取来自PyWebIO的指令,并发送给用户浏览器;同时后端框架接收用户提交的数据,并发送给相应的会话实例。
+
+Session暴露给后端框架的方法仅有 `Session.send_client_event ` 、 `Session.get_task_commands ` 和 `Session.close ` 。
+
+
+基于HTTP通信的后端的实现逻辑
+"""""""""""""""""""""""""""""""""
+
+**基于HTTP的前后端通信约定**
+
+前端按照固定间隔使用GET请求轮训后端接口,在请求中使用 ``webio-session-id`` HTTP头来传递会话ID。
+
+会话一开始时,会话ID由后端生成并通过响应中的 ``webio-session-id`` HTTP头返回给前端,后续前端的请求都会在请求头中使用 ``webio-session-id`` 字段传递会话ID。
+
+前端产生的事件使用POST请求发送给后端。对于前端的每次轮训和事件提交请求,后端都会返回当前未执行的指令序列作为响应,前端收到响应后会依次执行指令。
+
+**代码实现**
+
+以Flask后端为例,Flask后端与Session的交互都在Flask视图函数中实现,视图函数通过调用 `pywebio.platform.flask.webio_view <./_modules/pywebio/platform/flask.html#webio_view>`_ 获取,
+在 `webio_view` 中,先是实例化了一个 `pywebio.platform.httpbased.HttpHandler` ,然后声明了一个内部函数,这个内部函数就是Flask视图函数,
+在视图函数内,先是实例化了一个 `pywebio.platform.flask.FlaskHttpContext` 对象,然后通过调用 ``HttpHandler.handle_request(FlaskHttpContext)`` 就获得了视图的响应。
+
+这其中,FlaskHttpContext 的基类为 HttpContext ,HttpContext 接口各异的后端框架定义了一个统一的操作接口,用于从当前请求中获取请求相关的数据并设置请求的相应。 FlaskHttpContext 为HttpContext接口的Flask实现。
+
+而 HttpHandle 负责维护Session实例并实现HTTP请求与Session之间的交互,HttpHandle 与后端框架相关的交互全都通过 HttpContext 操作。
+
+HttpContext的生命周期为一次HTTP请求,HttpHandle的生命周期和整个后端框架的生命周期一致。
+
+HttpHandler.handle_request 负责处理前端发送给后端的每一次请求,HttpHandler.handle_request 的处理流程如下:
+
+1. 检测当前HTTP请求是否满足跨域设置
+2. 根绝当前请求的 webio-session-id 头信息找到相应的Session实例,若不存在 webio-session-id 头则创建新会话并分配webio-session-id
+3. 若当前请求为POST事件提交请求,则将提交的数据通过 Session.send_client_event 发送给Session
+4. 通过调用 Session.get_task_commands 获取待执行的指令序列,并通过 HttpContext 向后端设置响应数据
+
+此外,基于HTTP的会话,用户主动关闭会话时(比如关闭浏览器),后端无法立即感知,所以在HttpHandler.handle_request 中,
+还会周期性地检测会话的最后活跃时间,将一段时间内不活跃的会话视为过期,所以在HttpHandler清理过期会话并调用 Session.close 释放会话内的资源。
+
+
+基于WebSocket通信的后端的实现逻辑
+""""""""""""""""""""""""""""""""""""
+**基于WebSocket的前后端通信约定:**
+
+浏览器与后端使用一个WebSocket连接来保持一个会话,后端的指令通过JSON序列化之后的消息实时发送给前端,前端用户触发的事件数据也通过JSON序列化之后发送给后端。
+
+**代码实现**
+
+以Tornado后端为例
+
+webio_handler用于获取Tornado与前端进行通信的WebSocketHandler子类,其逻辑实现在 _webio_handler 中,由于WebSocket的有状态性,
+WebSocketHandler子类的实现比基于HTTP通信的HttpHandler要简单许多,关键部分如下:
+
+* 在WebSocket连接创建的时候初始化Session实例,并向Session对象注册了 on_task_command和on_session_close 回调,分别在新指令产生时和会话由执行单元关闭时由Session调用,
+ 用于实现WebSocketHandler向前端实时发送指令
+* 在收到前端浏览器发送来的消息后,WebSocketHandler将收到的数据通过 Session.send_client_event 发送给Session
+* 在WebSocket连接关闭时,调用 Session.close 释放会话内的资源。
+
+session与执行单元(输入/输出)的交互
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+会话提供给执行单元的关键接口有:
+
+* get_current_session : 静态方法,获取当前执行单元所属的会话实例
+* get_current_task_id : 静态方法,获取当前执行单元所属的id
+* send_task_command : 向会话发送指令
+* next_client_event : 读取来自浏览器的属于当前执行单元的下一个事件
+* register_callback : 向会话注册一个回调
+
+同时,会话根据实现方式不同,还分别提供了 register_thread 和 run_async 用于启动新的执行单元。
+
+
+**回调机制**
+
+在会话中,为了能够响应用户在界面上的某些事件(比如点击了输出内容中的某个按钮),于是设计了回调机制,可以在执行单元中使用register_callback向当前会话注册回调,然后执行单元会得到一个回调ID,
+执行单元再通过相关指令让浏览器输出一些可以触发的控件,并向控件绑定回调ID,当用户触发控件后,前端将带有回调ID的 :ref:`回调事件 ` 发回会话,会话会在专门的执行单元中或启动新执行单元中运行回调。
+
+基于线程的会话实现
+""""""""""""""""""""""""""""""""""""
+
+在基于线程的会话中,每个执行单元都是一个线程,每个执行单元通过一条消息队列从会话接收来自用户的事件消息,当执行单元所需要的事件用户还没有提交时,执行单元便会挂起。
+
+基于线程的会话使用线程ID作为执行单元的ID,在全局使用一个以线程id为key的字典来映射执行单元所属的会话实例,会话内不同执行单元的用户事件消息队列也通过执行单元ID进行索引。
+
+使用 register_thread 启动新的执行单元时,也需要为新执行单元注册用户事件消息队列。
+
+基于协程的会话实现
+""""""""""""""""""""""""""""""""""""
+在基于协程的会话中,每个执行单元都是一个由协程包装成的任务对象(Task),当会话接收来自用户的事件消息后,便激活相应的任务对象,使得协程恢复运行。
+
+由于基于协程的会话是单线程的,所以会话在激活任务对象前是通过将上下文信息保存在全局变量中来实现 get_current_session 和 get_current_task_id 方法,全局的上下文信息包含当前将要执行的会话的实例和执行单元的ID。
+
+
+Script mode的实现
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Script mode 也是基于线程的,但由于全局仅存在一个会话,所有执行单元必定全部属于这个会话,所以也无需主动调用register_thread(thread)注册线程。
+
+当PyWebIO检测到用户代码在后端Server还未启动的情况下就调用了PyWebIO交互函数时,便会启动Script mode:
+
+1. 在新线程中启动后端Server
+2. 启动浏览器打开后端Server运行的地址
+3. 在第一次与用户建立连接时初始化会话
+
+script mode的会话类继承了基于线程的会话类,并修改了部分方法:
+
+* 构造函数 : 仅允许script mode会话类被初始化一次
+* get_current_session : 直接返回全局的会话对象
+* get_current_task_id : 除了返回当前线程id,还会自动将当前线程使用 register_thread 注册到会话中
+
+相关对象的文档
+------------------------
+
+.. autoclass:: pywebio.platform.httpbased.HttpHandler
+ :members:
+
+.. autoclass:: pywebio.platform.flask.FlaskHttpContext
+ :members:
+
+.. autoclass:: pywebio.session.base.Session
+ :members:
+
diff --git a/docs/assets/architecture.png b/docs/assets/architecture.png
new file mode 100644
index 00000000..b9faee43
Binary files /dev/null and b/docs/assets/architecture.png differ
diff --git a/docs/assets/demo.gif b/docs/assets/demo.gif
index 17815cf1..2d8b0f7c 100644
Binary files a/docs/assets/demo.gif and b/docs/assets/demo.gif differ
diff --git a/docs/assets/demo.png b/docs/assets/demo.png
index 1cd1414f..b4885c03 100644
Binary files a/docs/assets/demo.png and b/docs/assets/demo.png differ
diff --git a/docs/assets/demo_zh.gif b/docs/assets/demo_zh.gif
new file mode 100644
index 00000000..17815cf1
Binary files /dev/null and b/docs/assets/demo_zh.gif differ
diff --git a/docs/assets/input_1.png b/docs/assets/input_1.png
index 058f656d..f800009d 100644
Binary files a/docs/assets/input_1.png and b/docs/assets/input_1.png differ
diff --git a/docs/assets/input_2.png b/docs/assets/input_2.png
index 7b2db0c6..42d58655 100644
Binary files a/docs/assets/input_2.png and b/docs/assets/input_2.png differ
diff --git a/docs/assets/theme/dark.png b/docs/assets/theme/dark.png
new file mode 100644
index 00000000..2c55cf6b
Binary files /dev/null and b/docs/assets/theme/dark.png differ
diff --git a/docs/assets/theme/default.png b/docs/assets/theme/default.png
new file mode 100644
index 00000000..591df653
Binary files /dev/null and b/docs/assets/theme/default.png differ
diff --git a/docs/assets/theme/minty.png b/docs/assets/theme/minty.png
new file mode 100644
index 00000000..b27fa5c2
Binary files /dev/null and b/docs/assets/theme/minty.png differ
diff --git a/docs/assets/theme/sketchy.png b/docs/assets/theme/sketchy.png
new file mode 100644
index 00000000..fb0a214f
Binary files /dev/null and b/docs/assets/theme/sketchy.png differ
diff --git a/docs/assets/theme/yeti.png b/docs/assets/theme/yeti.png
new file mode 100644
index 00000000..dbfb9b6c
Binary files /dev/null and b/docs/assets/theme/yeti.png differ
diff --git a/docs/battery.rst b/docs/battery.rst
new file mode 100644
index 00000000..37c3e6a9
--- /dev/null
+++ b/docs/battery.rst
@@ -0,0 +1,2 @@
+.. automodule:: pywebio_battery
+ :members:
\ No newline at end of file
diff --git a/docs/conf.py b/docs/conf.py
index 12eb762c..6c8d8a5e 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -19,8 +19,8 @@
# -- Project information -----------------------------------------------------
project = 'PyWebIO'
-copyright = 'WangWeimin'
-author = 'WangWeimin'
+copyright = 'Weimin Wang'
+author = 'Weimin Wang'
# -- General configuration ---------------------------------------------------
@@ -33,9 +33,13 @@
"sphinx.ext.viewcode",
'sphinx_tabs.tabs',
'sphinx.ext.extlinks',
- 'codeblock'
+ 'codeblock', # defined in docs/_ext/codeblock.py
+ 'sphinx_toolbox.collapse',
]
+# https://github.com/sphinx-doc/sphinx/issues/6316
+toc_object_entries = False
+
primary_domain = "py"
default_role = "py:obj"
# intersphinx_mapping = {"python": ("https://docs.python.org/3.6/", None)}
@@ -49,7 +53,8 @@
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
-language = 'zh_CN'
+language = 'en'
+gettext_additional_targets = ["literal-block"]
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
@@ -71,7 +76,9 @@
def setup(app):
"""Configure Sphinx"""
- app.add_stylesheet('pywebio.css')
+ app.add_css_file('pywebio.css')
+ # from docutils.parsers.rst.directives.admonitions import Note
+ # app.add_directive('collapse', Note)
# -- Extension configuration -------------------------------------------------
diff --git a/docs/cookbook.rst b/docs/cookbook.rst
new file mode 100644
index 00000000..d22b6d2f
--- /dev/null
+++ b/docs/cookbook.rst
@@ -0,0 +1,135 @@
+Cookbook
+==========================
+
+.. seealso:: :doc:`PyWebIO Battery `
+
+.. contents::
+ :local:
+
+Interaction related
+----------------------------------------------------------------------------------------------
+
+Equivalent to "Press any key to continue"
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. exportable-codeblock::
+ :name: cookbook-press-anykey-continue
+ :summary: Press any key to continue
+
+ actions(buttons=["Continue"])
+ put_text("Go next") # ..demo-only
+
+
+Output pandas dataframe
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. exportable-codeblock::
+ :name: cookbook-pandas-df
+ :summary: Output pandas dataframe
+
+ import numpy as np
+ import pandas as pd
+
+ df = pd.DataFrame(np.random.randn(6, 4), columns=list("ABCD"))
+ put_html(df.to_html(border=0))
+
+.. seealso:: `pandas.DataFrame.to_html — pandas documentation `_
+
+Output Matplotlib figure
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Instead of using ``matplotlib.pyplot.show()``, to show matplotlib figure in PyWebIO, you need to save the figure to in-memory buffer fist and then output the buffer
+via :func:`pywebio.output.put_image`:
+
+.. exportable-codeblock::
+ :name: cookbook-matplotlib
+ :summary: Output Matplotlib plot
+
+ import matplotlib
+ import matplotlib.pyplot as plt
+ import io
+ import pywebio
+
+ matplotlib.use('agg') # required, use a non-interactive backend
+
+ fig, ax = plt.subplots() # Create a figure containing a single axes.
+ ax.plot([1, 2, 3, 4], [1, 4, 2, 3]) # Plot some data on the axes.
+
+ buf = io.BytesIO()
+ fig.savefig(buf)
+ pywebio.output.put_image(buf.getvalue())
+
+The ``matplotlib.use('agg')`` is required so that the server does not try to create (and then destroy) GUI windows
+that will never be seen.
+
+When using Matplotlib in a web server (multiple threads environment), pyplot may cause some conflicts in some cases,
+read the following articles for more information:
+
+ * `Multi Threading in Python and Pyplot | by Ranjitha Korrapati | Medium `_
+
+ * `Embedding in a web application server (Flask) — Matplotlib documentation `_
+
+
+Add new syntax highlight for code output
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+When output code via `put_markdown()` or `put_code()`, PyWebIO provides syntax highlight for some common languages.
+If you find your code have no syntax highlight, you can add the syntax highlighter by two following steps:
+
+1. Go to `prismjs CDN page `_ to get your syntax highlighter link.
+2. Use :func:`config(js_file=...) ` to load the syntax highlight module
+
+::
+
+ @config(js_file="https://cdn.jsdelivr.net/npm/prismjs@1.23.0/components/prism-diff.min.js")
+ def main():
+ put_code("""
+ + AAA
+ - BBB
+ CCC
+ """.strip(), language='diff')
+
+ put_markdown("""
+ ```diff
+ + AAA
+ - BBB
+ CCC
+ ```
+ """, lstrip=True)
+
+
+
+Web application related
+----------------------------------------------------------------------------------------------
+
+Add Google AdSense/Analytics code
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+When you setup Google AdSense/Analytics, you will get a javascript file and a piece of code that needs to be inserted
+into your application page, you can use :func:`pywebio.config()` to inject js file and code to your PyWebIO application::
+
+ from pywebio import start_server, output, config
+
+ js_file = "https://www.googletagmanager.com/gtag/js?id=G-xxxxxxx"
+ js_code = """
+ window.dataLayer = window.dataLayer || [];
+ function gtag(){dataLayer.push(arguments);}
+ gtag('js', new Date());
+
+ gtag('config', 'G-xxxxxxx');
+ """
+
+ @config(js_file=js_file, js_code=js_code)
+ def main():
+ output.put_text("hello world")
+
+ start_server(main, port=8080)
+
+
+Refresh page on connection lost
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Add the following code to the beginning of your PyWebIO application main function::
+
+ session.run_js('WebIO._state.CurrentSession.on_session_close(()=>{setTimeout(()=>location.reload(), 4000})')
+
diff --git a/docs/demos.rst b/docs/demos.rst
deleted file mode 100644
index dbff9423..00000000
--- a/docs/demos.rst
+++ /dev/null
@@ -1,12 +0,0 @@
-示例Demos
-==========
-
-基本demo
-------------
-使用PyWebIO编写的示例应用
-
-.. automodule:: demos
-
-数据可视化demo
------------------
-PyWebIO支持使用第三方库进行数据可视化,详情见 :ref:`使用PyWebIO进行数据可视化 `
\ No newline at end of file
diff --git a/docs/guide.rst b/docs/guide.rst
index 38711a5a..5b955ed4 100644
--- a/docs/guide.rst
+++ b/docs/guide.rst
@@ -1,98 +1,108 @@
User's guide
============
-如果你接触过Web开发,你可能对接下来描述的PyWebIO的用法感到不太习惯,不同于传统Web开发的后端实现接口、前端进行展示交互的模式,在PyWebIO中,所有的逻辑都通过编写Python代码实现。
-
-你可以按照编写控制台程序的逻辑编写PyWebIO应用,只不过这里的终端变成了浏览器。通过PyWebIO提供的命令式API,
-你可以简单地调用 ``put_text`` 、 ``put_image`` 、 ``put_table`` 等函数输出文本、图片、表格等内容到浏览器,也可以调用 ``input`` 、 ``select`` 、
-``file_upload`` 等函数在浏览器上显示不同表单来接收用户的输入。此外PyWebIO中还提供了点击事件、布局等支持,让你可以使用最少的代码完成与用户的交互,
-并尽可能提供良好的用户体验。
-
-本篇使用指南从几个方面对PyWebIO的使用进行介绍,覆盖了PyWebIO的绝大部分特性。本文档中大部分示例代码的右上方都有一个Demo链接,点击后可以在线预览代码的运行效果。
-
-输入
+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 pattern that backend implement api and frontend display content.
+In PyWebIO, you only need to write code in Python.
+
+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, 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.
+
+This user guide introduces you the most of the features of PyWebIO. There is a demo link at the top right of the example
+codes in this document, where you can run the example code online and see what happens. Also, the
+`PyWebIO Playground `_ is a good place to write, run and share your PyWebIO code online.
+
+Input
------------
-输入函数都定义在 :doc:`pywebio.input ` 模块中,可以使用 ``from pywebio.input import *`` 引入。
+The input functions are defined in the :doc:`pywebio.input ` module and can be imported using ``from pywebio.input import *``.
-调用输入函数会在浏览器上弹出一个输入表单来获取输入。PyWebIO的输入函数是阻塞式的(和Python内置的 `input` 一样),在表单被成功提交之前,输入函数不会返回。
+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.
-基本输入
-^^^^^^^^^^^
+Basic input
+^^^^^^^^^^^^^
-首先是一些基本类型的输入
+Here are some basic types of input.
-文本输入:
+Text input:
.. exportable-codeblock::
:name: text-input
- :summary: 文本输入
+ :summary: Text input
age = input("How old are you?", type=NUMBER)
put_text('age = %r' % age) # ..demo-only
-这样一行代码的效果为:浏览器会弹出一个文本输入框来获取输入,在用户完成输入将表单提交后,函数返回用户输入的值。
+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.
-下面是一些其他类型的输入函数:
+Here are some other types of input functions:
.. exportable-codeblock::
:name: basic-input
- :summary: 基本输入
+ :summary: Basic input
- # 密码输入
+ # Password input
password = input("Input password", type=PASSWORD)
put_text('password = %r' % password) # ..demo-only
## ----
- # 下拉选择框
+ # Drop-down selection
gift = select('Which gift you want?', ['keyboard', 'ipad'])
put_text('gift = %r' % gift) # ..demo-only
## ----
- # 勾选选项
- agree = checkbox("用户协议", options=['I agree to terms and conditions'])
+ # Checkbox
+ agree = checkbox("User Term", options=['I agree to terms and conditions'])
put_text('agree = %r' % agree) # ..demo-only
## ----
- # 单选选项
+ # Single choice
answer = radio("Choose one", options=['A', 'B', 'C', 'D'])
put_text('answer = %r' % answer) # ..demo-only
## ----
- # 多行文本输入
+ # Multi-line text input
text = textarea('Text Area', rows=3, placeholder='Some text')
put_text('text = %r' % text) # ..demo-only
## ----
- # 文件上传
+ # File Upload
img = file_upload("Select a image:", accept="image/*")
if img: # ..demo-only
put_image(img['content'], title=img['filename']) # ..demo-only
-输入选项
-^^^^^^^^^^^
+Parameter of input functions
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-输入函数可指定的参数非常丰富(全部参数及含义请见 :doc:`函数文档 ` ):
+There are many parameters that can be passed to the input function(for complete parameters, please refer to the
+:doc:`function document `):
.. exportable-codeblock::
:name: input-args
- :summary: 输入参数
+ :summary: Parameter of input functions
input('This is label', type=TEXT, placeholder='This is placeholder',
help_text='This is help text', required=True)
-以上代码将在浏览器上显示如下:
+The results of the above example are as follows:
.. image:: /assets/input_1.png
-我们可以为输入指定校验函数,校验函数应在校验通过时返回None,否则返回错误消息:
+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:
.. exportable-codeblock::
:name: input-valid-func
- :summary: 输入指定校验函数
+ :summary: Input validate function for
- def check_age(p): # 检验函数校验通过时返回None,否则返回错误消息
+ def check_age(p): # return None when the check passes, otherwise return the error message
if p < 10:
return 'Too young!!'
if p > 60:
@@ -101,39 +111,40 @@ User's guide
age = input("How old are you?", type=NUMBER, validate=check_age)
put_text('age = %r' % age) # ..demo-only
-当用户输入了不合法的值时,页面上的显示如下:
+When the user input an illegal value, the input field is displayed as follows:
.. image:: /assets/input_2.png
-
-:func:`pywebio.input.textarea` 还支持使用 `Codemirror `_ 实现代码风格的编辑区,只需使用 ``code`` 参数传入Codemirror支持的选项即可(最简单的情况是直接传入 ``code={}`` 或 ``code=True``):
+You can use ``code`` parameter in :func:`pywebio.input.textarea()` to make a code editing textarea.
.. exportable-codeblock::
:name: codemirror
- :summary: textarea代码编辑
+ :summary: Code editing by using textarea
code = textarea('Code Edit', code={
- 'mode': "python", # 编辑区代码语言
- 'theme': 'darcula', # 编辑区darcula主题, Visit https://codemirror.net/demo/theme.html#cobalt to get more themes
+ 'mode': "python",
+ 'theme': 'darcula',
}, value='import something\n# Write your python code')
put_code(code, language='python') # ..demo-only
-文本框的显示效果为:
+The results of the above example are as follows:
.. image:: /assets/codemirror_textarea.png
-:ref:`这里 ` 列举了一些常用的Codemirror选项,完整的Codemirror选项请见:https://codemirror.net/doc/manual.html#config
-输入组
-^^^^^^^
+Input Group
+^^^^^^^^^^^^^
+
+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 as its key
+and the input data as its value:
-PyWebIO支持输入组, 返回结果为一个字典。`pywebio.input.input_group()` 接受单项输入组成的列表作为参数, 返回以单项输入函数中的 ``name`` 作为键、以输入数据为值的字典:
.. exportable-codeblock::
:name: input-group
- :summary: 输入组
+ :summary: Input Group
- def check_age(p): # 检验函数校验通过时返回None,否则返回错误消息 # ..demo-only
+ def check_age(p): # ..demo-only
if p < 10: # ..demo-only
return 'Too young!!' # ..demo-only
if p > 60: # ..demo-only
@@ -145,23 +156,23 @@ PyWebIO支持输入组, 返回结果为一个字典。`pywebio.input.input_group
])
put_text(data['name'], data['age'])
-输入组中同样支持使用 ``validate`` 参数设置校验函数,其接受整个表单数据作为参数:
+The input group also supports using ``validate`` parameter to set the validation function, which accepts the entire form data as parameter:
.. exportable-codeblock::
- :name: input-group
- :summary: 输入组
+ :name: input-group-validate
+ :summary: Input Group validation
- def check_age(p): # 检验函数校验通过时返回None,否则返回错误消息 # ..demo-only
+ def check_age(p): # single input item validation # ..demo-only
if p < 10: # ..demo-only
return 'Too young!!' # ..demo-only
if p > 60: # ..demo-only
return 'Too old!!' # ..demo-only
# ..demo-only
- def check_form(data): # 检验函数校验通过时返回None,否则返回 (input name,错误消息)
+ def check_form(data): # return (input name, error msg) when validation fail
if len(data['name']) > 6:
- return ('name', '名字太长!')
+ return ('name', 'Name too long!')
if data['age'] <= 0:
- return ('age', '年龄不能为负数!')
+ return ('age', 'Age can not be negative!')
data = input_group("Basic info",[ # ..demo-only
input('Input your name', name='name'), # ..demo-only
@@ -170,67 +181,95 @@ PyWebIO支持输入组, 返回结果为一个字典。`pywebio.input.input_group
put_text(data['name'], data['age']) # ..demo-only
.. attention::
- PyWebIO 根据是否在输入函数中传入 ``name`` 参数来判断输入函数是在 `input_group` 中还是被单独调用。
- 所以当单独调用一个输入函数时, **不要** 设置 ``name`` 参数;而在 `input_group` 中调用输入函数时,需 **务必提供** ``name`` 参数
+ 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.
-输出
+Output
------------
-输出函数都定义在 :doc:`pywebio.output ` 模块中,可以使用 ``from pywebio.output import *`` 引入。
+The output functions are all defined in the :doc:`pywebio.output ` module and can be imported using
+``from pywebio.output import *``.
-调用输出函数后,内容会实时输出到浏览器,在应用的生命周期内,可以在任意时刻调用输出函数。
+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.
-基本输出
+Basic Output
^^^^^^^^^^^^^^
-PyWebIO提供了一系列函数来输出表格、链接等格式:
+Using output functions, you can output a variety of content, such as text, tables, images and so on:
.. exportable-codeblock::
:name: basic-output
- :summary: 基本输出
+ :summary: Basic Output
- # 文本输出
+ # Text Output
put_text("Hello world!")
## ----
- # 表格输出
+ # Table Output
put_table([
- ['商品', '价格'],
- ['苹果', '5.5'],
- ['香蕉', '7'],
+ ['Commodity', 'Price'],
+ ['Apple', '5.5'],
+ ['Banana', '7'],
])
## ----
- # Markdown输出
- put_markdown('~~删除线~~')
+ # Image Output
+ put_image(open('/path/to/some/image.png', 'rb').read()) # local image # ..doc-only
+ put_image('http://example.com/some-image.png') # internet image # ..doc-only
+ put_image('https://www.python.org/static/img/python-logo.png') # ..demo-only
## ----
- # 文件输出
+ # Markdown Output
+ put_markdown('~~Strikethrough~~')
+ ## ----
+
+ # File Output
put_file('hello_word.txt', b'hello word!')
## ----
- # 显示一个弹窗
+ # Show a PopUp
popup('popup title', 'popup text content')
+ # Show a notification message
+ toast('New message 🔔')
+
+
+For all output functions provided by PyWebIO, please refer to the :doc:`pywebio.output ` module.
+In addition, PyWebIO also supports data visualization with some third-party libraries,
+see :doc:`Third-party library ecology `.
+
+
+.. note::
+
+ If you use PyWebIO in interactive execution environment of Python shell, IPython or jupyter notebook,
+ you need call `show()` method explicitly to show output::
+
+ >>> put_text("Hello world!").show()
+ >>> put_table([
+ ... ['A', 'B'],
+ ... [put_markdown(...), put_text('C')]
+ ... ]).show()
-PyWebIO提供的全部输出函数见 :doc:`pywebio.output ` 模块。另外,PyWebIO还支持一些第三方库来进行数据可视化,参见 :doc:`第三方库生态 ` 。
.. _combine_output:
-组合输出
-^^^^^^^^^^^^^^
-函数名以 ``put_`` 开始的输出函数,可以与一些输出函数组合使用,作为最终输出的一部分:
+Combined Output
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The output functions whose name starts with ``put_`` can be combined with some output functions as part of the final output:
-`put_table() ` 支持以 ``put_xxx()`` 调用作为单元格内容:
+You can pass ``put_xxx()`` calls to `put_table() ` as cell content:
.. exportable-codeblock::
:name: putxxx
- :summary: 组合输出
+ :summary: Combination output
put_table([
['Type', 'Content'],
['html', put_html('X2')],
- ['text', '
'], # 等价于 ['text', put_text('
')]
+ ['text', '
'], # equal to ['text', put_text('
')]
['buttons', put_buttons(['A', 'B'], onclick=...)], # ..doc-only
['buttons', put_buttons(['A', 'B'], onclick=put_text)], # ..demo-only
['markdown', put_markdown('`Awesome PyWebIO!`')],
@@ -238,60 +277,67 @@ PyWebIO提供的全部输出函数见 :doc:`pywebio.output ` 模块。
['table', put_table([['A', 'B'], ['C', 'D']])]
])
-上例显示效果如下:
+The results of the above example are as follows:
.. image:: /assets/put_table.png
-类似地, `popup() ` 也可以将 ``put_xxx()`` 调用作为弹窗内容:
+Similarly, you can pass ``put_xxx()`` calls to `popup() ` as the popup content:
.. exportable-codeblock::
:name: popup
- :summary: 弹窗
+ :summary: Popup
popup('Popup title', [
put_html('Popup Content
'),
- 'plain html:
', # 等价于 put_text('plain html:
')
+ 'plain html:
', # Equivalent to: put_text('plain html:
')
put_table([['A', 'B'], ['C', 'D']]),
- put_buttons(['close_popup()'], onclick=lambda _: close_popup())
+ put_button('close_popup()', onclick=close_popup)
])
-其他接受 ``put_xxx()`` 调用作为参数的输出函数还有 `put_collapse() ` 、 `put_scrollable() ` 、`put_row() ` 等,
-此外,还可以通过 `put_widget() ` 自定义可接收 ``put_xxx()`` 调用的输出组件,具体用法请参考函数文档。
+In addition, you can use `put_widget() ` to make your own output widgets that can accept ``put_xxx()`` calls.
-使用组合输出时,如果想在内容输出后,对其中的 ``put_xxx()`` 子项进行动态修改,可以使用 `output() ` 函数,
-`output() ` 就像一个占位符,它可以像 ``put_xxx()`` 一样传入 `put_table` 、 `popup` 、 `put_widget` 等函数中作为输出的一部分,
-并且,在输出后,还可以对其中的内容进行修改(比如重置或增加内容):
+For a full list of functions that accept ``put_xxx()`` calls as content, see :ref:`Output functions list `
+
+**Context Manager**
+
+Some output functions that accept ``put_xxx()`` calls as content can be used as context manager:
.. exportable-codeblock::
- :name: output
- :summary: 内容占位符——`output()`
+ :name: output-context-manager
+ :summary: Output as context manager
- hobby = output(put_text('Coding'))
- put_table([
- ['Name', 'Hobbies'],
- ['Wang', hobby] # hobby 初始为 Coding
- ])
- ## ----
+ with put_collapse('This is title'):
+ for i in range(4):
+ put_text(i)
- hobby.reset(put_text('Movie')) # hobby 被重置为 Movie
- ## ----
- hobby.append(put_text('Music'), put_text('Drama')) # 向 hobby 追加 Music, Drama
- ## ----
- hobby.insert(0, put_markdown('**Coding**')) # 将 Coding 插入 hobby 顶端
+ put_table([
+ ['Commodity', 'Price'],
+ ['Apple', '5.5'],
+ ['Banana', '7'],
+ ])
+For a full list of functions that support context manager, see :ref:`Output functions list `
-事件回调
-^^^^^^^^^^^^^^
-从上面可以看出,PyWebIO把交互分成了输入和输出两部分:输入函数为阻塞式调用,会在用户浏览器上显示一个表单,在用户提交表单之前输入函数将不会返回;输出函数将内容实时输出至浏览器。这种交互方式和控制台程序是一致的,因此PyWebIO应用非常适合使用控制台程序的编写逻辑来进行开发。
+.. _callback:
+
+Click Callback
+^^^^^^^^^^^^^^^^
-此外,PyWebIO还支持事件回调:PyWebIO允许你输出一些控件,当控件被点击时执行提供的回调函数。
+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 and 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 programming way.
-下面是一个例子:
+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.
+
+This is an example:
.. exportable-codeblock::
:name: onclick-callback
- :summary: 事件回调
+ :summary: Event callback
from functools import partial
@@ -305,60 +351,82 @@ PyWebIO提供的全部输出函数见 :doc:`pywebio.output ` 模块。
[3, put_buttons(['edit', 'delete'], onclick=partial(edit_row, row=3))],
])
-`put_table() ` 的调用不会阻塞。当用户点击了某行中的按钮时,PyWebIO会自动调用相应的回调函数:
+The call to `put_table() ` will not block. When user clicks a button, the corresponding
+callback function will be invoked:
.. image:: /assets/table_onclick.*
-当然,PyWebIO还支持单独的按钮控件:
+Of course, PyWebIO also supports outputting individual button:
.. exportable-codeblock::
:name: put-buttons
- :summary: 按钮控件
+ :summary: Event callback of button widget
def btn_click(btn_val):
put_text("You click %s button" % btn_val)
- put_buttons(['A', 'B', 'C'], onclick=btn_click)
-.. note::
- 在PyWebIO会话(关于会话的概念见下文 :ref:`Server与script模式 ` )结束后,事件回调也将不起作用,你可以在任务函数末尾处使用 :func:`pywebio.session.hold()` 函数来将会话保持,这样在用户关闭浏览器页面前,事件回调将一直可用。
+ put_buttons(['A', 'B', 'C'], onclick=btn_click) # a group of buttons
+
+ put_button("Click me", onclick=lambda: toast("Clicked")) # single button
-输出域Scope
+In fact, all output can be bound to click events, not just buttons. You can call ``onclick()`` method after the output
+function (function name like ``put_xxx()``) call:
+
+.. exportable-codeblock::
+ :name: onclick
+ :summary: Click callback on any output
+
+ put_image('some-image.png').onclick(lambda: toast('You click an image')) # ..doc-only
+ put_image('https://www.python.org/static/img/python-logo.png').onclick(lambda: toast('You click an image')) # ..demo-only
+
+ # set onclick in combined output
+ put_table([
+ ['Commodity', 'Price'],
+ ['Apple', put_text('5.5').onclick(lambda: toast('You click the text'))],
+ ])
+
+The return value of ``onclick()`` method is the object itself so it can be used in combined output.
+
+.. _output_scope:
+
+Output Scope
^^^^^^^^^^^^^^
-PyWebIO使用Scope模型来对内容输出的位置进行灵活地控制,PyWebIO的内容输出区可以划分出不同的输出域,PyWebIO将输出域称作 `Scope` 。
-输出域为输出内容的容器,各个输出域之间上下排列,输出域也可以进行嵌套。
+PyWebIO uses the scope model to give more control to the location of content output. The output scope is a container
+of output content. You can create a scope in somewhere and append content to it.
-每个输出函数(函数名形如 `put_xxx()` )都会将内容输出到一个Scope,默认为"当前Scope","当前Scope"由运行时上下文确定,输出函数也可以手动指定输出到的Scope。Scope名在会话内唯一。
+Each output function (function name like ``put_xxx()``) will output its content to a scope, the default is "current scope".
+The "current scope" is set by `use_scope() `.
.. _use_scope:
**use_scope()**
-可以使用 `use_scope() ` 开启并进入一个新的输出域,或进入一个已经存在的输出域:
+You can use `use_scope() ` to open and enter a new output scope, or enter an existing output scope:
.. exportable-codeblock::
:name: use-scope
- :summary: 使用`use_scope()`创建或进入输出域
+ :summary: use `use_scope()` to open or enter scope
- with use_scope('scope1'): # 创建并进入scope 'scope1'
- put_text('text1 in scope1')
+ with use_scope('scope1'): # open and enter a new output: 'scope1'
+ put_text('text1 in scope1') # output text to scope1
- put_text('text in parent scope of scope1')
+ put_text('text in parent scope of scope1') # output text to ROOT scope
- with use_scope('scope1'): # 进入之前创建的scope 'scope1'
- put_text('text2 in scope1')
+ with use_scope('scope1'): # enter an existing scope: 'scope1'
+ put_text('text2 in scope1') # output text to scope1
-以上代码将会输出::
+The results of the above code are as follows::
text1 in scope1
text2 in scope1
text in parent scope of scope1
-`use_scope() ` 还可以使用 `clear` 参数将scope中原有的内容清空:
+You can use ``clear`` parameter in `use_scope() ` to clear the existing content before entering the scope:
.. exportable-codeblock::
- :name: use-scope
- :summary: 使用`use_scope()`清空输出域内容
+ :name: use-scope-clear
+ :summary: `use_scope()`'s `clear` parameter
with use_scope('scope2'):
put_text('create scope2')
@@ -366,19 +434,19 @@ PyWebIO使用Scope模型来对内容输出的位置进行灵活地控制,PyWeb
put_text('text in parent scope of scope2')
## ----
- with use_scope('scope2', clear=True): # 进入之前创建的scope2,并清空原有内容
+ with use_scope('scope2', clear=True): # enter the existing scope and clear the previous content
put_text('text in scope2')
-以上代码将会输出::
+The results of the above code are as follows::
text in scope2
text in parent scope of scope2
-`use_scope() ` 还可以作为装饰器来使用:
+`use_scope() ` can also be used as decorator:
.. exportable-codeblock::
:name: use-scope-decorator
- :summary: `use_scope()`作为装饰器来使用
+ :summary: `use_scope()` as decorator
import time # ..demo-only
from datetime import datetime
@@ -391,16 +459,15 @@ PyWebIO使用Scope模型来对内容输出的位置进行灵活地控制,PyWeb
show_time() # ..demo-only
time.sleep(1) # ..demo-only
-第一次调用 ``show_time`` 时,将会在当前位置创建 ``time`` 输出域并在其中输出当前时间,之后每次调用 ``show_time()`` ,时间都会输出到相同的区域。
+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.
-Scope是可嵌套的,初始条件下,PyWebIO应用只有一个最顶层的 ``ROOT`` Scope。每创建一个新Scope,Scope的嵌套层级便会多加一层,每退出当前Scope,Scope的嵌套层级便会减少一层。
-PyWebIO使用Scope栈来保存运行时的Scope的嵌套层级。
-
-例如,如下代码将会创建3个Scope:
+Scopes can be nested. At the beginning, PyWebIO applications have only one ``ROOT`` scope.
+You can create new scope in a scope. For example, the following code will create 3 scopes:
.. exportable-codeblock::
:name: use-scope-nested
- :summary: 嵌套Scope
+ :summary: Nested Scope
with use_scope('A'):
put_text('Text in scope A')
@@ -420,7 +487,7 @@ PyWebIO使用Scope栈来保存运行时的Scope的嵌套层级。
put_buttons([('Put text to %s' % i, i) for i in ('A', 'B', 'C')], lambda s: put_text(s, scope=s)) # ..demo-only
-以上代码将会产生如下Scope布局::
+The above code will generate the following scope layout::
┌─ROOT────────────────────┐
│ │
@@ -436,103 +503,78 @@ PyWebIO使用Scope栈来保存运行时的Scope的嵌套层级。
│ └─────────────────────┘ │
└─────────────────────────┘
-.. _scope_param:
-
-**输出函数的scope相关参数**
+.. _put_scope:
-输出函数(函数名形如 ``put_xxx()`` )在默认情况下,会将内容输出到"当前Scope",可以通过 ``use_scope()`` 设置运行时上下文的"当前Scope"。
+**put_scope()**
-此外,也可以通过输出函数的 ``scope`` 参数指定输出的目的Scope:
+We already know that the scope is a container of output content. So can we use this container as a sub-item
+of a output (like, set a cell in table as a container)? Yes, you can use `put_scope() ` to
+create a scope explicitly.
+The function name starts with ``put_``, which means it can be pass to the functions that accept ``put_xxx()`` calls.
.. exportable-codeblock::
- :name: put-xxx-scope
- :summary: 输出函数的`scope`参数
+ :name: put_scope
+ :summary: `put_scope()`
- with use_scope('scope3'):
- put_text('text1 in scope3') # 输出到当前Scope:scope3
- put_text('text in ROOT scope', scope='ROOT') # 输出到ROOT Scope
-
- put_text('text2 in scope3', scope='scope3') # 输出到scope3
-
-以上将会输出::
-
- text1 in scope3
- text2 in scope3
- text in ROOT scope
-
-``scope`` 参数除了直接指定目标Scope名,还可以使用一个整形通过索引Scope栈来确定Scope:0表示最顶层也就是ROOT Scope,-1表示当前Scope,-2表示进入当前Scope前所使用的Scope,......
-
-默认条件下,在同一Scope中的输出内容,会根据输出函数的调用顺序从上往下排列,最后调用的输出函数会输出内容到目标Scope的底部。通过输出函数的 ``position`` 参数可以将输出内容插入到目标Scope的其他位置。
-
-一个Scope中各次输出的元素具有像数组一样的索引,最前面的编号为0,以此往后递增加一;同样可以使用负数对Scope中的元素进行索引,-1表示最后面的元素,-2表示次后面的元素......
-
-``position`` 参数类型为整形, ``position>=0`` 时表示输出内容到目标Scope的第position号元素的前面; ``position<0`` 时表示输出内容到目标Scope第position号元素之后:
-
-.. exportable-codeblock::
- :name: put-xxx-position
- :summary: 输出函数的`position`参数
+ put_table([
+ ['Name', 'Hobbies'],
+ ['Tom', put_scope('hobby', content=put_text('Coding'))] # hobby is initialized to coding
+ ])
- with use_scope('scope1'):
- put_text('A') # 输出内容: A
## ----
- with use_scope('scope1'): # ..demo-only
- put_text('B', position=0) # 输出内容: B A
- ## ----
- with use_scope('scope1'): # ..demo-only
- put_text('C', position=-2) # 输出内容: B C A
- ## ----
- with use_scope('scope1'): # ..demo-only
- put_text('D', position=1) # 输出内容: B D C A
-
-**输出域控制函数**
-
-除了 `use_scope()` , PyWebIO同样提供了以下scope控制函数:
-
-* `set_scope(name) ` : 在当前位置(或指定位置)创建scope
-* `clear(scope) ` : 清除scope的内容
-* `remove(scope) ` : 移除scope
-* `scroll_to(scope) ` : 将页面滚动到scope处
-
+ with use_scope('hobby', clear=True):
+ put_text('Movie') # hobby is reset to Movie
-页面环境设置
-^^^^^^^^^^^^^^
+ ## ----
+ # append Music, Drama to hobby
+ with use_scope('hobby'):
+ put_text('Music')
+ put_text('Drama')
-**页面标题**
+ ## ----
+ # insert the Coding into the top of the hobby
+ put_markdown('**Coding**', scope='hobby', position=0)
-调用 `set_env(title=...) ` 可以设置页面标题。
-**自动滚动**
+.. caution:: It is not allowed to have two scopes with the same name in the application.
-在进行一些持续性的输出时(比如日志输出),有时希望在有新输出后自动将页面滚动到最下方,这时可以调用 `set_env(auto_scroll_bottom=True) ` 来开启自动滚动。
-注意,开启后,只有输出到ROOT Scope才可以触发自动滚动。
+**Scope control**
-**输出动画**
+In addition to `use_scope() ` and `put_scope() `,
+PyWebIO also provides the following scope control functions:
-PyWebIO在输出内容时默认会使用淡入的动画效果来显示内容,可使用 `set_env(output_animation=False) ` 来关闭动画。
+* `clear(scope) ` : Clear the contents of the scope
+* `remove(scope) ` : Remove scope
+* `scroll_to(scope) ` : Scroll the page to the scope
-有关不同环境配置的效果可查看 :demo_host:`set_env Demo ?pywebio_api=set_env_demo>`
+Also, all output functions (function name like ``put_xxx()``) support a ``scope`` parameter to specify the destination
+scope to output, and support a ``position`` parameter to specify the insert position in target scope.
+Refer :ref:`output module ` for more information.
-布局
+Layout
^^^^^^^^^^^^^^
-一般情况下,使用上文介绍的各种输出函数足以完成各种内容的展示,但直接调用输出函数产生的输出之间都是竖直排列的,如果想实现更复杂的布局(比如在页面左侧显示一个代码块,在右侧显示一个图像),就需要借助布局函数。
-``pywebio.output`` 模块提供了3个布局函数,通过对他们进行组合可以完成各种复杂的布局:
+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.
-* `put_row() ` : 使用行布局输出内容. 内容在水平方向上排列
-* `put_column() ` : 使用列布局输出内容. 内容在竖直方向上排列
-* `put_grid() ` : 使用网格布局输出内容
+The ``pywebio.output`` module provides 3 layout functions, and you can create complex layouts by combining them:
-通过组合 ``put_row()`` 和 ``put_column()`` 可以实现灵活布局:
+* `put_row() ` : Use row layout to output content. The content is arranged horizontally
+* `put_column() ` : Use column layout to output content. The content is arranged vertically
+* `put_grid() ` : Output content using grid layout
+
+Here is an example by combining ``put_row()`` and ``put_column()``:
.. exportable-codeblock::
:name: put-row-column
- :summary: 布局函数
+ :summary: Layout functions
put_row([
put_column([
put_code('A'),
put_row([
- put_code('B1'), None, # None 表示输出之间的空白
+ put_code('B1'), None, # None represents the space between the output
put_code('B2'), None,
put_code('B3'),
]),
@@ -542,103 +584,131 @@ PyWebIO在输出内容时默认会使用淡入的动画效果来显示内容,
put_code('E')
])
-显示效果如下:
+The results of the above example are as follows:
.. image:: /assets/layout.png
:align: center
-布局函数还支持自定义各部分的尺寸::
+The layout function also supports customizing the size of each part::
+
+ put_row([put_image(...), put_image(...)], size='40% 60%') # The ratio of the width of two images is 2:3
- put_row([put_image(...), put_image(...)], size='40% 60%') # 左右两图宽度比2:3
+For more information, please refer to the :ref:`layout functions documentation `.
-更多布局函数的用法及代码示例请查阅 :ref:`布局函数文档 ` .
+.. _style:
-样式
+Style
^^^^^^^^^^^^^^
-如果你熟悉 `CSS样式 `_ ,你还可以使用 `style() ` 函数给输出设定自定义样式。
-可以给单个的 ``put_xxx()`` 输出设定CSS样式,也可以配合组合输出使用:
+If you are familiar with `CSS `_ styles,
+you can use the ``style()`` method of output return to set a custom style for the output.
+
+You can set the CSS style for a single ``put_xxx()`` output:
.. exportable-codeblock::
- :name: style
- :summary: 输出样式
+ :name: style-demo
+ :summary: style of output
- style(put_text('Red'), 'color: red')
+ put_text('hello').style('color: red; font-size: 20px')
## ----
- put_table([
- ['A', 'B'],
- ['C', style(put_text('Red'), 'color: red')],
- ])
+ # in combined output
+ put_row([
+ put_text('hello').style('color: red'),
+ put_markdown('markdown')
+ ]).style('margin-top: 20px')
-``style()`` 也接受列表作为输入,``style()`` 会为列表的每一项都设置CSS样式,返回值可以直接输出,可用于任何接受 ``put_xxx()`` 列表的地方:
+The return value of ``style()`` method is the object itself so it can be used in combined output.
-.. exportable-codeblock::
- :name: style-list
- :summary: 批量设置输出样式
+.. _server_and_script_mode:
- style([
- put_text('Red'),
- put_markdown('~~del~~')
- ], 'color: red')
+Run application
+----------------
- ## ----
- put_collapse('title', style([
- put_text('text'),
- put_markdown('~~del~~'),
- ], 'margin-left: 20px'))
+In PyWebIO, there are two modes to run PyWebIO applications: running as a script and using
+`pywebio.start_server() ` or
+`pywebio.platform.path_deploy() ` to run as a web service.
+Overview
+^^^^^^^^^^^^^^
-.. _server_and_script_mode:
+.. _server_mode:
+
+**Server mode**
+
+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.
-Server模式与Script模式
-------------------------------------
+`start_server() ` is the most common way to start a web server to serve given
+PyWebIO applications::
-在 :ref:`Hello, world ` 一节中,已经知道,PyWebIO支持在普通的脚本中调用和使用
-`start_server() ` 启动一个Web服务两种模式。
+ from pywebio import *
-**Server模式**
+ def main(): # PyWebIO application function
+ name = input.input("what's your name")
+ output.put_text("hello", name)
-在Server模式下,PyWebIO会启动一个Web服务来持续性地提供服务。需要提供一个任务函数(类似于Web开发中的视图函数),当用户访问服务地址时,PyWebIO会开启一个新会话并运行任务函数。
+ start_server(main, port=8080, debug=True)
-使用 `start_server() ` 来启动PyWebIO的Server模式, `start_server() ` 除了接收一个函数作为任务函数外,
-还支持传入函数列表或字典,从而使一个PyWebIO Server下可以有多个不同功能的服务,服务之间可以通过 `go_app() ` 进行跳转,详细内容见函数文档。
+Now head over to http://127.0.0.1:8080/, and you should see your hello greeting.
+
+By using ``debug=True`` to enable debug mode, the server will automatically reload if code changes.
+
+The `start_server() ` provide a remote access support, when enabled
+(by passing `remote_access=True` to `start_server()`), you will get a public, shareable address for the current
+application, others can access your application in their browser via this address. Because the processing happens
+on your device (as long as your device stays on!), you don't have to worry about any dependencies.
+Using remote access makes it easy to temporarily share the application with others.
+
+Another way to deploy PyWebIO application as web service is using `path_deploy() `.
+`path_deploy() ` is used to deploy the PyWebIO applications from a directory.
+Just define PyWebIO applications in python files under this directory, and you can access them via the path in the URL.
+Refer to :ref:`platform module ` for more information.
.. attention::
- 注意,在Server模式下,仅能在任务函数上下文中对PyWebIO的交互函数进行调用。比如如下调用是 **不被允许的** ::
+ Note that in Server mode, all functions from ``pywebio.input``, ``pywebio.output`` and ``pywebio.session`` modules can only be called in
+ the context of PyWebIO application functions. For example, the following code is **not allowed**::
import pywebio
from pywebio.input import input
- port = input('Input port number:') # ❌ 在任务函数上下文之外调用了PyWebIO交互函数!!
+ port = input('Input port number:') # ❌ error
pywebio.start_server(my_task_func, port=int(port))
-**Script模式**
+**Script mode**
-Script模式下,在任何位置都可以调用PyWebIO的交互函数。
+If you never call ``start_server()`` or ``path_deploy()`` in your code, then you are running PyWebIO application as script mode.
-如果用户在会话结束之前关闭了浏览器,那么之后会话内对于PyWebIO交互函数的调用将会引发一个 `SessionException ` 异常。
+In script mode, a web browser page will be open automatically when running to the first call to PyWebIO interactive functions,
+and all subsequent PyWebIO interactions will take place on this page. When the script exit, the page will be inactive.
+
+If the user closes the browser before the script exiting, then subsequent calls to PyWebIO's interactive functions
+will cause a `SessionException ` exception.
.. _thread_in_server_mode:
-并发
+Concurrent
^^^^^^^^^^^^^^
-PyWebIO 支持在多线程环境中使用。
+PyWebIO can be used in a multi-threading environment.
-**Script模式**
+**Script mode**
-在 Script模式下,你可以自由地启动线程,并在其中调用PyWebIO的交互函数。当所有非 `Daemon线程 `_ 运行结束后,脚本退出。
+In script mode, you can freely start new thread and call PyWebIO interactive functions in it.
+When all `non-daemonic `_ threads finish running, the script exits.
-**Server模式**
+**Server mode**
-Server模式下,如果需要在新创建的线程中使用PyWebIO的交互函数,需要手动调用 `register_thread(thread) ` 对新进程进行注册(这样PyWebIO才能知道新创建的线程属于哪个会话)。
-如果新创建的线程中没有使用到PyWebIO的交互函数,则无需注册。没有使用 `register_thread(thread) ` 注册的线程不受会话管理,其调用PyWebIO的交互函数将会产生 `SessionNotFoundException ` 异常。
-当会话的任务函数和会话内通过 `register_thread(thread) ` 注册的线程都结束运行时,会话关闭。
+In server mode, if you need to use PyWebIO interactive functions in new thread, you need to use
+`pywebio.session.register_thread(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) ` calling PyWebIO's interactive functions will cause
+`SessionNotFoundException `.
-Server模式下多线程的使用示例::
+Example of using multi-threading in Server mode::
def show_time():
while True:
@@ -650,9 +720,9 @@ Server模式下多线程的使用示例::
t = threading.Thread(target=show_time)
register_thread(t)
put_markdown('## Clock')
- t.start() # 在后台运行show_time()
+ t.start() # run `show_time()` in background
- # ❌ 没有使用register_thread注册的线程调用PyWebIO交互函数会产生异常
+ # ❌ this thread will cause `SessionNotFoundException`
threading.Thread(target=show_time).start()
put_text('Background task started.')
@@ -663,328 +733,83 @@ Server模式下多线程的使用示例::
.. _session_close:
-会话的结束
-^^^^^^^^^^^^^^
-
-会话还会因为用户的关闭浏览器而结束,这时当前会话内还未返回的PyWebIO输入函数调用将抛出 `SessionClosedException ` 异常,之后对于PyWebIO交互函数的调用将会产生 `SessionNotFoundException ` 或 `SessionClosedException ` 异常。
-
-可以使用 `defer_call(func) ` 来设置会话结束时需要调用的函数。无论是因为用户主动关闭页面还是任务结束使得会话关闭,设置的函数都会被执行。
-`defer_call(func) ` 可以用于资源清理等工作。在会话中可以多次调用 `defer_call() ` ,会话结束后将会顺序执行设置的函数。
-
-
-与Web框架集成
----------------
-
-.. _integration_web_framework:
+Close of session
+^^^^^^^^^^^^^^^^^
-可以将PyWebIO应用集成到现有的Python Web项目中,PyWebIO应用与Web项目共用一个Web框架。目前支持与Flask、Tornado、Django和aiohttp Web框架的集成。
+When user close the browser page, the session will be closed. After the browser page is closed, PyWebIO input function
+calls that have not yet returned in the current session will cause `SessionClosedException `,
+and subsequent calls to PyWebIO interactive functions will cause `SessionNotFoundException `
+or `SessionClosedException `.
-与Web框架集成需要完成两部分配置:托管PyWebIO前端静态文件;暴露PyWebIO后端接口。这其中需要注意前端页面和后端接口的路径约定,
-以及前端静态文件与后端接口分开部署时因为跨域而需要的特别设置。
+In most cases, you don't need to catch those exceptions, because let those exceptions to abort the running is the right way to exit.
-集成方法
-^^^^^^^^^^^
+You can use `pywebio.session.defer_call(func) ` to set the function to be called when the
+session closes. `defer_call(func) ` can be used for resource cleaning. You can call
+`defer_call(func) ` multiple times in the session, and the set functions will be executed
+sequentially after the session closes.
-不同Web框架的集成方法如下:
-
-.. tabs::
-
- .. tab:: Tornado
-
- 需要在Tornado应用中引入两个 ``RequestHandler`` ,
- 一个 ``RequestHandler`` 用来提供静态的前端文件,另一个 ``RequestHandler`` 用来和浏览器进行WebSocket通讯::
-
- import tornado.ioloop
- import tornado.web
- from pywebio.platform.tornado import webio_handler
- from pywebio import STATIC_PATH
-
- class MainHandler(tornado.web.RequestHandler):
- def get(self):
- self.write("Hello, world")
-
- if __name__ == "__main__":
- application = tornado.web.Application([
- (r"/", MainHandler),
- (r"/tool/io", webio_handler(task_func)), # task_func 为使用PyWebIO编写的任务函数
- (r"/tool/(.*)", tornado.web.StaticFileHandler,
- {"path": STATIC_PATH, 'default_filename': 'index.html'}) # 前端静态文件托管
- ])
- application.listen(port=80, address='localhost')
- tornado.ioloop.IOLoop.current().start()
-
- 以上代码调用 `webio_handler(task_func) ` 来获得PyWebIO和浏览器进行通讯的Tornado `WebSocketHandler `_ ,
- 并将其绑定在 ``/tool/io`` 路径下;同时将PyWebIO的静态文件使用 `tornado.web.StaticFileHandler `_ 托管到 ``/tool/(.*)`` 路径下。
- 启动Tornado服务器后,访问 ``http://localhost/tool/`` 即可打开PyWebIO应用
-
- .. attention::
-
- 当使用Tornado后端时,PyWebIO使用WebSocket协议和浏览器进行通讯,如果你的Tornado应用处在反向代理(比如Nginx)之后,
- 可能需要特别配置反向代理来支持WebSocket协议,:ref:`这里 ` 有一个Nginx配置WebSocket的例子。
-
- .. tab:: Flask
-
- 需要添加两个PyWebIO相关的路由:一个用来提供静态的前端文件,另一个用来和浏览器进行Http通讯::
-
- from pywebio.platform.flask import webio_view
- from pywebio import STATIC_PATH
- from flask import Flask, send_from_directory
-
- app = Flask(__name__)
-
- # task_func 为使用PyWebIO编写的任务函数
- app.add_url_rule('/io', 'webio_view', webio_view(task_func),
- methods=['GET', 'POST', 'OPTIONS']) # 接口需要能接收GET、POST和OPTIONS请求
-
- @app.route('/')
- @app.route('/')
- def serve_static_file(static_file='index.html'):
- """前端静态文件托管"""
- return send_from_directory(STATIC_PATH, static_file)
-
- app.run(host='localhost', port=80)
-
- 以上代码使用 `webio_view(task_func) ` 来获得运行PyWebIO应用的Flask视图 ,
- 并调用 `Flask.add_url_rule `_ 将其绑定在 ``/io`` 路径下;同时编写视图函数 ``serve_static_file`` 将PyWebIO使用的静态文件托管到 ``/`` 路径下。
- 启动Flask应用后,访问 ``http://localhost/`` 即可打开PyWebIO应用
-
- .. tab:: Django
-
- 在django的路由配置文件 ``urls.py`` 中加入PyWebIO相关的路由即可::
-
- # urls.py
-
- from functools import partial
- from django.urls import path
- from django.views.static import serve
- from pywebio import STATIC_PATH
- from pywebio.platform.django import webio_view
-
- # task_func 为使用PyWebIO编写的任务函数
- webio_view_func = webio_view(task_func)
-
- urlpatterns = [
- path(r"io", webio_view_func), # http通信接口
- path(r'', partial(serve, path='index.html'), {'document_root': STATIC_PATH}), # 前端index.html文件托管
- path(r'', serve, {'document_root': STATIC_PATH}), # 前端其他文件托管
- ]
-
- 需要添加3条路由规则,第一条路由规则将PyWebIO应用的视图函数绑定到 ``/io`` 路径下,第二条路由用于提供PyWebIO的前端index.html文件,最后一个路由用于提供PyWebIO的其他静态文件
-
- 启动Django应用后,访问 ``http://localhost/`` 即可打开PyWebIO应用
-
- .. tab:: aiohttp
-
- 添加两个PyWebIO相关的路由:一个用来提供静态的前端文件,另一个用来和浏览器进行WebSocket通讯::
-
- from aiohttp import web
- from pywebio.platform.aiohttp import static_routes, webio_handler
-
- app = web.Application()
- # task_func 为使用PyWebIO编写的任务函数
- app.add_routes([web.get('/io', webio_handler(task_func))]) # http通信接口
- app.add_routes(static_routes('/')) # 前端静态文件托管
-
- web.run_app(app, host='localhost', port=8080)
-
- 启动aiohttp应用后,访问 ``http://localhost/`` 即可打开PyWebIO应用
-
- .. attention::
-
- 当使用aiohttp后端时,PyWebIO使用WebSocket协议和浏览器进行通讯,如果你的aiohttp应用处在反向代理(比如Nginx)之后,
- 可能需要特别配置反向代理来支持WebSocket协议,:ref:`这里 ` 有一个Nginx配置WebSocket的例子。
-
-.. _integration_web_framework_note:
-
-注意事项
-^^^^^^^^^^^
-**PyWebIO静态资源的托管**
-
-在开发阶段,使用后端框架提供的静态文件服务对于开发和调试都十分方便,上文的与Web框架集成的示例代码也都是使用了后端框架提供的静态文件服务。
-但出于性能考虑,托管静态文件最好的方式是使用 `反向代理 `_ (比如 `nginx `_ )
-或者 `CDN `_ 服务。
-
-**前端页面和后端接口的路径约定**
-
-PyWebIO默认通过当前页面的同级的 ``./io`` API与后端进行通讯。
-
-例如你将PyWebIO静态文件托管到 ``/A/B/C/(.*)`` 路径下,那么你需要将PyWebIO API的路由绑定到 ``/A/B/C/io`` 处;
-你也可以在PyWebIO应用的地址中添加 ``pywebio_api`` url参数来指定PyWebIO后端API地址,
-例如 ``/A/B/C/?pywebio_api=/D/pywebio`` 将PyWebIO后端API地址设置到了 ``/D/pywebio`` 处。
-
-``pywebio_api`` 参数可以使用相对地址、绝对地址,也可以指定其他服务器。
-
-.. caution::
-
- 需要注意 ``pywebio_api`` 参数的格式:
-
- * 相对地址可以为 ``./xxx/xxx`` 或 ``xxx/xxx`` 的相对地址格式。
- * 绝对地址以 ``/`` 开头,比如 ``/aaa/bbb`` .
- * 指定其他服务器需要使用完整格式: ``http://example.com:5000/aaa/io`` 、 ``ws://example.com:8080/bbb/ws_io`` ,或者省略协议字段: ``//example.com:8080/aaa/io`` 。省略协议字段时,PyWebIO根据当前页面的协议确定要使用的协议: 若当前页面为http协议,则后端接口自动选择http或ws协议;若当前页面为https协议,则后端接口自动选择https或wss协议。
-
-如果你不想自己托管静态文件,你可以使用PyWebIO的Github Page页面: ``https://wang0618.github.io/PyWebIO/pywebio/html/?pywebio_api=`` ,需要在页面上通过 ``pywebio_api`` 参数传入后端API地址,并且将 ``https://wang0618.github.io`` 加入 ``allowed_origins`` 列表中(见下文"跨域配置"说明)。
-
-**跨域配置**
-
-当后端API与前端页面不在同一host下时,需要在 `webio_handler() ` 或
-`webio_view() ` 中使用 ``allowed_origins`` 或 ``check_origin``
-参数来使后端接口允许前端页面的请求。
-
-.. _coroutine_based_session:
-
-基于协程的会话
----------------
-此部分内容属于高级特性,您不必使用此部分也可以实现PyWebIO支持的全部功能。PyWebIO中所有仅用于协程会话的函数或方法都在文档中有特别说明。
-
-PyWebIO的会话实现默认是基于线程的,用户每打开一个和服务端的会话连接,PyWebIO会启动一个线程来运行任务函数。
-除了基于线程的会话,PyWebIO还提供了基于协程的会话。基于协程的会话接受协程函数作为任务函数。
-
-基于协程的会话为单线程模型,所有会话都运行在一个线程内。对于IO密集型的任务,协程比线程占用更少的资源同时又拥有媲美于线程的性能。
-另外,协程的上下文切换具有可预测性,能够减少程序同步与加锁的需要,可以有效避免大多数临界区问题。
-
-使用协程会话
-^^^^^^^^^^^^^^^^
-
-要使用基于协程的会话,需要使用 ``async`` 关键字将任务函数声明为协程函数,并使用 ``await`` 语法调用PyWebIO输入函数:
-
-.. code-block:: python
- :emphasize-lines: 5,6
-
- from pywebio.input import *
- from pywebio.output import *
- from pywebio import start_server
-
- async def say_hello():
- name = await input("what's your name?")
- put_text('Hello, %s' % name)
-
- start_server(say_hello, auto_open_webbrowser=True)
-
-在协程任务函数中,也可以使用 ``await`` 调用其他协程或标准库 `asyncio `_ 中的可等待对象( `awaitable objects `_ ):
-
-.. code-block:: python
- :emphasize-lines: 6,10
-
- import asyncio
- from pywebio import start_server
-
- async def hello_word():
- put_text('Hello ...')
- await asyncio.sleep(1) # await asyncio 库中的 awaitable objects
- put_text('... World!')
-
- async def main():
- await hello_word() # await 协程
- put_text('Bye, bye')
-
- start_server(main, auto_open_webbrowser=True)
-
-.. attention::
-
- 在基于协程的会话中, :doc:`pywebio.input ` 模块中的定义输入函数都需要使用 ``await`` 语法来获取返回值,
- 忘记使用 ``await`` 将会是在使用基于协程的会话时常出现的错误。
-
- 其他在协程会话中也需要使用 ``await`` 语法来进行调用函数有:
-
- * `pywebio.session.run_asyncio_coroutine(coro_obj) `
- * `pywebio.session.eval_js(expression) `
- * `pywebio.session.hold() `
-
-.. warning::
-
- 虽然PyWebIO的协程会话兼容标准库 ``asyncio`` 中的 ``awaitable objects`` ,但 ``asyncio`` 库不兼容PyWebIO协程会话中的 ``awaitable objects`` .
-
- 也就是说,无法将PyWebIO中的 ``awaitable objects`` 传入 ``asyncio`` 中的接受 ``awaitable objects`` 作为参数的函数中,比如如下调用是 **不被支持的** ::
-
- await asyncio.shield(pywebio.input())
- await asyncio.gather(asyncio.sleep(1), pywebio.session.eval_js('1+1'))
- task = asyncio.create_task(pywebio.input())
-
-协程会话的并发
-^^^^^^^^^^^^^^^^
-
-在基于协程的会话中,你可以启动线程,但是无法在其中调用PyWebIO交互函数( `register_thread() ` 在协程会话中不可用)。
-但你可以使用 `run_async(coro) ` 来异步执行一个协程对象,新协程内可以使用PyWebIO交互函数::
-
- from pywebio import start_server
- from pywebio.session import run_async
-
- async def counter(n):
- for i in range(n):
- put_text(i)
- await asyncio.sleep(1)
-
- async def main():
- run_async(counter(10))
- put_text('Main coroutine function exited.')
-
-
- start_server(main, auto_open_webbrowser=True)
-
-`run_async(coro) ` 返回一个 `TaskHandle ` ,通过 `TaskHandle ` 可以查询协程运行状态和关闭协程。
-
-协程会话的关闭
-^^^^^^^^^^^^^^^^
-与基于线程的会话类似,在基于协程的会话中,当任务函数和在会话内通过 `run_async() ` 运行的协程全部结束后,会话关闭。
+More about PyWebIO
+---------------------
+By now, you already get the most important features of PyWebIO and can start to write awesome PyWebIO applications.
+However, there are some other useful features we don't cover in the above. Here we just make a briefly explain about them.
+When you need them in your application, you can refer to their document.
-对于因为用户的关闭浏览器而造成的会话结束,处理逻辑和 :ref:`基于线程的会话 ` 一致:
-此时当前会话内还未返回的PyWebIO输入函数调用将抛出 `SessionClosedException ` 异常,之后对于PyWebIO交互函数的调用将会产生 `SessionNotFoundException ` 或 `SessionClosedException ` 异常。
+Also, :doc:`here ` is a cookbook where you can find some useful code snippets for your PyWebIO application.
-协程会话也同样支持使用 `defer_call(func) ` 来设置会话结束时需要调用的函数。
+``session`` module
+^^^^^^^^^^^^^^^^^^^^
+The :doc:`pywebio.session ` module give you more control to session.
-协程会话与Web框架集成
-^^^^^^^^^^^^^^^^^^^^^^^^^
+ * Use `set_env() ` to configure the title, page appearance, input panel and so on for current session.
-基于协程的会话同样可以与Web框架进行集成,只需要在原来传入任务函数的地方改为传入协程函数即可。
+ * The `info ` object provides a lot information about the current session,
+ such as the user IP address, user language and user browser information.
-但当前在使用基于协程的会话集成进Flask或Django时,存在一些限制:
+ * `local ` is a session-local storage, it used to save data whose values are session specific.
-一是协程函数内还无法直接通过 ``await`` 直接等待asyncio库中的协程对象,目前需要使用 `run_asyncio_coroutine() ` 进行包装。
+ * `run_js() ` let you execute JavaScript code in user's browser,
+ and `eval_js() ` let you execute JavaScript expression and get the value of it.
-二是,在启动Flask/Django这类基于线程的服务器之前需要启动一个单独的线程来运行事件循环。
+``pin`` module
+^^^^^^^^^^^^^^^^^^^^
+As you already know, the input function of PyWebIO is blocking and the input form will be destroyed after successful submission.
+In some cases, you may want to make the input form not disappear after submission, and can continue to receive input.
+So PyWebIO provides the :doc:`pywebio.pin ` module to achieve persistent input by pinning input widgets to the page.
-使用基于协程的会话集成进Flask的示例:
+``platform`` module
+^^^^^^^^^^^^^^^^^^^^
-.. code-block:: python
- :emphasize-lines: 12,25
+The :doc:`pywebio.platform ` module provides support for deploying PyWebIO applications in different ways.
- import asyncio
- import threading
- from flask import Flask, send_from_directory
- from pywebio import STATIC_PATH
- from pywebio.output import *
- from pywebio.platform.flask import webio_view
- from pywebio.platform.httpbased import run_event_loop
- from pywebio.session import run_asyncio_coroutine
+There are two protocols (WebSocket and HTTP) can be used in server to communicates with the browser. The WebSocket is
+used by default. If you want to use HTTP protocol, you can choose other ``start_server()`` functions in this module.
- async def hello_word():
- put_text('Hello ...')
- await run_asyncio_coroutine(asyncio.sleep(1)) # 无法直接 await asyncio.sleep(1)
- put_text('... World!')
+You might want to set some web page related configuration (such as SEO information, js and css injection) for your PyWebIO application,
+`pywebio.config() ` can be helpful.
- app = Flask(__name__)
- app.add_url_rule('/io', 'webio_view', webio_view(hello_word),
- methods=['GET', 'POST', 'OPTIONS'])
+Advanced features
+^^^^^^^^^^^^^^^^^^^^
- @app.route('/')
- @app.route('/')
- def serve_static_file(static_file='index.html'):
- return send_from_directory(STATIC_PATH, static_file)
+The PyWebIO application can be integrated into an existing Python web project, the PyWebIO application and the web
+project share a web framework. Refer to :ref:`Advanced Topic: Integration with Web Framework `
+for more information.
- # 事件循环线程
- threading.Thread(target=run_event_loop, daemon=True).start()
- app.run(host='localhost', port='80')
+PyWebIO also provides support for coroutine-based sessions. Refer to :ref:`Advanced Topic: Coroutine-based session `
+for more information.
-最后,使用PyWebIO编写的协程函数不支持Script模式,总是需要使用 ``start_server`` 来启动一个服务或者集成进Web框架来调用。
+If you try to bundles your PyWebIO application into a stand-alone executable file, to make users can run the application
+without installing a Python interpreter or any modules, you might want to refer to :ref:`Libraries support: Build stand-alone App `
+If you want to make some data visualization in your PyWebIO application, you can't miss :ref:`Libraries support: Data visualization `
Last but not least
---------------------
-以上就是PyWebIO的全部功能了,你可以继续阅读接下来的文档,或者立即开始PyWebIO应用的编写了。
+This is basically all features of PyWebIO, you can continue to read the rest of the documents, or start writing your PyWebIO applications now.
-最后再提供一条建议,当你在使用PyWebIO遇到设计上的问题时,可以问一下自己:如果在是在终端程序中我会怎么做?
-如果你已经有答案了,那么在PyWebIO中一样可以使用这样的方式完成。如果问题依然存在或者觉得解决方案不够好,
-你可以考虑使用 `put_buttons() ` 提供的回调机制。
+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?
+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 :ref:`callback mechanism ` or :doc:`pin <./pin>` module.
-好了,Have fun with PyWebIO!
\ No newline at end of file
+OK, Have fun with PyWebIO!
diff --git a/docs/index.rst b/docs/index.rst
index 33cedc8b..8fa9200a 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,99 +1,110 @@
PyWebIO
==========
-PyWebIO提供了一系列命令式的交互函数来在浏览器上获取用户输入和进行输出,将浏览器变成了一个“富文本终端”,可以用于构建简单的Web应用或基于浏览器的GUI应用。
-使用PyWebIO,开发者能像编写终端脚本一样(基于input和print进行交互)来编写应用,无需具备HTML和JS的相关知识;
-PyWebIO还可以方便地整合进现有的Web服务。非常适合快速构建对UI要求不高的应用。
+PyWebIO provides a diverse set of imperative functions to obtain user input and output content on the browser,
+turning the browser into a "rich text terminal", and can be used to build simple web applications or browser-based
+GUI applications. Using PyWebIO, developers can write applications just like writing terminal scripts
+(interaction based on input and print function), without the need to have knowledge of HTML and JS.
+PyWebIO is ideal for quickly building interactive applications that don't require a complicated user interface.
-
-特性
+Features
------------
-- 使用同步而不是基于回调的方式获取输入,代码编写逻辑更自然
-- 非声明式布局,布局方式简单高效
-- 代码侵入性小,旧脚本代码仅需修改输入输出逻辑便可改造为Web服务
-- 支持整合到现有的Web服务,目前支持与Flask、Django、Tornado、aiohttp框架集成
-- 同时支持基于线程的执行模型和基于协程的执行模型
-- 支持结合第三方库实现数据可视化
+- Use synchronization instead of callback-based method to get input
+- Non-declarative layout, simple and efficient
+- Less intrusive: old script code can be transformed into a Web service only by modifying the input and output operation
+- Support integration into existing web services, currently supports Flask, Django, Tornado, aiohttp and FastAPI(Starlette) framework
+- Support for ``asyncio`` and coroutine
+- Support data visualization with third-party libraries
-Install
-------------
+Installation
+--------------
-稳定版安装::
+Stable version::
pip3 install -U pywebio
-开发版安装::
+Development version::
- pip3 install -U --force-reinstall https://code.aliyun.com/wang0618/pywebio/repository/archive.zip
+ pip3 install -U https://github.com/pywebio/PyWebIO/archive/dev-release.zip
-**系统要求**: PyWebIO要求 Python 版本在 3.5.2 及以上
+**Prerequisites**: PyWebIO requires Python 3.5.2 or newer
.. _hello_word:
Hello, world
--------------
-这是一个使用PyWebIO计算 `BMI指数 `_ 的脚本::
+Here is a simple PyWebIO script to calculate the `BMI `_ ::
# A simple script to calculate BMI
from pywebio.input import input, FLOAT
from pywebio.output import put_text
def bmi():
- height = input("请输入你的身高(cm):", type=FLOAT)
- weight = input("请输入你的体重(kg):", type=FLOAT)
+ height = input("Input your height(cm):", type=FLOAT)
+ weight = input("Input your weight(kg):", type=FLOAT)
BMI = weight / (height / 100) ** 2
- top_status = [(14.9, '极瘦'), (18.4, '偏瘦'),
- (22.9, '正常'), (27.5, '过重'),
- (40.0, '肥胖'), (float('inf'), '非常肥胖')]
+ top_status = [(16, 'Severely underweight'), (18.5, 'Underweight'),
+ (25, 'Normal'), (30, 'Overweight'),
+ (35, 'Moderately obese'), (float('inf'), 'Severely obese')]
for top, status in top_status:
if BMI <= top:
- put_text('你的 BMI 值: %.1f,身体状态:%s' % (BMI, status))
+ put_text('Your BMI: %.1f. Category: %s' % (BMI, status))
break
if __name__ == '__main__':
bmi()
-如果没有使用PyWebIO,这只是一个非常简单的脚本,而通过使用PyWebIO提供的输入输出函数,你可以在浏览器中与代码进行交互:
+This is just a very simple script if you ignore PyWebIO, but after using the input and output functions provided by PyWebIO,
+you can interact with the code in the browser:
.. image:: /assets/demo.*
:width: 450px
:align: center
-将上面代码最后一行对 ``bmi()`` 的直接调用改为使用 `pywebio.start_server(bmi, port=80) ` 便可以在80端口提供 ``bmi()`` 服务( :demo_host:`在线Demo ?pywebio_api=bmi>` )。
+In the last line of the above code, changing the function call ``bmi()`` to
+`pywebio.start_server(bmi, port=80) ` will start a bmi web service on port 80
+( :demo_host:`online Demo ` ).
-将 ``bmi()`` 服务整合到现有的Web框架请参考 :ref:`与Web框架集成 `
+If you want to integrate the ``bmi()`` service into an existing web framework, you can visit
+:ref:`Integration with a web framework ` section of this document.
Documentation
-------------
-这个文档同时也提供 `PDF 和 Epub 格式 `_.
+This documentation is also available in `PDF and Epub formats `_.
.. toctree::
:maxdepth: 2
- :caption: 使用手册
+ :caption: Manual
guide
input
output
session
platform
+ pin
+ advanced
libraries_support
- demos
- misc
.. toctree::
:titlesonly:
- FAQ
+ cookbook
releases
.. toctree::
:maxdepth: 2
- :caption: 实现文档
+ :caption: Battery
+
+ battery
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Implement Doc
spec
diff --git a/docs/input.rst b/docs/input.rst
index ef9f0501..f540e026 100644
--- a/docs/input.rst
+++ b/docs/input.rst
@@ -1,4 +1,4 @@
-``pywebio.input`` --- 输入模块
+``pywebio.input`` --- Get input from web browser
====================================================
.. automodule:: pywebio.input
diff --git a/docs/libraries_support.rst b/docs/libraries_support.rst
index 18f7139b..636cc0d9 100644
--- a/docs/libraries_support.rst
+++ b/docs/libraries_support.rst
@@ -1,17 +1,52 @@
-第三方库生态
-==============
+Libraries support
+======================
+
+.. _stand_alone_app:
+
+Build stand-alone App
+----------------------
+`PyInstaller `_ bundles a Python application and all its dependencies into a folder or executable. The user can run the packaged app without installing a Python interpreter or any modules.
+
+You can use PyInstaller to packages PyWebIO application into a stand-alone executable or folder:
+
+1. Create a pyinstaller spec (specification) file::
+
+ pyi-makespec app.py
+
+ You need replace ``app.py`` to your PyWebIO application file name.
+
+2. Only for PyWebIO before v1.8: Edit the spec file, change the ``datas`` parameter of ``Analysis``::
+
+ from pywebio.utils import pyinstaller_datas
+
+ a = Analysis(
+ ...
+ datas=pyinstaller_datas(),
+ ...
+
+3. Build the application by passing the spec file to the pyinstaller command::
+
+ pyinstaller app.spec
+
+
+If you want to create a one-file bundled executable, you need pass ``--onefile`` option in first step.
+
+For more information, please visit: https://pyinstaller.readthedocs.io/en/stable/spec-files.html
+
.. _visualization:
-数据可视化
--------------
-PyWebIO支持使用第三方库进行数据可视化
+Data visualization
+--------------------
+PyWebIO supports for data visualization with the third-party libraries.
Bokeh
^^^^^^^^^^^^^^^^^^^^^^
-`Bokeh `_ 是一个支持创建实时交互的数据可视化库。
-在 PyWebIO 会话中调用 ``bokeh.io.output_notebook(notebook_type='pywebio')`` 来设置Bokeh输出到PyWebIO::
+`Bokeh `_ is an interactive visualization library for modern web browsers. It provides elegant, concise construction of versatile graphics, and affords high-performance interactivity over large or streaming datasets.
+
+You can use ``bokeh.io.output_notebook(notebook_type='pywebio')`` in the PyWebIO session to setup Bokeh environment.
+Then you can use ``bokeh.io.show()`` to output a boken chart::
from bokeh.io import output_notebook
from bokeh.io import show
@@ -21,54 +56,69 @@ Bokeh
...
show(fig)
-相应demo见 :charts_demo_host:`bokeh demo ?app=bokeh>`
+See related demo on :charts_demo_host:`bokeh demo ?app=bokeh>`
+
+In addition to creating ordinary charts, Bokeh can also build the Bokeh applications by starting the `Bokeh server `_. The purpose of the Bokeh server is to make it easy for Python users to create interactive web applications that can connect front-end UI events to real, running Python code.
-除了创建普通图表,Bokeh还可以通过启动Bokeh server来显示Bokeh app,Bokeh app支持向图表的添加按钮、输入框等交互组件,并向组件注册Python回调,从而创建可以与Python代码交互的图表。
+In PyWebIO, you can also use ``bokeh.io.show()`` to display a Bokeh App. For the example, see `bokeh_app.py `_.
-在PyWebIO中,你也可以使用 ``bokeh.io.show()`` 来显示一个Bokeh App,代码示例见 `bokeh_app.py `_。
+.. note:: Bokeh App currently is only available in the default Tornado backend
-.. image:: https://cdn.jsdelivr.net/gh/wang0618/pywebio-chart-gallery@master/assets/bokeh.png
+.. image:: https://fastly.jsdelivr.net/gh/wang0618/pywebio-chart-gallery@master/assets/bokeh.png
pyecharts
^^^^^^^^^^^^^^^^^^^^^^
-`pyecharts `_ 是一个使用Python创建 `Echarts `_ 可视化图表的库。
-在 PyWebIO 中使用 `put_html() ` 可以输出 pyecharts 库创建的图表::
+`pyecharts `_ is a python plotting library which uses `Echarts `_ as underlying implementation.
- # chart 为 pyecharts 的图表实例
+In PyWebIO, you can use the following code to output the pyecharts chart instance::
+
+ # `chart` is pyecharts chart instance
pywebio.output.put_html(chart.render_notebook())
-相应demo见 :charts_demo_host:`pyecharts demo ?app=pyecharts>`
+See related demo on :charts_demo_host:`pyecharts demo ?app=pyecharts>`
.. only:: not latex
- .. image:: https://cdn.jsdelivr.net/gh/wang0618/pywebio-chart-gallery@master/assets/pyecharts.gif
+ .. image:: https://fastly.jsdelivr.net/gh/wang0618/pywebio-chart-gallery@master/assets/pyecharts.gif
plotly
^^^^^^^^^^^^^^^^^^^^^^
-`plotly.py `_ 是一个非常流行的Python数据可视化库,可以生成高质量的交互式图表。
-PyWebIO 支持输出使用 plotly 库创建的图表。使用方式为在PyWebIO会话中调用::
+`plotly.py `_ is an interactive, open-source, and browser-based graphing library for Python.
+
+In PyWebIO, you can use the following code to output the plotly chart instance::
- # fig 为 plotly 的图表实例
+ # `fig` is plotly chart instance
html = fig.to_html(include_plotlyjs="require", full_html=False)
pywebio.output.put_html(html)
-相应demo见 :charts_demo_host:`plotly demo ?app=plotly>`
+See related demo on :charts_demo_host:`plotly demo ?app=plotly>`
+
+.. image:: https://fastly.jsdelivr.net/gh/wang0618/pywebio-chart-gallery@master/assets/plotly.png
+
+pyg2plot
+^^^^^^^^^^^^^^^^^^^^^^
+
+`pyg2plot `_ is a python plotting library which uses `G2Plot `_ as underlying implementation.
+
+In PyWebIO, you can use the following code to output the pyg2plot chart instance::
+
+ # `chart` is pyg2plot chart instance
+ pywebio.output.put_html(chart.render_notebook())
-.. image:: https://cdn.jsdelivr.net/gh/wang0618/pywebio-chart-gallery@master/assets/plotly.png
+See related demo on :charts_demo_host:`plotly demo ?app=pyg2plot>`
cutecharts.py
^^^^^^^^^^^^^^^^^^^^^^
-`cutecharts.py `_ 是一个可以创建具有卡通风格的可视化图表的python库。
-底层使用了 `chart.xkcd `_ Javascript库。
+`cutecharts.py `_ is a hand drawing style charts library for Python which uses `chart.xkcd `_ as underlying implementation.
-在 PyWebIO 中使用 `put_html() ` 可以输出 cutecharts.py 库创建的图表::
+In PyWebIO, you can use the following code to output the cutecharts.py chart instance::
- # chart 为 cutecharts 的图表实例
+ # `chart` is cutecharts chart instance
pywebio.output.put_html(chart.render_notebook())
-相应demo见 :charts_demo_host:`cutecharts demo ?app=cutecharts>`
+See related demo on :charts_demo_host:`cutecharts demo ?app=cutecharts>`
-.. image:: https://cdn.jsdelivr.net/gh/wang0618/pywebio-chart-gallery@master/assets/cutecharts.png
+.. image:: https://fastly.jsdelivr.net/gh/wang0618/pywebio-chart-gallery@master/assets/cutecharts.png
diff --git a/docs/locales/fa/LC_MESSAGES/FAQ.po b/docs/locales/fa/LC_MESSAGES/FAQ.po
new file mode 100644
index 00000000..f99bd65c
--- /dev/null
+++ b/docs/locales/fa/LC_MESSAGES/FAQ.po
@@ -0,0 +1,71 @@
+# Copyright (C) Weimin Wang
+# This file is distributed under the same license as the PyWebIO package.
+#
+# FIRST AUTHOR , 2022.
+# Pikhosh , 2022.
+msgid ""
+msgstr ""
+"Project-Id-Version: PyWebIO 1.5.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2022-02-26 16:35+0330\n"
+"PO-Revision-Date: 2022-02-26 20:27+0330\n"
+"Last-Translator: Pikhosh \n"
+"Language-Team: Persian <>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+"Language: fa\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Lokalize 21.12.2\n"
+
+#: ../../FAQ.rst:2
+msgid "FAQ"
+msgstr "سوالات متداول"
+
+#: ../../FAQ.rst:8
+msgid ""
+"How to make the input form not disappear after submission, and can "
+"continue to receive input?"
+msgstr ""
+"چجوری فرم ورودی رو طوری کنیم که بعد از ارسال ناپدید نشه، و بتونه "
+"به دریافت ورودی ادامه بده؟"
+
+#: ../../FAQ.rst:10
+msgid ""
+"You can consider the :doc:`pin <./pin>` module. It achieves persistent "
+"input by pinning input widgets to the page."
+msgstr ""
+"شما می توانید ماژول :doc:`pin <./pin>` را در نظر بگیرید. اون به ورودی "
+"مداوم با پین کردن ویجت های ورودی به صفحه دست پیدا می کند."
+
+#: ../../FAQ.rst:14
+msgid "How to output an input widget such as a search bar?"
+msgstr "چجوری یک ویجت ورودی رو به عنوان خروجی نشون بدم مثل یه نوار جست جو؟"
+
+#: ../../FAQ.rst:16
+msgid "You can consider the :doc:`pin <./pin>` module."
+msgstr "شما می توانید ماژول :doc:`pin <./pin>` را در نظر بگیرید."
+
+#: ../../FAQ.rst:20
+msgid "Why the callback of ``put_buttons()`` does not work?"
+msgstr "چرا فراخوانی ``put_buttons()`` کار نمی کنه؟"
+
+#: ../../FAQ.rst:22
+msgid ""
+"You might use the old version of PyWebIO, upgrade it to the latest "
+"version or see `the old document "
+"`_"
+msgstr ""
+"شما ممکن است از نسخه قدیمی PyWebIO استفاده کنید، به آخرین "
+"نسخه ارتقا اش دهید یا `the old document "
+"`_را ببینید"
+
+#: ../../FAQ.rst:25
+msgid "Why I cannot download the file using ``put_file()``?"
+msgstr "چرا من نمی تونم با استفاده از ``put_file()`` فایل رو دانلود کنم؟"
+
+#: ../../FAQ.rst:27
+msgid "The reason is the same as above."
+msgstr "دلیل مشابه بالاست."
+
diff --git a/docs/locales/fa/LC_MESSAGES/advanced.po b/docs/locales/fa/LC_MESSAGES/advanced.po
new file mode 100644
index 00000000..1e10a02c
--- /dev/null
+++ b/docs/locales/fa/LC_MESSAGES/advanced.po
@@ -0,0 +1,664 @@
+# Copyright (C) Weimin Wang
+# This file is distributed under the same license as the PyWebIO package.
+#
+# FIRST AUTHOR , 2022.
+# Pikhosh , 2022.
+msgid ""
+msgstr ""
+"Project-Id-Version: PyWebIO 1.5.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2022-02-26 16:35+0330\n"
+"PO-Revision-Date: 2022-02-28 12:30+0330\n"
+"Last-Translator: Pikhosh \n"
+"Language-Team: Persian <>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+"Language: fa\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Lokalize 21.12.2\n"
+
+#: ../../advanced.rst:2
+msgid "Advanced topic"
+msgstr "موضوع پیشرفته"
+
+#: ../../advanced.rst:4
+msgid "This section will introduce the advanced features of PyWebIO."
+msgstr ""
+
+#: ../../advanced.rst:10
+msgid "Start multiple applications with start_server()"
+msgstr ""
+
+#: ../../advanced.rst:12
+msgid ""
+"`start_server() ` accepts a "
+"function as PyWebIO application. In addition, `start_server() "
+"` also accepts a list of "
+"application function or a dictionary of it to start multiple "
+"applications. You can use `pywebio.session.go_app() "
+"` or `put_link() ` to "
+"jump between application::"
+msgstr ""
+
+#: ../../advanced.rst:17
+msgid ""
+"def task_1():\n"
+" put_text('task_1')\n"
+" put_buttons(['Go task 2'], [lambda: go_app('task_2')])\n"
+"\n"
+"def task_2():\n"
+" put_text('task_2')\n"
+" put_buttons(['Go task 1'], [lambda: go_app('task_1')])\n"
+"\n"
+"def index():\n"
+" put_link('Go task 1', app='task_1') # Use `app` parameter to specify"
+" the task name\n"
+" put_link('Go task 2', app='task_2')\n"
+"\n"
+"# equal to `start_server({'index': index, 'task_1': task_1, 'task_2': "
+"task_2})`\n"
+"start_server([index, task_1, task_2])"
+msgstr ""
+
+#: ../../advanced.rst:32
+msgid ""
+"When the first parameter of `start_server() "
+"` is a dictionary, whose key is "
+"application name and value is application function. When it is a list, "
+"PyWebIO will use function name as application name."
+msgstr ""
+
+#: ../../advanced.rst:35
+msgid ""
+"You can select which application to access through the ``app`` URL "
+"parameter (for example, visit ``http://host:port/?app=foo`` to access the"
+" ``foo`` application), By default, the ``index`` application is opened "
+"when no ``app`` URL parameter provided. When the ``index`` application "
+"doesn't exist, PyWebIO will provide a default index application."
+msgstr ""
+
+#: ../../advanced.rst:44
+msgid "Integration with web framework"
+msgstr ""
+
+#: ../../advanced.rst:46
+msgid ""
+"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."
+msgstr ""
+
+#: ../../advanced.rst:50
+msgid "The integration methods of those web frameworks are as follows:"
+msgstr ""
+
+#: ../../advanced.rst:54
+msgid "Tornado"
+msgstr ""
+
+#: ../../advanced.rst:58
+msgid "**Tornado**"
+msgstr ""
+
+#: ../../advanced.rst:60
+msgid ""
+"Use `pywebio.platform.tornado.webio_handler()` to get the "
+"`WebSocketHandler "
+"`_"
+" class for running PyWebIO applications in Tornado::"
+msgstr ""
+
+#: ../../advanced.rst:64
+msgid ""
+"import tornado.ioloop\n"
+"import tornado.web\n"
+"from pywebio.platform.tornado import webio_handler\n"
+"\n"
+"class MainHandler(tornado.web.RequestHandler):\n"
+" def get(self):\n"
+" self.write(\"Hello, world\")\n"
+"\n"
+"if __name__ == \"__main__\":\n"
+" application = tornado.web.Application([\n"
+" (r\"/\", MainHandler),\n"
+" (r\"/tool\", webio_handler(task_func)), # `task_func` is PyWebIO"
+" task function\n"
+" ])\n"
+" application.listen(port=80, address='localhost')\n"
+" tornado.ioloop.IOLoop.current().start()"
+msgstr ""
+
+#: ../../advanced.rst:81
+msgid ""
+"In above code, we add a routing rule to bind the ``WebSocketHandler`` of "
+"the PyWebIO application to the ``/tool`` path. After starting the Tornado"
+" server, you can visit ``http://localhost/tool`` to open the PyWebIO "
+"application."
+msgstr ""
+
+#: ../../advanced.rst:86
+msgid ""
+"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 ` is an example of Nginx "
+"WebSocket configuration."
+msgstr ""
+
+#: ../../advanced.rst:90
+msgid "Flask"
+msgstr ""
+
+#: ../../advanced.rst:94
+msgid "**Flask**"
+msgstr ""
+
+#: ../../advanced.rst:96
+msgid ""
+"Use `pywebio.platform.flask.webio_view()` to get the view function for "
+"running PyWebIO applications in Flask::"
+msgstr ""
+
+#: ../../advanced.rst:98
+msgid ""
+"from pywebio.platform.flask import webio_view\n"
+"from flask import Flask\n"
+"\n"
+"app = Flask(__name__)\n"
+"\n"
+"# `task_func` is PyWebIO task function\n"
+"app.add_url_rule('/tool', 'webio_view', webio_view(task_func),\n"
+" methods=['GET', 'POST', 'OPTIONS']) # need GET,POST and "
+"OPTIONS methods\n"
+"\n"
+"app.run(host='localhost', port=80)"
+msgstr ""
+
+#: ../../advanced.rst:110
+msgid ""
+"In above code, we add a routing rule to bind the view function of the "
+"PyWebIO application to the ``/tool`` path. After starting the Flask "
+"application, visit ``http://localhost/tool`` to open the PyWebIO "
+"application."
+msgstr ""
+
+#: ../../advanced.rst:113
+msgid "Django"
+msgstr ""
+
+#: ../../advanced.rst:117
+msgid "**Django**"
+msgstr ""
+
+#: ../../advanced.rst:119
+msgid ""
+"Use `pywebio.platform.django.webio_view()` to get the view function for "
+"running PyWebIO applications in Django::"
+msgstr ""
+
+#: ../../advanced.rst:121
+msgid ""
+"# urls.py\n"
+"\n"
+"from django.urls import path\n"
+"from pywebio.platform.django import webio_view\n"
+"\n"
+"# `task_func` is PyWebIO task function\n"
+"webio_view_func = webio_view(task_func)\n"
+"\n"
+"urlpatterns = [\n"
+" path(r\"tool\", webio_view_func),\n"
+"]"
+msgstr ""
+
+#: ../../advanced.rst:134
+msgid ""
+"In above code, we add a routing rule to bind the view function of the "
+"PyWebIO application to the ``/tool`` path. After starting the Django "
+"server, visit ``http://localhost/tool`` to open the PyWebIO application"
+msgstr ""
+
+#: ../../advanced.rst:137
+msgid "aiohttp"
+msgstr ""
+
+#: ../../advanced.rst:141
+msgid "**aiohttp**"
+msgstr ""
+
+#: ../../advanced.rst:143
+msgid ""
+"Use `pywebio.platform.aiohttp.webio_handler()` to get the `Request "
+"Handler `_ coroutine for running PyWebIO applications in aiohttp::"
+msgstr ""
+
+#: ../../advanced.rst:147
+msgid ""
+"from aiohttp import web\n"
+"from pywebio.platform.aiohttp import webio_handler\n"
+"\n"
+"app = web.Application()\n"
+"# `task_func` is PyWebIO task function\n"
+"app.add_routes([web.get('/tool', webio_handler(task_func))])\n"
+"\n"
+"web.run_app(app, host='localhost', port=80)"
+msgstr ""
+
+#: ../../advanced.rst:156
+msgid ""
+"After starting the aiohttp server, visit ``http://localhost/tool`` to "
+"open the PyWebIO application"
+msgstr ""
+
+#: ../../advanced.rst:160
+msgid ""
+"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 ` is an example of Nginx "
+"WebSocket configuration."
+msgstr ""
+
+#: ../../advanced.rst:165
+msgid "FastAPI/Starlette"
+msgstr ""
+
+#: ../../advanced.rst:169
+msgid "**FastAPI/Starlette**"
+msgstr ""
+
+#: ../../advanced.rst:171
+msgid ""
+"Use `pywebio.platform.fastapi.webio_routes()` to get the "
+"FastAPI/Starlette routes for running PyWebIO applications. You can mount "
+"the routes to your FastAPI/Starlette app."
+msgstr ""
+
+#: ../../advanced.rst:174
+msgid "FastAPI::"
+msgstr ""
+
+#: ../../advanced.rst:176
+msgid ""
+"from fastapi import FastAPI\n"
+"from pywebio.platform.fastapi import webio_routes\n"
+"\n"
+"app = FastAPI()\n"
+"\n"
+"@app.get(\"/app\")\n"
+"def read_main():\n"
+" return {\"message\": \"Hello World from main app\"}\n"
+"\n"
+"# `task_func` is PyWebIO task function\n"
+"app.mount(\"/tool\", FastAPI(routes=webio_routes(task_func)))"
+msgstr ""
+
+#: ../../advanced.rst:188
+msgid "Starlette::"
+msgstr ""
+
+#: ../../advanced.rst:190
+msgid ""
+"from starlette.applications import Starlette\n"
+"from starlette.responses import JSONResponse\n"
+"from starlette.routing import Route, Mount\n"
+"from pywebio.platform.fastapi import webio_routes\n"
+"\n"
+"async def homepage(request):\n"
+" return JSONResponse({'hello': 'world'})\n"
+"\n"
+"app = Starlette(routes=[\n"
+" Route('/', homepage),\n"
+" Mount('/tool', routes=webio_routes(task_func)) # `task_func` is "
+"PyWebIO task function\n"
+"])"
+msgstr ""
+
+#: ../../advanced.rst:203
+msgid ""
+"After starting the server by using ``uvicorn :app`` , visit "
+"``http://localhost:8000/tool/`` to open the PyWebIO application"
+msgstr ""
+
+#: ../../advanced.rst:205
+msgid ""
+"See also: `FastAPI doc `_ , `Starlette doc `_"
+msgstr ""
+
+#: ../../advanced.rst:209
+msgid ""
+"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 ` is an example of Nginx "
+"WebSocket configuration."
+msgstr ""
+
+#: ../../advanced.rst:217
+msgid "Notes"
+msgstr ""
+
+#: ../../advanced.rst:218
+msgid "**Deployment in production**"
+msgstr ""
+
+#: ../../advanced.rst:220
+msgid ""
+"In your production system, you may want to deploy the web applications "
+"with some WSGI/ASGI servers such as uWSGI, Gunicorn, and Uvicorn. 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, the request may be dispatched to a process that does not"
+" hold the session to which the request belongs. So you can only start one"
+" worker to handle requests when using Flask or Django backend."
+msgstr ""
+
+#: ../../advanced.rst:225
+msgid ""
+"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. Those backends use the WebSocket protocol to "
+"communicate with the browser in PyWebIO, so there is no the issue as "
+"described above."
+msgstr ""
+
+#: ../../advanced.rst:229
+msgid "**Static resources Hosting**"
+msgstr ""
+
+#: ../../advanced.rst:231
+msgid ""
+"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``."
+msgstr ""
+
+#: ../../advanced.rst:235
+msgid ""
+"When setting ``cdn=False`` , you need to host the static resources in the"
+" same directory as the PyWebIO application. In addition, you can also "
+"pass a string to ``cdn`` parameter to directly set the URL of PyWebIO "
+"static resources directory."
+msgstr ""
+
+#: ../../advanced.rst:238
+msgid ""
+"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."
+msgstr ""
+
+#: ../../advanced.rst:243
+msgid ""
+"``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."
+msgstr ""
+
+#: ../../advanced.rst:250
+msgid "Coroutine-based session"
+msgstr ""
+
+#: ../../advanced.rst:251
+msgid ""
+"In most cases, you don’t need the coroutine-based session. All functions "
+"or methods in PyWebIO that are only used for coroutine sessions are "
+"specifically noted in the document."
+msgstr ""
+
+#: ../../advanced.rst:254
+msgid ""
+"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."
+msgstr ""
+
+#: ../../advanced.rst:258
+msgid ""
+"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."
+msgstr ""
+
+#: ../../advanced.rst:264
+msgid "Using coroutine session"
+msgstr ""
+
+#: ../../advanced.rst:266
+msgid ""
+"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:"
+msgstr ""
+
+#: ../../advanced.rst:269
+#, python-format
+msgid ""
+" from pywebio.input import *\n"
+" from pywebio.output import *\n"
+" from pywebio import start_server\n"
+"\n"
+" async def say_hello():\n"
+" name = await input(\"what's your name?\")\n"
+" put_text('Hello, %s' % name)\n"
+"\n"
+" start_server(say_hello, auto_open_webbrowser=True)"
+msgstr ""
+
+#: ../../advanced.rst:283
+msgid ""
+"In the coroutine task function, you can also use ``await`` to call other "
+"coroutines or (`awaitable objects `_) in the standard library "
+"`asyncio `_:"
+msgstr ""
+
+#: ../../advanced.rst:287
+msgid ""
+" import asyncio\n"
+" from pywebio import start_server\n"
+"\n"
+" async def hello_word():\n"
+" put_text('Hello ...')\n"
+" await asyncio.sleep(1) # await awaitable objects in asyncio\n"
+" put_text('... World!')\n"
+"\n"
+" async def main():\n"
+" await hello_word() # await coroutine\n"
+" put_text('Bye, bye')\n"
+"\n"
+" start_server(main, auto_open_webbrowser=True)"
+msgstr ""
+
+#: ../../advanced.rst:306
+msgid ""
+"In coroutine-based session, all input functions defined in the "
+":doc:`pywebio.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."
+msgstr ""
+
+#: ../../advanced.rst:309
+msgid ""
+"Other functions that need to use ``await`` syntax in the coroutine "
+"session are:"
+msgstr ""
+
+#: ../../advanced.rst:311
+msgid ""
+"`pywebio.session.run_asyncio_coroutine(coro_obj) "
+"`"
+msgstr ""
+
+#: ../../advanced.rst:312
+msgid "`pywebio.session.eval_js(expression) `"
+msgstr ""
+
+#: ../../advanced.rst:316
+msgid ""
+"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."
+msgstr ""
+
+#: ../../advanced.rst:319
+msgid ""
+"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** ::"
+msgstr ""
+
+#: ../../advanced.rst:322
+msgid ""
+"await asyncio.shield(pywebio.input())\n"
+"await asyncio.gather(asyncio.sleep(1), pywebio.session.eval_js('1+1'))\n"
+"task = asyncio.create_task(pywebio.input())"
+msgstr ""
+
+#: ../../advanced.rst:329
+msgid "Concurrency in coroutine-based sessions"
+msgstr ""
+
+#: ../../advanced.rst:331
+msgid ""
+"In coroutine-based session, you can start new thread, but you cannot call"
+" PyWebIO interactive functions in it (`register_thread() "
+"` is not available in coroutine "
+"session). But you can use `run_async(coro) ` "
+"to execute a coroutine object asynchronously, and PyWebIO interactive "
+"functions can be used in the new coroutine:"
+msgstr ""
+
+#: ../../advanced.rst:336
+msgid ""
+" from pywebio import start_server\n"
+" from pywebio.session import run_async\n"
+"\n"
+" async def counter(n):\n"
+" for i in range(n):\n"
+" put_text(i)\n"
+" await asyncio.sleep(1)\n"
+"\n"
+" async def main():\n"
+" run_async(counter(10))\n"
+" put_text('Main coroutine function exited.')\n"
+"\n"
+"\n"
+" start_server(main, auto_open_webbrowser=True)"
+msgstr ""
+
+#: ../../advanced.rst:355
+msgid ""
+"`run_async(coro) ` returns a `TaskHandler "
+"`, which can be used to query"
+" the running status of the coroutine or close the coroutine."
+msgstr ""
+
+#: ../../advanced.rst:359
+msgid "Close of session"
+msgstr ""
+
+#: ../../advanced.rst:361
+msgid ""
+"Similar to thread-based session, when user close the browser page, the "
+"session will be closed."
+msgstr ""
+
+#: ../../advanced.rst:363
+msgid ""
+"After the browser page closed, PyWebIO input function calls that have not"
+" yet returned in the current session will cause `SessionClosedException "
+"`, and subsequent calls to "
+"PyWebIO interactive functions will cause `SessionNotFoundException "
+"` or `SessionClosedException"
+" `."
+msgstr ""
+
+#: ../../advanced.rst:368
+msgid ""
+"`defer_call(func) ` also available in "
+"coroutine session."
+msgstr ""
+
+#: ../../advanced.rst:373
+msgid "Integration with Web Framework"
+msgstr ""
+
+#: ../../advanced.rst:375
+msgid ""
+"The PyWebIO application that using coroutine-based session can also be "
+"integrated to the web framework."
+msgstr ""
+
+#: ../../advanced.rst:377
+msgid ""
+"However, there are some limitations when using coroutine-based sessions "
+"to integrate into Flask or Django:"
+msgstr ""
+
+#: ../../advanced.rst:379
+msgid ""
+"First, when ``await`` the coroutine objects/awaitable objects in the "
+"``asyncio`` module, you need to use `run_asyncio_coroutine() "
+"` to wrap the coroutine object."
+msgstr ""
+
+#: ../../advanced.rst:382
+msgid ""
+"Secondly, you need to start a new thread to run the event loop before "
+"starting a Flask/Django server."
+msgstr ""
+
+#: ../../advanced.rst:384
+msgid "Example of coroutine-based session integration into Flask:"
+msgstr ""
+
+#: ../../advanced.rst:386
+msgid ""
+" import asyncio\n"
+" import threading\n"
+" from flask import Flask, send_from_directory\n"
+" from pywebio import STATIC_PATH\n"
+" from pywebio.output import *\n"
+" from pywebio.platform.flask import webio_view\n"
+" from pywebio.platform import run_event_loop\n"
+" from pywebio.session import run_asyncio_coroutine\n"
+"\n"
+" async def hello_word():\n"
+" put_text('Hello ...')\n"
+" await run_asyncio_coroutine(asyncio.sleep(1)) # can't just \"await "
+"asyncio.sleep(1)\"\n"
+" put_text('... World!')\n"
+"\n"
+" app = Flask(__name__)\n"
+" app.add_url_rule('/hello', 'webio_view', webio_view(hello_word),\n"
+" methods=['GET', 'POST', 'OPTIONS'])\n"
+"\n"
+" # thread to run event loop\n"
+" threading.Thread(target=run_event_loop, daemon=True).start()\n"
+" app.run(host='localhost', port=80)"
+msgstr ""
+
+#: ../../advanced.rst:411
+msgid ""
+"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."
+msgstr ""
+
diff --git a/docs/locales/fa/LC_MESSAGES/arch.po b/docs/locales/fa/LC_MESSAGES/arch.po
new file mode 100644
index 00000000..2100fdb4
--- /dev/null
+++ b/docs/locales/fa/LC_MESSAGES/arch.po
@@ -0,0 +1,509 @@
+# Copyright (C) Weimin Wang
+# This file is distributed under the same license as the PyWebIO package.
+#
+# FIRST AUTHOR , 2022.
+# Pikhosh , 2022.
+msgid ""
+msgstr ""
+"Project-Id-Version: PyWebIO 1.5.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2022-02-26 16:35+0330\n"
+"PO-Revision-Date: 2022-02-28 14:20+0330\n"
+"Last-Translator: Pikhosh \n"
+"Language-Team: Persian <>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+"Language: fa\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Lokalize 21.12.2\n"
+
+#: ../../arch.rst:2
+msgid "Architecture"
+msgstr ""
+
+#: ../../arch.rst:5
+msgid "概念"
+msgstr ""
+
+#: ../../arch.rst:7
+msgid "``Session`` 表示浏览器与程序交互产生的一次会话。PyWebIO在会话中运行 ``Task`` ,任务是"
+msgstr ""
+
+#: ../../arch.rst:9
+msgid "会话中除了起始的执行单元,也可以并发启动新的执行单元,在新的执行单元中也可以进行输入输出。"
+msgstr ""
+
+#: ../../arch.rst:11
+msgid "在用户端,相同会话中的不同的执行单元的输入是独立的,共享输出空间,但输出域的栈结构各自独立。"
+msgstr ""
+
+#: ../../arch.rst:13
+msgid ""
+"若用户正在填写一个执行单元的表单,会话中的其他执行单元也开始向用户请求输入,此时用户正在填写的表单将会隐藏, "
+"新的输入表单将会显示给用户,当用户填写完新表单并提交后,旧表单重新显示,之前在旧表单上的输入也会保留。"
+msgstr ""
+
+#: ../../arch.rst:16
+msgid "在基于线程的会话中,会话中的每个执行单元都是一个线程"
+msgstr ""
+
+#: ../../arch.rst:18
+msgid "在基于协程的会话中,会话中的每个执行单元都是一个协程"
+msgstr ""
+
+#: ../../arch.rst:20
+msgid "除了并发执行的执行单元,会话中还有事件回调函数,目前就只有按钮控件可以绑定点击事件的回调函数。"
+msgstr ""
+
+#: ../../arch.rst:23
+msgid "架构"
+msgstr ""
+
+#: ../../arch.rst:25
+msgid ""
+"会话内的每个执行单元使用唯一的task_id进行标识,由于会话内的输入需要区分执行单元,所以每个表单提交时, "
+"除了表单的内容以外,还会携带表单所在的执行单元的task_id,这样,后台会话才可以知道该将表单数据传递给哪个执行单元。"
+msgstr ""
+
+#: ../../arch.rst:31
+msgid ""
+"PyWebIO会话是由事件驱动的,这些事件来自用户在页面上的操作,比如提交表单,点击按钮,这些事件会通过http请求或websocket连接发送到后端框架。"
+msgstr ""
+
+#: ../../arch.rst:33
+msgid ""
+"后端框架维护有当前在线的Session实例,后端框架在收到用户提交的事件后,回调用相关Session实例的 "
+"``send_client_event()`` 方法将事件发送至会话;"
+msgstr ""
+
+#: ../../arch.rst:35
+msgid ""
+"一个会话内会拥有至少一个执行单元,执行单元在调用PyWebIO的输入函数后会临时挂起,当会话收到用户的输入提交后,会话便将执行单元恢复执行,并提供用户输入的"
+"值。"
+" 执行单元内,任何输入输出的调用都会转换成一些命令序列发送给会话."
+msgstr ""
+
+#: ../../arch.rst:38
+msgid ""
+"当后端框架通过HTTP与用户浏览器通信时,用户浏览器是以轮训的方式获取指令,会话会保存由执行单元生成的、还未发送到浏览器的命令序列,等待下次轮训时由后端框架"
+"取走。"
+msgstr ""
+
+#: ../../arch.rst:40
+msgid ""
+"当后端框架通过WebSocket与用户建立连接时,任何由执行单元发送到会话的命令都会立即发送到后端,并由后端通过WebSocket连接通知用户浏览器。"
+msgstr ""
+
+#: ../../arch.rst:43
+msgid "实现"
+msgstr ""
+
+#: ../../arch.rst:46
+msgid "后端与Session的交互"
+msgstr ""
+
+#: ../../arch.rst:48
+msgid "后端框架负责从Session会话中获取来自PyWebIO的指令,并发送给用户浏览器;同时后端框架接收用户提交的数据,并发送给相应的会话实例。"
+msgstr ""
+
+#: ../../arch.rst:50
+msgid ""
+"Session暴露给后端框架的方法仅有 `Session.send_client_event "
+"` 、 "
+"`Session.get_task_commands "
+"` 和 `Session.close "
+"` 。"
+msgstr ""
+
+#: ../../arch.rst:54
+msgid "基于HTTP通信的后端的实现逻辑"
+msgstr ""
+
+#: ../../arch.rst:56
+msgid "**基于HTTP的前后端通信约定**"
+msgstr ""
+
+#: ../../arch.rst:58
+msgid "前端按照固定间隔使用GET请求轮训后端接口,在请求中使用 ``webio-session-id`` HTTP头来传递会话ID。"
+msgstr ""
+
+#: ../../arch.rst:60
+msgid ""
+"会话一开始时,会话ID由后端生成并通过响应中的 ``webio-session-id`` HTTP头返回给前端,后续前端的请求都会在请求头中使用 "
+"``webio-session-id`` 字段传递会话ID。"
+msgstr ""
+
+#: ../../arch.rst:62
+msgid ""
+"前端产生的事件使用POST请求发送给后端。对于前端的每次轮训和事件提交请求,后端都会返回当前未执行的指令序列作为响应,前端收到响应后会依次执行指令。"
+msgstr ""
+
+#: ../../arch.rst:64 ../../arch.rst:93
+msgid "**代码实现**"
+msgstr ""
+
+#: ../../arch.rst:66
+msgid ""
+"以Flask后端为例,Flask后端与Session的交互都在Flask视图函数中实现,视图函数通过调用 "
+"`pywebio.platform.flask.webio_view "
+"<./_modules/pywebio/platform/flask.html#webio_view>`_ 获取, 在 `webio_view` "
+"中,先是实例化了一个 `pywebio.platform.httpbased.HttpHandler` "
+",然后声明了一个内部函数,这个内部函数就是Flask视图函数, 在视图函数内,先是实例化了一个 "
+"`pywebio.platform.flask.FlaskHttpContext` 对象,然后通过调用 "
+"``HttpHandler.handle_request(FlaskHttpContext)`` 就获得了视图的响应。"
+msgstr ""
+
+#: ../../arch.rst:70
+msgid ""
+"这其中,FlaskHttpContext 的基类为 HttpContext ,HttpContext "
+"接口各异的后端框架定义了一个统一的操作接口,用于从当前请求中获取请求相关的数据并设置请求的相应。 FlaskHttpContext "
+"为HttpContext接口的Flask实现。"
+msgstr ""
+
+#: ../../arch.rst:72
+msgid ""
+"而 HttpHandle 负责维护Session实例并实现HTTP请求与Session之间的交互,HttpHandle "
+"与后端框架相关的交互全都通过 HttpContext 操作。"
+msgstr ""
+
+#: ../../arch.rst:74
+msgid "HttpContext的生命周期为一次HTTP请求,HttpHandle的生命周期和整个后端框架的生命周期一致。"
+msgstr ""
+
+#: ../../arch.rst:76
+msgid ""
+"HttpHandler.handle_request 负责处理前端发送给后端的每一次请求,HttpHandler.handle_request "
+"的处理流程如下:"
+msgstr ""
+
+#: ../../arch.rst:78
+msgid "检测当前HTTP请求是否满足跨域设置"
+msgstr ""
+
+#: ../../arch.rst:79
+msgid ""
+"根绝当前请求的 webio-session-id 头信息找到相应的Session实例,若不存在 webio-session-id "
+"头则创建新会话并分配webio-session-id"
+msgstr ""
+
+#: ../../arch.rst:80
+msgid "若当前请求为POST事件提交请求,则将提交的数据通过 Session.send_client_event 发送给Session"
+msgstr ""
+
+#: ../../arch.rst:81
+msgid "通过调用 Session.get_task_commands 获取待执行的指令序列,并通过 HttpContext 向后端设置响应数据"
+msgstr ""
+
+#: ../../arch.rst:83
+msgid ""
+"此外,基于HTTP的会话,用户主动关闭会话时(比如关闭浏览器),后端无法立即感知,所以在HttpHandler.handle_request 中,"
+" 还会周期性地检测会话的最后活跃时间,将一段时间内不活跃的会话视为过期,所以在HttpHandler清理过期会话并调用 Session.close"
+" 释放会话内的资源。"
+msgstr ""
+
+#: ../../arch.rst:88
+msgid "基于WebSocket通信的后端的实现逻辑"
+msgstr ""
+
+#: ../../arch.rst:89
+msgid "**基于WebSocket的前后端通信约定:**"
+msgstr ""
+
+#: ../../arch.rst:91
+msgid ""
+"浏览器与后端使用一个WebSocket连接来保持一个会话,后端的指令通过JSON序列化之后的消息实时发送给前端,前端用户触发的事件数据也通过JSON序列化之"
+"后发送给后端。"
+msgstr ""
+
+#: ../../arch.rst:95
+msgid "以Tornado后端为例"
+msgstr ""
+
+#: ../../arch.rst:97
+msgid ""
+"webio_handler用于获取Tornado与前端进行通信的WebSocketHandler子类,其逻辑实现在 _webio_handler "
+"中,由于WebSocket的有状态性, "
+"WebSocketHandler子类的实现比基于HTTP通信的HttpHandler要简单许多,关键部分如下:"
+msgstr ""
+
+#: ../../arch.rst:100
+msgid ""
+"在WebSocket连接创建的时候初始化Session实例,并向Session对象注册了 "
+"on_task_command和on_session_close 回调,分别在新指令产生时和会话由执行单元关闭时由Session调用, "
+"用于实现WebSocketHandler向前端实时发送指令"
+msgstr ""
+
+#: ../../arch.rst:102
+msgid ""
+"在收到前端浏览器发送来的消息后,WebSocketHandler将收到的数据通过 Session.send_client_event "
+"发送给Session"
+msgstr ""
+
+#: ../../arch.rst:103
+msgid "在WebSocket连接关闭时,调用 Session.close 释放会话内的资源。"
+msgstr ""
+
+#: ../../arch.rst:106
+msgid "session与执行单元(输入/输出)的交互"
+msgstr ""
+
+#: ../../arch.rst:108
+msgid "会话提供给执行单元的关键接口有:"
+msgstr ""
+
+#: ../../arch.rst:110
+msgid "get_current_session : 静态方法,获取当前执行单元所属的会话实例"
+msgstr ""
+
+#: ../../arch.rst:111
+msgid "get_current_task_id : 静态方法,获取当前执行单元所属的id"
+msgstr ""
+
+#: ../../arch.rst:112
+msgid "send_task_command : 向会话发送指令"
+msgstr ""
+
+#: ../../arch.rst:113
+msgid "next_client_event : 读取来自浏览器的属于当前执行单元的下一个事件"
+msgstr ""
+
+#: ../../arch.rst:114
+msgid "register_callback : 向会话注册一个回调"
+msgstr ""
+
+#: ../../arch.rst:116
+msgid "同时,会话根据实现方式不同,还分别提供了 register_thread 和 run_async 用于启动新的执行单元。"
+msgstr ""
+
+#: ../../arch.rst:119
+msgid "**回调机制**"
+msgstr ""
+
+#: ../../arch.rst:121
+msgid ""
+"在会话中,为了能够响应用户在界面上的某些事件(比如点击了输出内容中的某个按钮),于是设计了回调机制,可以在执行单元中使用register_callback向"
+"当前会话注册回调,然后执行单元会得到一个回调ID,"
+" 执行单元再通过相关指令让浏览器输出一些可以触发的控件,并向控件绑定回调ID,当用户触发控件后,前端将带有回调ID的 :ref:`回调事件 "
+"` 发回会话,会话会在专门的执行单元中或启动新执行单元中运行回调。"
+msgstr ""
+
+#: ../../arch.rst:125
+msgid "基于线程的会话实现"
+msgstr ""
+
+#: ../../arch.rst:127
+msgid ""
+"在基于线程的会话中,每个执行单元都是一个线程,每个执行单元通过一条消息队列从会话接收来自用户的事件消息,当执行单元所需要的事件用户还没有提交时,执行单元便会"
+"挂起。"
+msgstr ""
+
+#: ../../arch.rst:129
+msgid ""
+"基于线程的会话使用线程ID作为执行单元的ID,在全局使用一个以线程id为key的字典来映射执行单元所属的会话实例,会话内不同执行单元的用户事件消息队列也通过"
+"执行单元ID进行索引。"
+msgstr ""
+
+#: ../../arch.rst:131
+msgid "使用 register_thread 启动新的执行单元时,也需要为新执行单元注册用户事件消息队列。"
+msgstr ""
+
+#: ../../arch.rst:134
+msgid "基于协程的会话实现"
+msgstr ""
+
+#: ../../arch.rst:135
+msgid ""
+"在基于协程的会话中,每个执行单元都是一个由协程包装成的任务对象(Task),当会话接收来自用户的事件消息后,便激活相应的任务对象,使得协程恢复运行。"
+msgstr ""
+
+#: ../../arch.rst:137
+msgid ""
+"由于基于协程的会话是单线程的,所以会话在激活任务对象前是通过将上下文信息保存在全局变量中来实现 get_current_session 和 "
+"get_current_task_id 方法,全局的上下文信息包含当前将要执行的会话的实例和执行单元的ID。"
+msgstr ""
+
+#: ../../arch.rst:141
+msgid "Script mode的实现"
+msgstr ""
+
+#: ../../arch.rst:142
+msgid ""
+"Script mode "
+"也是基于线程的,但由于全局仅存在一个会话,所有执行单元必定全部属于这个会话,所以也无需主动调用register_thread(thread)注册线程。"
+msgstr ""
+
+#: ../../arch.rst:144
+msgid "当PyWebIO检测到用户代码在后端Server还未启动的情况下就调用了PyWebIO交互函数时,便会启动Script mode:"
+msgstr ""
+
+#: ../../arch.rst:146
+msgid "在新线程中启动后端Server"
+msgstr ""
+
+#: ../../arch.rst:147
+msgid "启动浏览器打开后端Server运行的地址"
+msgstr ""
+
+#: ../../arch.rst:148
+msgid "在第一次与用户建立连接时初始化会话"
+msgstr ""
+
+#: ../../arch.rst:150
+msgid "script mode的会话类继承了基于线程的会话类,并修改了部分方法:"
+msgstr ""
+
+#: ../../arch.rst:152
+msgid "构造函数 : 仅允许script mode会话类被初始化一次"
+msgstr ""
+
+#: ../../arch.rst:153
+msgid "get_current_session : 直接返回全局的会话对象"
+msgstr ""
+
+#: ../../arch.rst:154
+msgid "get_current_task_id : 除了返回当前线程id,还会自动将当前线程使用 register_thread 注册到会话中"
+msgstr ""
+
+#: ../../arch.rst:157
+msgid "相关对象的文档"
+msgstr ""
+
+#: of pywebio.platform.httpbased.HttpHandler:1
+msgid "基于HTTP的后端Handler实现"
+msgstr ""
+
+#: of pywebio.platform.httpbased.HttpHandler:4
+msgid ""
+"Don't need a lock when access HttpHandler._webio_sessions, See: "
+"https://stackoverflow.com/questions/1312331/using-a-global-dictionary-"
+"with-threads-in-python"
+msgstr ""
+
+#: of pywebio.platform.httpbased.HttpHandler.handle_request_context:1
+msgid "called when every http request"
+msgstr ""
+
+#: of pywebio.session.base.Session:1
+msgid "会话对象,由Backend创建"
+msgstr ""
+
+#: of pywebio.session.base.Session:5
+msgid "属性:"
+msgstr ""
+
+#: of pywebio.session.base.Session:4
+msgid "info 表示会话信息的对象 save 会话的数据对象,提供用户在对象上保存一些会话相关数据"
+msgstr ""
+
+#: of pywebio.session.base.Session:20
+msgid "由Task在当前Session上下文中调用:"
+msgstr ""
+
+#: of pywebio.session.base.Session:8
+msgid "get_current_session get_current_task_id"
+msgstr ""
+
+#: of pywebio.session.base.Session:11
+msgid ""
+"get_scope_name pop_scope push_scope send_task_command next_client_event "
+"on_task_exception register_callback need_keep_alive"
+msgstr ""
+
+#: of pywebio.session.base.Session:20
+msgid "defer_call"
+msgstr ""
+
+#: of pywebio.session.base.Session:25
+msgid "由Backend调用:"
+msgstr ""
+
+#: of pywebio.session.base.Session:23
+msgid "send_client_event get_task_commands close"
+msgstr ""
+
+#: of pywebio.session.base.Session:28
+msgid "Task和Backend都可调用:"
+msgstr ""
+
+#: of pywebio.session.base.Session:28
+msgid "closed"
+msgstr ""
+
+#: of pywebio.session.base.Session:33
+msgid "Session是不同的后端Backend与协程交互的桥梁:"
+msgstr ""
+
+#: of pywebio.session.base.Session:31
+msgid ""
+"后端Backend在接收到用户浏览器的数据后,会通过调用 ``send_client_event`` "
+"来通知会话,进而由Session驱动协程的运行。 Task内在调用输入输出函数后,会调用 ``send_task_command`` "
+"向会话发送输入输出消息指令, Session将其保存并留给后端Backend处理。"
+msgstr ""
+
+#: of pywebio.session.base.Session.get_scope_name:1
+msgid "获取当前任务的scope栈检索scope名"
+msgstr ""
+
+#: of pywebio.session.base.Session.close
+#: pywebio.session.base.Session.defer_call
+#: pywebio.session.base.Session.get_scope_name
+msgid "Parameters"
+msgstr "پارامتر ها"
+
+#: of pywebio.session.base.Session.get_scope_name:3
+msgid "scope栈的索引"
+msgstr ""
+
+#: of pywebio.session.base.Session.get_scope_name
+#: pywebio.session.base.Session.pop_scope
+msgid "Returns"
+msgstr ""
+
+#: of pywebio.session.base.Session.get_scope_name:4
+msgid "scope名,不存在时返回 None"
+msgstr ""
+
+#: of pywebio.session.base.Session.pop_scope:1
+msgid "弹出当前scope"
+msgstr ""
+
+#: of pywebio.session.base.Session.pop_scope:3
+msgid "当前scope名"
+msgstr ""
+
+#: of pywebio.session.base.Session.push_scope:1
+msgid "进入新scope"
+msgstr ""
+
+#: of pywebio.session.base.Session.next_client_event:1
+msgid "获取来自客户端的下一个事件。阻塞调用,若在等待过程中,会话被用户关闭,则抛出SessionClosedException异常"
+msgstr ""
+
+#: of pywebio.session.base.Session.close:1
+msgid "Close current session"
+msgstr ""
+
+#: of pywebio.session.base.Session.close:3
+msgid "Don't block thread. Used in closing from backend."
+msgstr ""
+
+#: of pywebio.session.base.Session.register_callback:1
+msgid "向Session注册一个回调函数,返回回调id"
+msgstr ""
+
+#: of pywebio.session.base.Session.register_callback:3
+msgid ""
+"Session需要保证当收到前端发送的事件消息 ``{event: \"callback\",task_id: 回调id, data:...}``"
+" 时, ``callback`` 回调函数被执行, 并传入事件消息中的 ``data`` 字段值作为参数"
+msgstr ""
+
+#: of pywebio.session.base.Session.defer_call:1
+msgid "设置会话结束时调用的函数。可以用于资源清理。 在会话中可以多次调用 `defer_call()` ,会话结束后将会顺序执行设置的函数。"
+msgstr ""
+
+#: of pywebio.session.base.Session.defer_call:4
+msgid "话结束时调用的函数"
+msgstr ""
+
diff --git a/docs/locales/fa/LC_MESSAGES/cookbook.po b/docs/locales/fa/LC_MESSAGES/cookbook.po
new file mode 100644
index 00000000..30e4e634
--- /dev/null
+++ b/docs/locales/fa/LC_MESSAGES/cookbook.po
@@ -0,0 +1,455 @@
+# Copyright (C) Weimin Wang
+# This file is distributed under the same license as the PyWebIO package.
+#
+# FIRST AUTHOR , 2022.
+# Pikhosh , 2022.
+msgid ""
+msgstr ""
+"Project-Id-Version: PyWebIO 1.5.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2022-02-26 16:35+0330\n"
+"PO-Revision-Date: 2022-02-28 12:33+0330\n"
+"Last-Translator: Pikhosh \n"
+"Language-Team: Persian <>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+"Language: fa\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Lokalize 21.12.2\n"
+
+#: ../../cookbook.rst:2
+msgid "Cookbook"
+msgstr "دستور های پخت"
+
+#: ../../cookbook.rst:8
+msgid "Interaction related"
+msgstr ""
+
+#: ../../cookbook.rst:11
+msgid "Equivalent to \"Press any key to continue\""
+msgstr ""
+
+#: ../../cookbook.rst:13
+msgid ""
+"actions(buttons=[\"Continue\"])\n"
+"put_text(\"Go next\") # ..demo-only"
+msgstr ""
+
+#: ../../cookbook.rst:22
+msgid "Output pandas dataframe"
+msgstr ""
+
+#: ../../cookbook.rst:24
+msgid ""
+"import numpy as np\n"
+"import pandas as pd\n"
+"\n"
+"df = pd.DataFrame(np.random.randn(6, 4), columns=list(\"ABCD\"))\n"
+"put_html(df.to_html(border=0))"
+msgstr ""
+
+#: ../../cookbook.rst:34
+msgid ""
+"`pandas.DataFrame.to_html — pandas documentation "
+"`_"
+msgstr ""
+
+#: ../../cookbook.rst:37
+msgid "Output Matplotlib figure"
+msgstr ""
+
+#: ../../cookbook.rst:39
+msgid ""
+"Simply do not call ``matplotlib.pyplot.show``, directly save the figure "
+"to in-memory buffer and output the buffer via "
+":func:`pywebio.output.put_image`:"
+msgstr ""
+
+#: ../../cookbook.rst:42
+msgid ""
+"import matplotlib\n"
+"import matplotlib.pyplot as plt\n"
+"import io\n"
+"import pywebio\n"
+"\n"
+"matplotlib.use('agg') # required, use a non-interactive backend\n"
+"\n"
+"fig, ax = plt.subplots() # Create a figure containing a single axes.\n"
+"ax.plot([1, 2, 3, 4], [1, 4, 2, 3]) # Plot some data on the axes.\n"
+"\n"
+"buf = io.BytesIO()\n"
+"fig.savefig(buf)\n"
+"pywebio.output.put_image(buf.getvalue())"
+msgstr ""
+
+#: ../../cookbook.rst:60
+msgid ""
+"The ``matplotlib.use('agg')`` is required so that the server does not try"
+" to create (and then destroy) GUI windows that will never be seen."
+msgstr ""
+
+#: ../../cookbook.rst:63
+msgid ""
+"When using Matplotlib in a web server (multiple threads environment), "
+"pyplot may cause some conflicts in some cases, read the following "
+"articles for more information:"
+msgstr ""
+
+#: ../../cookbook.rst:66
+msgid ""
+"`Multi Threading in Python and Pyplot | by Ranjitha Korrapati | Medium "
+"`_"
+msgstr ""
+
+#: ../../cookbook.rst:68
+msgid ""
+"`Embedding in a web application server (Flask) — Matplotlib documentation"
+" "
+"`_"
+msgstr ""
+
+#: ../../cookbook.rst:72
+msgid "Blocking confirm model"
+msgstr ""
+
+#: ../../cookbook.rst:74
+msgid ""
+"The following code uses the lock mechanism to make the button callback "
+"function synchronous:"
+msgstr ""
+
+#: ../../cookbook.rst:78
+msgid ""
+"import threading\n"
+"from pywebio import output\n"
+"\n"
+"def confirm(title, content=None, timeout=None):\n"
+" \"\"\"Show a confirm model.\n"
+"\n"
+" :param str title: Model title.\n"
+" :param list/put_xxx() content: Model content.\n"
+" :param None/float timeout: Seconds for operation time out.\n"
+" :return: Return `True` when the \"CONFIRM\" button is clicked,\n"
+" return `False` when the \"CANCEL\" button is clicked,\n"
+" return `None` when a timeout is given and the operation times "
+"out.\n"
+" \"\"\"\n"
+" if not isinstance(content, list):\n"
+" content = [content]\n"
+"\n"
+" event = threading.Event()\n"
+" result = None\n"
+"\n"
+" def onclick(val):\n"
+" nonlocal result\n"
+" result = val\n"
+" event.set()\n"
+"\n"
+" content.append(output.put_buttons([\n"
+" {'label': 'CONFIRM', 'value': True},\n"
+" {'label': 'CANCEL', 'value': False, 'color': 'danger'},\n"
+" ], onclick=onclick))\n"
+" output.popup(title=title, content=content, closable=False)\n"
+"\n"
+" event.wait(timeout=timeout) # wait the model buttons are clicked\n"
+" output.close_popup()\n"
+" return result\n"
+"\n"
+"\n"
+"res = confirm('Confirm', 'You have 5 seconds to make s choice', "
+"timeout=5)\n"
+"output.put_text(\"Your choice is:\", res)"
+msgstr ""
+
+#: ../../cookbook.rst:121
+msgid "Input in the popup"
+msgstr ""
+
+#: ../../cookbook.rst:124
+msgid ""
+"In the following code, we define a ``popup_input()`` function, which can "
+"be used to get input in popup:"
+msgstr ""
+
+#: ../../cookbook.rst:128
+msgid ""
+"def popup_input(pins, names, title='Please fill out the form'):\n"
+" \"\"\"Show a form in popup window.\n"
+"\n"
+" :param list pins: pin output list.\n"
+" :param list pins: pin name list.\n"
+" :param str title: model title.\n"
+" :return: return the form as dict, return None when user cancel the "
+"form.\n"
+" \"\"\"\n"
+" if not isinstance(pins, list):\n"
+" pins = [pins]\n"
+"\n"
+" from pywebio.utils import random_str\n"
+" action_name = 'action_' + random_str(10)\n"
+"\n"
+" pins.append(put_actions(action_name, buttons=[\n"
+" {'label': 'Submit', 'value': True},\n"
+" {'label': 'Cancel', 'value': False, 'color': 'danger'},\n"
+" ]))\n"
+" popup(title=title, content=pins, closable=False)\n"
+"\n"
+" change_info = pin_wait_change(action_name)\n"
+" result = None\n"
+" if change_info['name'] == action_name and change_info['value']:\n"
+" result = {name: pin[name] for name in names}\n"
+" close_popup()\n"
+" return result\n"
+"\n"
+"\n"
+"from pywebio.pin import put_input\n"
+"\n"
+"result = popup_input([\n"
+" put_input('name', label='Input your name'),\n"
+" put_input('age', label='Input your age', type=\"number\")\n"
+"], names=['name', 'age'])\n"
+"put_text(result)"
+msgstr ""
+
+#: ../../cookbook.rst:168
+msgid ""
+"The code uses :doc:`pin module ` to add input widgets to popup "
+"window, and uses the lock mechanism to wait the form buttons to be "
+"clicked."
+msgstr ""
+
+#: ../../cookbook.rst:173
+msgid "Redirect stdout to PyWebIO application"
+msgstr ""
+
+#: ../../cookbook.rst:176
+msgid ""
+"The following code shows how to redirect stdout of python code and "
+"subprocess to PyWebIO application:"
+msgstr ""
+
+#: ../../cookbook.rst:180
+msgid ""
+"import io\n"
+"import time\n"
+"import subprocess # ..doc-only\n"
+"from contextlib import redirect_stdout\n"
+"\n"
+"# redirect `print()` to pywebio\n"
+"class WebIO(io.IOBase):\n"
+" def write(self, content):\n"
+" put_text(content, inline=True)\n"
+"\n"
+"with redirect_stdout(WebIO()):\n"
+" for i in range(10):\n"
+" print(i, time.time())\n"
+" time.sleep(0.2)\n"
+"\n"
+"## ----\n"
+"import subprocess # ..demo-only\n"
+"# redirect a subprocess' stdout to pywebio\n"
+"process = subprocess.Popen(\"ls -ahl\", shell=True, "
+"stdout=subprocess.PIPE, stderr=subprocess.STDOUT)\n"
+"while True:\n"
+" output = process.stdout.readline()\n"
+" if output:\n"
+" put_text(output.decode('utf8'), inline=True)\n"
+"\n"
+" if not output and process.poll() is not None:\n"
+" break"
+msgstr ""
+
+#: ../../cookbook.rst:213
+msgid "Add missing syntax highlight for code output"
+msgstr ""
+
+#: ../../cookbook.rst:215
+msgid ""
+"When output code via `put_markdown()` or `put_code()`, PyWebIO provides "
+"syntax highlight for some common languages. If you find your code have no"
+" syntax highlight, you can add the syntax highlighter by two following "
+"steps:"
+msgstr ""
+
+#: ../../cookbook.rst:218
+msgid ""
+"Go to `prismjs CDN page "
+"`_"
+" to get your syntax highlighter link."
+msgstr ""
+
+#: ../../cookbook.rst:219
+msgid ""
+"Use :func:`config(js_file=...) ` to load the syntax "
+"highlight module"
+msgstr ""
+
+#: ../../cookbook.rst:223
+msgid ""
+"@config(js_file=\"https://cdn.jsdelivr.net/npm/prismjs@1.23.0/components"
+"/prism-diff.min.js\")\n"
+"def main():\n"
+" put_code(\"\"\"\n"
+"+ AAA\n"
+"- BBB\n"
+"CCC\n"
+" \"\"\".strip(), language='diff')\n"
+"\n"
+" put_markdown(\"\"\"\n"
+" ```diff\n"
+" + AAA\n"
+" - BBB\n"
+" CCC\n"
+" ```\n"
+" \"\"\", lstrip=True)"
+msgstr ""
+
+#: ../../cookbook.rst:242
+msgid "Web application related"
+msgstr ""
+
+#: ../../cookbook.rst:245
+msgid "Get URL parameters of current page"
+msgstr ""
+
+#: ../../cookbook.rst:247
+msgid ""
+"You can use URL parameter (known also as \"query strings\" or \"URL query"
+" parameters\") to pass information to your web application. In PyWebIO "
+"application, you can use the following code to get the URL parameters as "
+"a Python dict."
+msgstr ""
+
+#: ../../cookbook.rst:250
+msgid ""
+"# `query` is a dict\n"
+"query = eval_js(\"Object.fromEntries(new "
+"URLSearchParams(window.location.search))\")\n"
+"put_text(query)"
+msgstr ""
+
+#: ../../cookbook.rst:260
+msgid "Add Google AdSense/Analytics code"
+msgstr ""
+
+#: ../../cookbook.rst:262
+msgid ""
+"When you setup Google AdSense/Analytics, you will get a javascript file "
+"and a piece of code that needs to be inserted into your application page,"
+" you can use :func:`pywebio.config()` to inject js file and code to your "
+"PyWebIO application::"
+msgstr ""
+
+#: ../../cookbook.rst:265
+msgid ""
+"from pywebio import start_server, output, config\n"
+"\n"
+"js_file = \"https://www.googletagmanager.com/gtag/js?id=G-xxxxxxx\"\n"
+"js_code = \"\"\"\n"
+"window.dataLayer = window.dataLayer || [];\n"
+"function gtag(){dataLayer.push(arguments);}\n"
+"gtag('js', new Date());\n"
+"\n"
+"gtag('config', 'G-xxxxxxx');\n"
+"\"\"\"\n"
+"\n"
+"@config(js_file=js_file, js_code=js_code)\n"
+"def main():\n"
+" output.put_text(\"hello world\")\n"
+"\n"
+"start_server(main, port=8080)"
+msgstr ""
+
+#: ../../cookbook.rst:284
+msgid "Refresh page on connection lost"
+msgstr ""
+
+#: ../../cookbook.rst:286
+msgid ""
+"Add the following code to the beginning of your PyWebIO application main "
+"function::"
+msgstr ""
+
+#: ../../cookbook.rst:288
+msgid ""
+"session.run_js('WebIO._state.CurrentSession.on_session_close(()="
+">{setTimeout(()=>location.reload(),"
+" 4000})')"
+msgstr ""
+
+#: ../../cookbook.rst:291
+msgid "Cookie and localStorage manipulation"
+msgstr ""
+
+#: ../../cookbook.rst:294
+msgid ""
+"You can use `pywebio.session.run_js()` and `pywebio.session.eval_js()` to"
+" deal with cookies or localStorage with js."
+msgstr ""
+
+#: ../../cookbook.rst:296
+msgid "``localStorage`` manipulation:"
+msgstr ""
+
+#: ../../cookbook.rst:298
+msgid ""
+"set_localstorage = lambda key, value: run_js(\"localStorage.setItem(key, "
+"value)\", key=key, value=value)\n"
+"get_localstorage = lambda key: eval_js(\"localStorage.getItem(key)\", "
+"key=key)\n"
+"\n"
+"set_localstorage('hello', 'world')\n"
+"val = get_localstorage('hello')\n"
+"put_text(val)"
+msgstr ""
+
+#: ../../cookbook.rst:310
+msgid "Cookie manipulation:"
+msgstr ""
+
+#: ../../cookbook.rst:314
+msgid ""
+"# https://stackoverflow.com/questions/14573223/set-cookie-and-get-cookie-"
+"with-javascript\n"
+"run_js(\"\"\"\n"
+"window.setCookie = function(name,value,days) {\n"
+" var expires = \"\";\n"
+" if (days) {\n"
+" var date = new Date();\n"
+" date.setTime(date.getTime() + (days*24*60*60*1000));\n"
+" expires = \"; expires=\" + date.toUTCString();\n"
+" }\n"
+" document.cookie = name + \"=\" + (value || \"\") + expires + \"; "
+"path=/\";\n"
+"}\n"
+"window.getCookie = function(name) {\n"
+" var nameEQ = name + \"=\";\n"
+" var ca = document.cookie.split(';');\n"
+" for(var i=0;i < ca.length;i++) {\n"
+" var c = ca[i];\n"
+" while (c.charAt(0)==' ') c = c.substring(1,c.length);\n"
+" if (c.indexOf(nameEQ) == 0) return "
+"c.substring(nameEQ.length,c.length);\n"
+" }\n"
+" return null;\n"
+"}\n"
+"\"\"\")\n"
+"\n"
+"def setcookie(key, value, days=0):\n"
+" run_js(\"setCookie(key, value, days)\", key=key, value=value, "
+"days=days)\n"
+"\n"
+"def getcookie(key):\n"
+" return eval_js(\"getCookie(key)\", key=key)\n"
+"\n"
+"setcookie('hello', 'world')\n"
+"val = getcookie('hello')\n"
+"put_text(val)"
+msgstr ""
+
diff --git a/docs/locales/fa/LC_MESSAGES/exceptions.po b/docs/locales/fa/LC_MESSAGES/exceptions.po
new file mode 100644
index 00000000..9f9706f3
--- /dev/null
+++ b/docs/locales/fa/LC_MESSAGES/exceptions.po
@@ -0,0 +1,43 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) Weimin Wang
+# This file is distributed under the same license as the PyWebIO package.
+# FIRST AUTHOR , 2022.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PyWebIO 1.5.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2022-02-26 16:35+0330\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+
+#: ../../exceptions.rst:2
+msgid "``pywebio.exceptions``"
+msgstr ""
+
+#: of pywebio.exceptions:2
+msgid "pywebio.exceptions"
+msgstr ""
+
+#: of pywebio.exceptions:4
+msgid "This module contains the set of PyWebIO's exceptions."
+msgstr ""
+
+#: of pywebio.exceptions.SessionException:1
+msgid "Base class for PyWebIO session related exceptions"
+msgstr ""
+
+#: of pywebio.exceptions.SessionClosedException:1
+msgid "The session has been closed abnormally"
+msgstr ""
+
+#: of pywebio.exceptions.SessionNotFoundException:1
+msgid "Session not found"
+msgstr ""
+
diff --git a/docs/locales/fa/LC_MESSAGES/guide.po b/docs/locales/fa/LC_MESSAGES/guide.po
new file mode 100644
index 00000000..78c0ac63
--- /dev/null
+++ b/docs/locales/fa/LC_MESSAGES/guide.po
@@ -0,0 +1,1380 @@
+# Copyright (C) Weimin Wang
+# This file is distributed under the same license as the PyWebIO package.
+#
+# FIRST AUTHOR , 2022.
+# Pikhosh , 2022.
+msgid ""
+msgstr ""
+"Project-Id-Version: PyWebIO 1.5.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2022-02-26 16:35+0330\n"
+"PO-Revision-Date: 2022-03-14 02:17+0330\n"
+"Last-Translator: Pikhosh \n"
+"Language-Team: Persian <>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+"Language: fa\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Lokalize 21.12.2\n"
+
+#: ../../guide.rst:2
+msgid "User's guide"
+msgstr "راهنمای کاربر"
+
+#: ../../guide.rst:4
+msgid ""
+"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 pattern that backend implement api and "
+"frontend display content. In PyWebIO, you only need to write code in "
+"Python."
+msgstr ""
+"اگر شما با توسعه وب آشنایی دارید، ممکن است که به "
+"استفاده از PyWebIO که در زیر توضیح داده شده عادت نداشته باشید، که از "
+"الگو توسعه وب سنتی که بک اند API را پیاده سازی و "
+"فرانت اند محتوا را نمایش می دهد متفاوت است. در PyWebIO، شما فقط نیاز دارید که"
+" به "
+"پایتون کد بنویسید."
+
+#: ../../guide.rst:8
+msgid ""
+"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, 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."
+msgstr ""
+"در واقع، نحوه نوشتن اپلیکیشن های PyWebIO بیشتر شبیه نوشتن یک "
+"برنامه کنسول است، بجز اینکه ترمینال در اینجا تبدیل به یک مرورگر می شود. با"
+" استفاده "
+"از API دستوری ارائه شده توسط PyWebIO، شما می توانید به راحتی "
+"``put_text()``, ``put_image()``, ``put_table()`` و توابع دیگر را برای "
+"خروجی دادن متن، تصاویر، جداول و محتوا های دیگر به مرورگر فراخوانی کنید یا شما "
+"می توانید برخی توابع مانند ``input()``, ``select()``, "
+"``file_upload()`` را برای نمایش فرم های متفاوت روی مرورگر برای دریافت ورودی "
+"کاربر فراخوانی کنید. علاوه بر این، PyWebIO همچنین پشتیبانی برای رویداد های"
+" کلیک، "
+"چیدمان، و... را ارائه می دهد. PyWebIO هدف دارد تا به شما اجازه دهد که از"
+" کمترین کد برای تعامل "
+"با کاربر استفاده کنید و یک تجربه کاربری خوب را تا حد امکان ارائه دهید."
+
+#: ../../guide.rst:15
+msgid ""
+"This user guide introduces you the most of the features of PyWebIO. There"
+" is a demo link at the top right of the example codes in this document, "
+"where you can run the example code online and see what happens."
+msgstr ""
+"این راهنمای کاربر بیشتر ویژگی های PyWebIO را به شما معرفی می کند. یک"
+" لینک دمو در بالا سمت راست کد های مثال در این مستندات وجود دارد، "
+"جایی که شما می توانید کد مثال را به طور آنلاین اجرا کنید و ببینید که چه"
+" اتفاقی می افتد."
+
+#: ../../guide.rst:19
+msgid "Input"
+msgstr "ورودی"
+
+#: ../../guide.rst:21
+msgid ""
+"The input functions are defined in the :doc:`pywebio.input ` "
+"module and can be imported using ``from pywebio.input import *``."
+msgstr ""
+
+#: ../../guide.rst:23
+msgid ""
+"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."
+msgstr ""
+
+#: ../../guide.rst:27
+msgid "Basic input"
+msgstr "ورودی پایه"
+
+#: ../../guide.rst:29
+msgid "Here are some basic types of input."
+msgstr "در اینجا برخی از انواع پایه ورودی آورده شده."
+
+#: ../../guide.rst:31
+msgid "Text input:"
+msgstr "ورودی متن:"
+
+#: ../../guide.rst:33
+#, python-format
+msgid ""
+"age = input(\"How old are you?\", type=NUMBER)\n"
+"put_text('age = %r' % age) # ..demo-only"
+msgstr ""
+"age = input(\"شما چند سال دارید؟\", type=NUMBER)\n"
+"put_text('سن = %r' % age) # ..demo-only"
+
+#: ../../guide.rst:40
+msgid ""
+"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."
+msgstr ""
+
+#: ../../guide.rst:43
+msgid "Here are some other types of input functions:"
+msgstr "در اینجا برخی از انواع دیگر توابع ورودی آورده شده:"
+
+#: ../../guide.rst:45
+#, python-format
+msgid ""
+"# Password input\n"
+"password = input(\"Input password\", type=PASSWORD)\n"
+"put_text('password = %r' % password) # ..demo-only\n"
+"## ----\n"
+"\n"
+"# Drop-down selection\n"
+"gift = select('Which gift you want?', ['keyboard', 'ipad'])\n"
+"put_text('gift = %r' % gift) # ..demo-only\n"
+"## ----\n"
+"\n"
+"# Checkbox\n"
+"agree = checkbox(\"User Term\", options=['I agree to terms and "
+"conditions'])\n"
+"put_text('agree = %r' % agree) # ..demo-only\n"
+"## ----\n"
+"\n"
+"# Single choice\n"
+"answer = radio(\"Choose one\", options=['A', 'B', 'C', 'D'])\n"
+"put_text('answer = %r' % answer) # ..demo-only\n"
+"## ----\n"
+"\n"
+"# Multi-line text input\n"
+"text = textarea('Text Area', rows=3, placeholder='Some text')\n"
+"put_text('text = %r' % text) # ..demo-only\n"
+"## ----\n"
+"\n"
+"# File Upload\n"
+"img = file_upload(\"Select a image:\", accept=\"image/*\")\n"
+"if img: # ..demo-only\n"
+" put_image(img['content'], title=img['filename']) # ..demo-only"
+msgstr ""
+"# Password input\n"
+"password = input(\"پسورد را وارد کنید\", type=PASSWORD)\n"
+"put_text('پسورد = %r' % password) # ..demo-only\n"
+"## ----\n"
+"\n"
+"# Drop-down selection\n"
+"gift = select('شما چه هدیه ای را می خواهید؟', ['keyboard', 'ipad'])\n"
+"put_text('هدیه = %r' % gift) # ..demo-only\n"
+"## ----\n"
+"\n"
+"# Checkbox\n"
+"agree = checkbox(\"شرط کاربر\", options=['من با شرایط و ضوابط موافقت می"
+" کنم'])\n"
+"put_text('موافقت = %r' % agree) # ..demo-only\n"
+"## ----\n"
+"\n"
+"# Single choice\n"
+"answer = radio(\"یکی را انتخاب کنید\", options=['A', 'B', 'C', 'D'])\n"
+"put_text('پاسخ = %r' % answer) # ..demo-only\n"
+"## ----\n"
+"\n"
+"# Multi-line text input\n"
+"text = textarea('ناحیه متن', rows=3, placeholder='قدری متن')\n"
+"put_text('متن = %r' % text) # ..demo-only\n"
+"## ----\n"
+"\n"
+"# File Upload\n"
+"img = file_upload(\"یک تصویر انتخاب کنید:\", accept=\"image/*\")\n"
+"if img: # ..demo-only\n"
+" put_image(img['content'], title=img['filename']) # ..demo-only"
+
+#: ../../guide.rst:81
+msgid "Parameter of input functions"
+msgstr "پارامتر توابع ورودی"
+
+#: ../../guide.rst:83
+msgid ""
+"There are many parameters that can be passed to the input function(for "
+"complete parameters, please refer to the :doc:`function document "
+"`):"
+msgstr ""
+
+#: ../../guide.rst:86
+msgid ""
+"input('This is label', type=TEXT, placeholder='This is placeholder',\n"
+" help_text='This is help text', required=True)"
+msgstr ""
+"input('این لیبل است', type=TEXT, placeholder='این placeholder است',\n"
+" help_text='این help text است', required=True)"
+
+#: ../../guide.rst:93 ../../guide.rst:129 ../../guide.rst:279
+#: ../../guide.rst:586
+msgid "The results of the above example are as follows:"
+msgstr "نتایج مثال بالا به شرح زیر است:"
+
+#: ../../guide.rst:97
+msgid ""
+"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:"
+msgstr ""
+
+#: ../../guide.rst:100
+#, python-format
+msgid ""
+"def check_age(p): # return None when the check passes, otherwise return "
+"the error message\n"
+" if p < 10:\n"
+" return 'Too young!!'\n"
+" if p > 60:\n"
+" return 'Too old!!'\n"
+"\n"
+"age = input(\"How old are you?\", type=NUMBER, validate=check_age)\n"
+"put_text('age = %r' % age) # ..demo-only"
+msgstr ""
+"def check_age(p): # return None when the check passes, otherwise return "
+"the error message\n"
+" if p < 10:\n"
+" return 'خیلی جوان!!'\n"
+" if p > 60:\n"
+" return 'خیلی پیر!!'\n"
+"\n"
+"age = input(\"شما چند سال دارید؟\", type=NUMBER, validate=check_age)\n"
+"put_text('age = %r' % age) # ..demo-only"
+
+#: ../../guide.rst:113
+msgid ""
+"When the user input an illegal value, the input field is displayed as "
+"follows:"
+msgstr ""
+
+#: ../../guide.rst:117
+msgid ""
+"You can use ``code`` parameter in :func:`pywebio.input.textarea()` to "
+"make a code editing textarea."
+msgstr ""
+
+#: ../../guide.rst:119
+msgid ""
+"code = textarea('Code Edit', code={\n"
+" 'mode': \"python\",\n"
+" 'theme': 'darcula',\n"
+"}, value='import something\\n# Write your python code')\n"
+"put_code(code, language='python') # ..demo-only"
+msgstr ""
+"code = textarea('ویرایش کد', code={\n"
+" 'mode': \"python\",\n"
+" 'theme': 'darcula',\n"
+"}, value='import something\\n# کد پایتون خودتان را بنویسید')\n"
+"put_code(code, language='python') # ..demo-only"
+
+#: ../../guide.rst:135
+msgid "Input Group"
+msgstr "گروه ورودی"
+
+#: ../../guide.rst:137
+msgid ""
+"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 as its key and the input data as its value:"
+msgstr ""
+
+#: ../../guide.rst:142
+msgid ""
+"def check_age(p): # ..demo-only\n"
+" if p < 10: # ..demo-only\n"
+" return 'Too young!!' # ..demo-only\n"
+" if p > 60: # ..demo-only\n"
+" return 'Too old!!' # ..demo-only\n"
+" # ..demo-only\n"
+"data = input_group(\"Basic info\",[\n"
+" input('Input your name', name='name'),\n"
+" input('Input your age', name='age', type=NUMBER, validate=check_age)\n"
+"])\n"
+"put_text(data['name'], data['age'])"
+msgstr ""
+"def check_age(p): # ..demo-only\n"
+" if p < 10: # ..demo-only\n"
+" return 'خیلی جوان!!' # ..demo-only\n"
+" if p > 60: # ..demo-only\n"
+" return 'خیلی پیر!!' # ..demo-only\n"
+" # ..demo-only\n"
+"data = input_group(\"اطلاعات پایه\",[\n"
+" input('نام خود را وارد کنید', name='name'),\n"
+" input('سن خود را وارد کنید', name='age', type=NUMBER, validate=check_age)\n"
+"])\n"
+"put_text(data['name'], data['age'])"
+
+#: ../../guide.rst:158
+msgid ""
+"The input group also supports using ``validate`` parameter to set the "
+"validation function, which accepts the entire form data as parameter:"
+msgstr ""
+
+#: ../../guide.rst:160
+msgid ""
+"def check_age(p): # single input item validation # ..demo-only\n"
+" if p < 10: # ..demo-only\n"
+" return 'Too young!!' # ..demo-only\n"
+" if p > 60: # ..demo-only\n"
+" return 'Too old!!' # ..demo-only\n"
+" # ..demo-only\n"
+"def check_form(data): # return (input name, error msg) when validation "
+"fail\n"
+" if len(data['name']) > 6:\n"
+" return ('name', 'Name too long!')\n"
+" if data['age'] <= 0:\n"
+" return ('age', 'Age can not be negative!')\n"
+"\n"
+"data = input_group(\"Basic info\",[ # ..demo-only\n"
+" input('Input your name', name='name'), # ..demo-only\n"
+" input('Input your age', name='age', type=NUMBER, validate=check_age) "
+"# ..demo-only\n"
+"], validate=check_form) # ..demo-only\n"
+"put_text(data['name'], data['age']) # ..demo-only"
+msgstr ""
+"def check_age(p): # single input item validation # ..demo-only\n"
+" if p < 10: # ..demo-only\n"
+" return 'خیلی جوان!!' # ..demo-only\n"
+" if p > 60: # ..demo-only\n"
+" return 'خیلی پیر!!' # ..demo-only\n"
+" # ..demo-only\n"
+"def check_form(data): # return (input name, error msg) when validation "
+"fail\n"
+" if len(data['name']) > 6:\n"
+" return ('name', 'نام خیلی طولانی است!')\n"
+" if data['age'] <= 0:\n"
+" return ('age', 'سن نمی تواند منفی باشد!')\n"
+"\n"
+"data = input_group(\"اطلاعات پایه\",[ # ..demo-only\n"
+" input('نام خود را وارد کنید', name='name'), # ..demo-only\n"
+" input('سن خود را وارد کنید', name='age', type=NUMBER, validate=check_age) "
+"# ..demo-only\n"
+"], validate=check_form) # ..demo-only\n"
+"put_text(data['name'], data['age']) # ..demo-only"
+
+#: ../../guide.rst:183
+msgid ""
+"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."
+msgstr ""
+
+#: ../../guide.rst:188
+msgid "Output"
+msgstr "خروجی"
+
+#: ../../guide.rst:190
+msgid ""
+"The output functions are all defined in the :doc:`pywebio.output "
+"` module and can be imported using ``from pywebio.output import "
+"*``."
+msgstr ""
+
+#: ../../guide.rst:193
+msgid ""
+"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."
+msgstr ""
+
+#: ../../guide.rst:197
+msgid "Basic Output"
+msgstr "خروجی پایه"
+
+#: ../../guide.rst:199
+msgid ""
+"Using output functions, you can output a variety of content, such as "
+"text, tables, images and so on:"
+msgstr ""
+
+#: ../../guide.rst:201
+msgid ""
+"# Text Output\n"
+"put_text(\"Hello world!\")\n"
+"## ----\n"
+"\n"
+"# Table Output\n"
+"put_table([\n"
+" ['Commodity', 'Price'],\n"
+" ['Apple', '5.5'],\n"
+" ['Banana', '7'],\n"
+"])\n"
+"## ----\n"
+"\n"
+"# Image Output\n"
+"put_image(open('/path/to/some/image.png', 'rb').read()) # local image # "
+"..doc-only\n"
+"put_image('http://example.com/some-image.png') # internet image # ..doc-"
+"only\n"
+"put_image('https://www.python.org/static/img/python-logo.png') # ..demo-"
+"only\n"
+"## ----\n"
+"\n"
+"# Markdown Output\n"
+"put_markdown('~~Strikethrough~~')\n"
+"## ----\n"
+"\n"
+"# File Output\n"
+"put_file('hello_word.txt', b'hello word!')\n"
+"## ----\n"
+"\n"
+"# Show a PopUp\n"
+"popup('popup title', 'popup text content')\n"
+"\n"
+"# Show a notification message\n"
+"toast('New message 🔔')"
+msgstr ""
+"# Text Output\n"
+"put_text(\"سلام دنیا!\")\n"
+"## ----\n"
+"\n"
+"# Table Output\n"
+"put_table([\n"
+" ['کالا', 'قیمت'],\n"
+" ['سیب', '5.5'],\n"
+" ['موز', '7'],\n"
+"])\n"
+"## ----\n"
+"\n"
+"# Image Output\n"
+"put_image(open('/path/to/some/image.png', 'rb').read()) # local image # "
+"..doc-only\n"
+"put_image('http://example.com/some-image.png') # internet image # ..doc-"
+"only\n"
+"put_image('https://www.python.org/static/img/python-logo.png') # ..demo-"
+"only\n"
+"## ----\n"
+"\n"
+"# Markdown Output\n"
+"put_markdown('~~Strikethrough~~')\n"
+"## ----\n"
+"\n"
+"# File Output\n"
+"put_file('hello_word.txt', b'hello word!')\n"
+"## ----\n"
+"\n"
+"# Show a PopUp\n"
+"popup('popup title', 'popup text content')\n"
+"\n"
+"# Show a notification message\n"
+"toast('New message 🔔')"
+
+#: ../../guide.rst:238
+msgid ""
+"For all output functions provided by PyWebIO, please refer to the "
+":doc:`pywebio.output ` module. In addition, PyWebIO also "
+"supports data visualization with some third-party libraries, see :doc"
+":`Third-party library ecology `."
+msgstr ""
+
+#: ../../guide.rst:245
+msgid ""
+"If you use PyWebIO in interactive execution environment of Python shell, "
+"IPython or jupyter notebook, you need call `show()` method explicitly to "
+"show output::"
+msgstr ""
+
+#: ../../guide.rst:248
+msgid ""
+">>> put_text(\"Hello world!\").show()\n"
+">>> put_table([\n"
+"... ['A', 'B'],\n"
+"... [put_markdown(...), put_text('C')]\n"
+"... ]).show()"
+msgstr ""
+
+#: ../../guide.rst:258
+msgid "Combined Output"
+msgstr "خروجی ترکیبی"
+
+#: ../../guide.rst:260
+msgid ""
+"The output functions whose name starts with ``put_`` can be combined with"
+" some output functions as part of the final output:"
+msgstr ""
+
+#: ../../guide.rst:262
+msgid ""
+"You can pass ``put_xxx()`` calls to `put_table() "
+"` as cell content:"
+msgstr ""
+
+#: ../../guide.rst:264
+msgid ""
+"put_table([\n"
+" ['Type', 'Content'],\n"
+" ['html', put_html('X