Example of the downloader
Example of using the downloader |
---|
Check the following link to review the demo of downloading a file.
Running the exmaple requires users to install the optional dependencies by
python -m pip install dash[diskcache]
This small demo only provides one Start button.
- By clicking it, a short waiting period will be simulated and the progress of the callback will be displayed.
- When the callback gets finalized, the cache type and the address of the file to be downloaded will be displayed and the downloading event will automatically start.
- When the downloading event is finalized successfully or canceled by users, a status will be displayed.
Define the cache and the downloader
In the initialization method, we provide the ServiceData(...)
handle.
class Demo:
def __init__(self) -> None:
self.service = ServiceData(CacheFile(None))
self.root = os.path.dirname(__file__)
To simulate the process of preparing a large file, we store the data with
CacheFile(...)
which is compatible with the background callback.
Define the layout
The implementation of the layout contains one button and two outputs.
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"),
),
)
The downloader is specified here. It is a Dash component. The property mitm
is a
monitor preserving the active status of the downloading event. Its service route
need to be the same as the configuration of ServiceDownloader
which is used later.
Similar to the other examples, here we only use one button with id=btn
to start the
callback. The returned value of the callback will be handled by the Downloader
component.
Define the callbacks
In this example, we use the background callback to display the progress. We deliberately make the callback wait for a short period to simulate the process of preparing the large-size data.
@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
The second output is the address of the cached data. This callback has two special
configurations. The first configuration is the content_type
, where we provide the
charset
of the text file. The second configuration is download=True
. If the file
is cached for downloading, this argument should be always specified as True
.
The returned address will be used to fire another callback subsequently, the definition of the next callback is:
@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
This callback triggers the downloading event. When the address is valid, it will be
used for updating the property url
of the downloader. Note that every time when
url
is updated, a downloading event will start.
Even if the empty address is input to the component, it is still OK because an empty
url
will not trigger a downloading event.
@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)
The last callback is used for showing the status of the downloader. As long as the
status is updated by once, no matter whether the downloading is successful or not,
the status
property will be updated as a dictionary.
The property status
is maintained by the downloader itself. Modifying this property
by a callback is meaningless.
Load the services
For a service handle, its configurations are determined after the initialization.
Therefore, it is OK to call service.serve(...)
method anywhere. In this example, we
make the services loaded when calling the load()
method of the WrappedApp
.
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)
The route of ServiceDownloader
needs to be consistent with the mitm
property of
the downloader.
Configuring this feature will make the mitm
served by the local script. If not
make these configurations, the mitm
will be hosted by a script on GitHub. In other
words, if users let mitm
be empty and do not configure ServiceDownloader
,
the downloader will work only when the Internet is accessible.