跳到主要内容

范例:各类缓存

范例:各类缓存
Example of cache types

针对各种类型的缓存,提供了以下三个范例工程:

single_process.py

background_callback.py

tempfile_cache.py

信息

要运行使用背景callback的范例,请安装以下可选依赖项:

python -m pip install dash[diskcache]

这些范例各自实现了以下等价的效果。

  1. 三个按钮:StringIO, BytesIOPath.
    1. 按下StringIOsvg图片文件将以文字数据的方式载入并发送到前端。
    2. 按下BytesIOsvg图片文件将以字节数据的方式载入并发送到前端。
    3. 按下Pathsvg图片文件对应的路径将会缓存,图片的数据将会载入并发送到前端。
  2. 按下按钮的时候,数据的类型、缓存数据的地址,以及加载的图片将会展示在页面上。
  3. 对于使用了background_callbacks的范例,callback刻意处理成了带有短暂延迟的效果。在运行callback时,亦即准备图片数据时,将会显示进度。

定义布局

single_process.py为例,范例工程使用的布局定义如下:

html.Div(
(
html.Div(
(
html.P("Get Image by:"),
html.Button(id="btn-strio", children="StringIO"),
html.Button(id="btn-bytesio", children="BytesIO"),
html.Button(id="btn-path", children="Path"),
)
),
html.Div((html.P("Cache type:"), html.P(id="type"))),
html.Div((html.P("Cache address:"), html.P(id="addr"))),
html.Div((html.P("Cached Image:"), html.Img(id="cache"))),
),
)

按钮提供了触发callback的功能,最终结果将会由html.Img(...)展示。初始化时,图片组件是空的,换言之,并无任何图片显示。

按下按钮的时候,结果会刷新,以便确认按下的是哪一个按钮、以及缓存数据的地址是何状态。

定义callback

Callback的触发事件是按下按钮。根据所按按钮的ID,数据会以不同的方式载入。例如,当按下btn-strio之时,SVG文件将会以文字的形式载入。服务方法self.service.register(...)接收数据和文件信息作为输入,并返回用来访问图片的地址。

@app.callback(
Output("type", "children"),
Output("addr", "children"),
Input("btn-strio", "n_clicks"),
Input("btn-bytesio", "n_clicks"),
Input("btn-path", "n_clicks"),
prevent_initial_call=True,
)
def click_get_image(
n_clicks_strio: Optional[int],
n_clicks_bytesio: Optional[int],
n_clicks_path: Optional[int],
):
prop_id = str(dash.callback_context.triggered[0]["prop_id"])
file_path = os.path.join(self.root, "test_image.svg")
if prop_id.startswith("btn-strio") and n_clicks_strio:
with open(file_path, "r") as fobj:
fio = io.StringIO(fobj.read())
elif prop_id.startswith("btn-bytesio") and n_clicks_bytesio:
with open(file_path, "rb") as fobj:
fio = io.BytesIO(fobj.read())
elif prop_id.startswith("btn-path") and n_clicks_path:
fio = file_path
else:
return dash.no_update, dash.no_update
addr = self.service.register(
fio,
content_type="image/svg+xml",
mime_type="image/svg+xml",
one_time_service=True,
)
return str(fio.__class__.__name__), addr

运行该callback后,第一个返回值显示了所载入数据的类型。第二个返回值应当是如下形式的地址:

/cache-data?uid=...

该地址会连锁触发另一个callback,其定义如下:

@app.callback(
Output("cache", "src"),
Input("addr", "children"),
prevent_initial_call=True,
)
def update_cache(addr):
if not addr:
return dash.no_update
return addr

这一callback、在地址不为空的情况下,会将地址传递给图片组件的src属性。换言之,图片的数据源最终会设置为/cache-data?uid=...的形式。

当图片地址得到更新时,由于浏览器会读取图片、并立刻访问缓存地址,从而触发数据的载入,客户端将以flask.stream_with_context的方式接收数据。

使用背景callback

第二和第三个范例展示了如何将文件缓存和运行在子进程中的背景callback整合到一起。要实现这一目标,只需要做出轻微的改变。以tempfile_cache.py为例。第一处修改位于初始化的部分。

class Demo:
def __init__(self) -> None:
self.service = ServiceData(CacheFile(None))
self.root = os.path.dirname(__file__)

其中,缓存替换成了CacheFile,以支持不同进程之间的数据I/O。

第二处修改位于layout的定义,需要添加一个组件、以展示callback运行的进度。

html.Div(
(
...
# hightlight-next-line
html.Div((html.P(("Progress:", html.Span(id="prog"))))),
html.Div((html.P("Cache type:"), html.P(id="type"))),
...
),
)

最终,callback的修改如下:

@app.callback(
Output("type", "children"),
...
Input("btn-path", "n_clicks"),
background=True,
running=[
(Output("btn-strio", "disabled"), True, False),
(Output("btn-bytesio", "disabled"), True, False),
(Output("btn-path", "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_strio: Optional[int],
n_clicks_bytesio: Optional[int],
n_clicks_path: Optional[int],
):
prop_id = str(dash.callback_context.triggered[0]["prop_id"])
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(
fio,
content_type="image/svg+xml",
mime_type="image/svg+xml",
one_time_service=True,
)
return str(fio.__class__.__name__), addr

此处唯一修改的部分,纯粹是为了使用背景callback并展示进度。对于service.register方法而言,即使缓存的类型改为CacheFile,其用法仍无任何变化。