范例:使用下载组件
范例:使用下载组件 |
---|
以下链接提供了一个下载文件的范例。
要运行使用背景callback的范例,请安装以下可选依赖项:
python -m pip install dash[diskcache]
这一小型范例只提供了一个Start按钮。
- 按下按钮时,会模拟一小段等待时间,在此期间进度会更新。
- Callback结束时,会展示缓存类型、和将要下载的文件的地址。下载文件事件会自动触发。
- 下载事件结束时,无论成功与否,都将展示下载状态。
定义缓存和下载器
在初始化方法中,提供了ServiceData(...)
句柄。
class Demo:
def __init__(self) -> None:
self.service = ServiceData(CacheFile(None))
self.root = os.path.dirname(__file__)
为了模拟准备一个大体量文件的过程,使用CacheFile(...)
缓存数据,其支持背景callback。
定义布局
所实现的布局中,包含一个按钮和两个输出。
html.Div(
(
html.Div(
html.P(
(
html.Span(
"Download a image:", style={"paddingRight": "0.5rem"}
),
html.Button(id="btn", children="Start"),
)
)
),
html.Div((html.P(("Progress:", html.Span(id="prog"))))),
html.Div((html.P("Cache type:"), html.P(id="type"))),
html.Div((html.P("Cache address:"), html.P(id="addr"))),
html.Div((html.P("Downloader status:"), html.P(id="dlstats"))),
Downloader(id="download", mitm="/dfc-downloader"),
),
)
此处设置了下载器,其为一个Dash组件。属性mitm
用来维持下载事件的激活状态。其服务对应的路由需要和接下来将要用到的ServiceDownloader
保持一致。
与其他范例相若,此处只有id=btn
的按钮能触发callback。Callback的返回值由布局中的Downloader
组件处理。
定义callback
该例中,使用了背景callback展示进度。透过刻意的一小段延迟、模拟准备大体量数据所消耗的时间。
@app.callback(
Output("type", "children"),
Output("addr", "children"),
Input("btn", "n_clicks"),
background=True,
running=[
(Output("btn", "disabled"), True, False),
],
progress=[Output("prog", "children")],
manager=background_callback_manager,
prevent_initial_call=True,
)
def click_get_image(
set_progress: Callable[[Tuple[str]], None], n_clicks: Optional[int]
):
if not n_clicks:
return dash.no_update, dash.no_update
file_path = os.path.join(self.root, "test_image.svg")
n = 10
for i in range(n):
time.sleep(0.1)
set_progress(("{0}%".format(int(round((i + 1) / n * 100))),))
addr = self.service.register(
file_path,
content_type="image/svg+xml; charset=utf-8",
mime_type="image/svg+xml",
one_time_service=True,
download=True,
)
return str(file_path.__class__.__name__), addr
第二个输出值为缓存数据的地址。此callback含有两个特殊设置。其一是content_type
的配置,包含了文字文件的charset
。其二是download=True
这一配置。若所缓存的文件是用来下载的,这一参数总是要设置为True
。
所返回的地址会连锁触发另一个callback,其定义如下:
@app.callback(
Output("download", "url"),
Input("addr", "children"),
prevent_initial_call=True,
)
def trigger_downloading_event(addr):
if not addr:
return dash.no_update
return addr
这一callback触发了下载事件。当地址有效时,该值会用于更新下载器的属性url
。须知每当url
更新时,一个新的下载事件就会触发。
即使向组件输入了空的地址,也是没问题的。因为空的url
并不会触发下载事件。
@app.callback(
Output("dlstats", "children"),
Input("download", "status"),
prevent_initial_call=True,
)
def trigger_downloading_status(status: Optional[DownloaderStatus]):
if not status:
return ""
return str(status)
最后的callback用来展示下载器的状态。只要状态更新过一次,无论下载成功与否,属性status
都会更新为一个字典。
属性status
由下载器自身维护。透过某个callback主动修改它是没有任何意义的。
加载服务
对于某个服务句柄,它的设置在初始化后就 已经确定。因此,可以在任何位置调用service.serve(...)
方法。本例中,所有的服务都在调用WrappedApp
的load()
方法时一并加载。
class WrappedApp:
"""A wrapped app. This wrapped app is accessed by the tests."""
def __init__(self, app: dash.Dash) -> None:
self.app = app
def load(self) -> None:
demo = Demo()
app.layout = demo.layout()
demo.bind(app)
demo.service.serve(app)
ServiceDownloader("/dfc-downloader").serve(app)
此处ServiceDownloader
的路由需要和下载器的属性mitm
保持一致。
设置这一特性,能使mitm
由本地脚本提供。若不做这些设置,则mitm
会托管到GitHub上的脚本。换言之,若用户令属性mitm
留空、且不配置ServiceDownloader
的话,下载器将只能在互联网可用的前提下工作。