Iāll take a look tomorrow @johnkangw ! I suspect itāll be a quick fix somewhere in the routing code.
Hey @AnnMarieW
To double back on this, are there plans to integrate a solution to accessing the cache in callbacks outside the app.py
file?
For example, I have multipage app that uses a structure similar to the āCaching and Signalingā example in the docs. Once the expensive query is complete, callbacks on every page will be utilizing this same data source and need to access the global cache.
Since the cache is tied to the app object, I havenāt been able to make this work on a multipage setup without just putting all my callbacks inside the app.py
file. This quickly becomes hard to navigate with a larger multipage app.
Iām happy to put together a simple example if that wasnāt clear, thanks!
Hi @bigmike
Iām not sure, but I added it to this Github issue since itās related. Thanks for raising this issue. You can track the progress in Github.
Awesome feature, thanks!
One pain Iāve run into while using this feature is from tests. If I use pytest to do something like
from pages import my_page
def test_my_page():
assert(4 == my_page.my_function())
I get AttributeError: module 'dash' has no attribute 'register_page'
when running pytest
. My workaround has been to wrap dash.register_page in a try-catch.
Hi guys,
Just started converting my dash app over to a multipage app and have a couple of questions.
Is it possible to go to another page from within a callback, preferable with a query string?
Can you access the query parameter from within a callback?
How do you use dcc.Loading so that we get loading icon while each page loads?
Thanks for any help, RIchard
Hi @Precog
The easiest way is to return a different layout based on the query string. Another option is to use path variables. You can find examples in the docs here: https://dashlabs.pythonanywhere.com/
There are two sample apps with query strings in dash-labs demos folder
-
multi_page_basics/
is a minimal example including both query strings and path variable. -
multi_page_query_strings/
is an example of sharing query data between pages
One way to access query parameters from a callback is to store them in a dcc.Store
component.
You can find more information on loading states here: Loading States | Dash for Python Documentation | Plotly
Thanks AnnMarieW.
Just in case anyone else came across the same issues as me, this is what I did:
1) Is it possible to go to another page from within a callback, , preferably with a query string?
I used dcc.Location in a callback to redirect to another page:
return dcc.Location(pathname=f"/status-page", id="someid_doesnt_matter")
I tried adding a query string such as f"/status-page?unitname=unit" but i get a 404 error because the url ends up being /status-page%3funitname=unit . Any ideas how to get round this one?
2)Can you access the query parameter from within a callback?
I didnt try this but i presume you can add location as a state and get the query string in the callback e.g. State(āurlā, āpathnameā)
3) How do you use dcc.Loading so that we get loading icon while each page loads?
I ended up wrapping the page container as follows: dcc.Loading(dl.plugins.page_container)
Hope this helps anyone else.
Hi, is there any update on the issue raised by johnkangw re UnsupportedRelativePath
? I have the same problem although not using Dash Enterprise but proxying Flask through IIS - hence wsgi. Interestingly Iām getting 200 returned on various routes before it fails on dash-update-component, e.g.
200 http://localhost/myapp/_dash-component-suites/dash/dash_table/bundle.v5_0_0m1644814682.js
200 http://localhost/myapp/_dash-dependencies
200 http://localhost/myapp/_dash-layout
500 http://localhost/myapp/_dash-update-component
This is awesome, but I am running into an issue:
I execute the below code and on my first attempt it works! I am able to have a dropdown and navigate to these pages successfully.
Printing out my dash.page_registry(values) successfully:
{āmoduleā: āpages.heatmapsā, āsupplied_pathā: ā/ā, āpath_templateā: None, āpathā: ā/ā, āsupplied_nameā: None, ānameā: āHeatmapsā, āsupplied_titleā: None, ātitleā: āHeatmapsā, āsupplied_descriptionā: None, ādescriptionā: āHeatmapsā, āorderā: 0, āsupplied_orderā: None, āsupplied_layoutā: None, āimageā: None, āsupplied_imageā: None, āredirect_fromā: None, ālayoutā: Div([P(āMedals included:ā), Checklist(id=āheatmaps-medalsā, options=[{ālabelā: āgoldā, āvalueā: āgoldā}, {ālabelā: āsilverā, āvalueā: āsilverā}, {ālabelā: ābronzeā, āvalueā: ābronzeā}], value=[āgoldā, āsilverā, ābronzeā]), Graph(id=āheatmaps-graphā)])}
{āmoduleā: āpages.bar_chartsā, āsupplied_pathā: None, āpath_templateā: None, āpathā: ā/bar-chartsā, āsupplied_nameā: None, ānameā: āBar chartsā, āsupplied_titleā: None, ātitleā: āBar chartsā, āsupplied_descriptionā: None, ādescriptionā: āBar chartsā, āorderā: None, āsupplied_orderā: None, āsupplied_layoutā: None, āimageā: None, āsupplied_imageā: None, āredirect_fromā: None, ālayoutā: Div([Dropdown(id=ādropdownā, clearable=False, options=[{ālabelā: āSunā, āvalueā: āSunā}, {ālabelā: āSatā, āvalueā: āSatā}, {ālabelā: āThurā, āvalueā: āThurā}, {ālabelā: āFriā, āvalueā: āFriā}], style={āwidthā: ā50%ā}, value=āSunā), Graph(id=ābar-chartā)])}
{āmoduleā: āpages.histogramsā, āsupplied_pathā: None, āpath_templateā: None, āpathā: ā/histogramsā, āsupplied_nameā: None, ānameā: āHistogramsā, āsupplied_titleā: None, ātitleā: āHistogramsā, āsupplied_descriptionā: None, ādescriptionā: āHistogramsā, āorderā: None, āsupplied_orderā: None, āsupplied_layoutā: None, āimageā: None, āsupplied_imageā: None, āredirect_fromā: None, ālayoutā: Div([Graph(id=āhistograms-graphā), P(āMean:ā), Slider(id=āhistograms-meanā, marks={-3: ā-3ā, 3: ā3ā}, max=3, min=-3, value=0), P(āStandard Deviation:ā), Slider(id=āhistograms-stdā, marks={1: ā1ā, 3: ā3ā}, max=3, min=1, value=1)])}
However, If I stop executing the code and retry the code, the dropdown values no longer appear and I cannot navigate to my pages. If I restart my IDE (Spyder for python on Windows), I am able to successfully re-run it.
No registry values are available to be printed
Screenshots attached:
Code:
import os
path =rāC:\Users\hellothere\Dropbox\test_multi_page - Copyā
os.chdir(path)
import dash # pip install dash
import dash_labs as dl # pip install dash-labs
import dash_bootstrap_components as dbc # pip install dash-bootstrap-components
Code from: dash-labs/docs/demos/multi_page_example1 at main Ā· plotly/dash-labs Ā· GitHub
app = dash.Dash(
name, plugins=[dl.plugins.pages], external_stylesheets=[dbc.themes.BOOTSTRAP]
)
for x in dash.page_registry.values():
print(x)
navbar = dbc.NavbarSimple(
dbc.DropdownMenu(
[
dbc.DropdownMenuItem(page[ānameā], href=page[āpathā])
for page in dash.page_registry.values()
if page[āmoduleā] != āpages.not_found_404ā
],
nav=True,
label=āMore Pagesā,
),
brand=āMulti Page App Plugin Demoā,
color=āprimaryā,
dark=True,
className=āmb-2ā,
)
app.layout = dbc.Container(
[navbar, dl.plugins.page_container],
fluid=True,
)
if name == āmainā:
app.run_server(debug=False)
Hi @newuser357 and welcome back
Thanks for trying out pages
and Iām glad you like it! Sorry you ran into this bug. The issue is that it canāt find the pages/
folder unless the app.py
is run from the CWD.
This has already been reported, and as soon as the nice folks at Plotly approve my pull request to fix this, we can do another release.
Hey Dash community,
Iām pleased to announce the latest release of dash-labs V1.0.3
pip install -U dash-labs
Bug Fix: No longer need to run app.py from cwd.
This version fixes a bug where the main app.py needed to be run from the current working directory else it couldnāt find the files in the pages/
folder. Thanks for reporting @newuser357 and @eiriklid in dash-labs issue #84
New feature Title and Description Updated Dynamically!
As requested by @raptorbrad and @bradley-erickson in dash-labs issue#74
Itās now possible to update the title in the browser tab and the title and description in the meta tags dynamically with a function.
You can see an example of this live in the on-line dash-labs docs: Asset Analysis: inventory branch-1001
The easiest way to try all the \pages
features locally is to copy the multi_page_basics folder from the dash labs demos directory and run app.py
Here is the pages/path_variables.py
in the demo which updates the title and description dynamically based on the path variables.
pages/path_variables.py
import dash
def title(asset_id=None, dept_id=None):
return f"Asset Analysis: {asset_id} {dept_id}"
def description(asset_id=None, dept_id=None):
return f"This is the AVN Industries Asset Analysis: {asset_id} in {dept_id}"
dash.register_page(
__name__,
path_template="/asset/<asset_id>/department/<dept_id>",
title=title,
description=description,
path="/asset/inventory/department/branch-1001",
)
def layout(asset_id=None, dept_id=None, **other_unknown_query_strings):
return dash.html.Div(
f"variables from pathname: asset_id: {asset_id} dept_id: {dept_id}"
)
Here is what the link looks like if you share a link to this page here or on social media:
The title and description will be updated if you change the variables in the path.
Thanks for putting so much effort into dash-labs @AnnMarieW! It is a great addition to dash and works beautifully!
I have trouble wrapping my head around something that might have been solved before, but I could not find any pointers:
I am trying to set up a dash-lab \pages app that enables users to upload a (potentially large, 500MB) data file to be stored temporarily and analyzed in different ways. I want to avoid actual authentication, but still I need some kind of user management to store the file temporarily in a user/session-specific folder.
Currently, I create a user/session id during the first call to the app.layout, similar to @chriddypās example here: Github: dash-cache-signal-session, and store it in a hidden Div. However, due to the restrictions that the ānativeā dash uploader component has, I want to use the dash-uploader component that supports writing uploaded files with an arbitrary size directly to a user-specific folder if a unique user/session id is provided. I am not sure, how to forward the unique session id created during the initial call of the main layout function to the pages. Does anyone know how to do this or has done this before?
MWE:
app.py:
import app_configuration
import dash
import dash_bootstrap_components as dbc
import dash_labs as dl
import dash_uploader as du
import uuid
from dash import dcc, html, Input, Output, State
app = dash.Dash(__name__,
plugins=[dl.plugins.pages],
suppress_callback_exceptions=True)
app.title = "app title"
du.configure_upload(app, "uploads", use_upload_id=True, upload_api=None, http_request_handler=None)
split_position = 2
navbar = dbc.Navbar(
dbc.Container([
dbc.Row(
dbc.Col(dbc.NavbarBrand(html.B(app_configuration.APP_NAME), className="ms-2")),
align="center",
className="g-0",
),
dbc.Row([
dbc.NavbarToggler(id="navbar-toggler"),
dbc.Collapse(
dbc.Nav([
dbc.NavItem(dbc.NavLink(page['name'], href=page['path'], active="exact" if page["path"] == "/" else "partial")) if page["order"] != split_position else dbc.NavItem(dbc.NavLink(page['name'], href=page['path'], active="partial"), className="me-auto") for page in dash.page_registry.values() if page["module"] != "pages.not_found_404" and "sub" not in page["module"]
], className="w-100"),
id="navbar-collapse",
is_open=False,
navbar=True,),
], className="flex-grow-1"),
], fluid=True),
dark=True,
color="primary",
fixed="top",
sticky="top",
)
def layout():
return dbc.Container([
dcc.Location(id='url', refresh=False),
html.Div(id="userid", hidden=False),
html.Div([navbar], className="mb-2"),
dl.plugins.page_container
], fluid=True, style={"position": "relative", "display": "block"})
app.layout = layout
@app.callback(
Output('userid', 'children'),
Input('url', 'pathname'),
State('userid', 'children')
)
def create_user(url, user_id):
"""Create a unique user/session id and store it in a hidden div."""
print("create_user:", url, user_id)
if not user_id:
user_id = str(uuid.uuid4())
return user_id
if __name__ == "__main__":
app.run_server(debug=True, port=5000)
page_1.py:
import dash
import dash_bootstrap_components as dbc
import dash_uploader as du
from dash import html
dash.register_page(__name__, top_nav=True, path="/uploader", order=1)
content = html.Div([
du.Upload(id="uploader",
text="Drag & Drop or click to select",
filetypes=["txt", "csv", "xlsx"],
max_file_size=512,
upload_id=UNIQUE_USER_ID_GOES_HERE,
max_files=1)
])
layout = dbc.Container([
dbc.Row([
dbc.Col([content])
])
], fluid=True, className="mb-3")
Hi @mawe and thanks for your kind words
This is a great question. You could try updating the layout of page_1.py
in a callback with the Input
of the āuseridā component (which is typically a dcc.Store
rather than a hidden div.
So page_1 might look something like:
import dash
import dash_bootstrap_components as dbc
import dash_uploader as du
from dash import html, callback, Input, Output
dash.register_page(__name__, top_nav=True, path="/uploader", order=1)
layout = dbc.Container([
dbc.Row([
dbc.Col(html.Div(id="content"))
])
], fluid=True, className="mb-3")
@callback(
Output("content", "children"),
Input("userid", "data"),
)
def update_layout(userid):
return html.Div([
du.Upload(id="uploader",
text="Drag & Drop or click to select",
filetypes=["txt", "csv", "xlsx"],
max_file_size=512,
upload_id=userid,
max_files=1)
])
I didnāt try this - could you let me know if it works?
Thanks @AnnMarieW, it works! Actually, I was thinking something along what you proposed, but I never tried because I assumed that the somewhat dynamically generated dash-uploader component would not be available for callbacksā¦ Next time, Iāll better try these ideas I also changed the hidden div to a dcc.Store, as you suggested. The hidden div was a leftover of my test prototype. Thanks again!
So I have updated the package. and am trying the new example ( py the multi_page_basics folder
If I download that example as is - I donāt get any of the pages. If I then declare my path (in this case the dropbox app on my desktop), I see the expected result. But if I stop running the code and then run it again - I am missing all of the added pages.
I am assuming this is due to some path variable issue that I am not figuring out how to fix. Most likely its user error.
Updated package via pip update:
without declaring the path:
declaring the path in app.py:
after ending the code and restarting:
Amazing feature! I am using app.index_string to modify the default HTML Index Template to add Google Analytics to my app. When using the pages plugin it seems to completely ignore my index_string customization. Does anyone know what causes this and how I can still customize the HTML Index template while using this feature?
Hi @ryanf
Thanks for reporting! You are correct ā I could verify that the pages
plugin does not handle a customized index_string
.
Iām working on the PR to move pages
to Dash and the good news is that it works in that version. The PR is very close to ready, and Iām hoping it will be included in the next Dash release
Iāll open this as an issue in dash-labs so we can track it, but it will probably be on the back burner unless there is some big delay in getting it included in Dash.
Hi @AnnMarieW! I appreciate you researching this! Great to hear that it should be resolved with the (hopefully soon) move to Dash. Thanks for all your hard work on pages! It really is an incredibly valuable feature!!