-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for config-aware relative paths #1073
Changes from 11 commits
0af759c
249d305
7e620a9
6b3978d
02a4295
868e93a
57cf39b
dd790ab
86b7665
a0b5f1f
20dd95f
0c0af34
685cd69
c11aabe
31854eb
5879961
46af325
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,6 +36,8 @@ | |
from . import _watch | ||
from ._utils import get_asset_path as _get_asset_path | ||
from ._utils import create_callback_id as _create_callback_id | ||
from ._utils import get_relative_path as _get_relative_path | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this pattern ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
from ._utils import strip_relative_path as _strip_relative_path | ||
from ._configs import get_combined_config, pathname_configs | ||
from .version import __version__ | ||
|
||
|
@@ -1565,6 +1567,80 @@ def get_asset_url(self, path): | |
|
||
return asset | ||
|
||
def get_relative_path(self, path): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Naming options:
Notes:
I think I prefer There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The other thing I'm wondering is whether we need the reverse operation, stripping off There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For use in @app.callback(...)
def display_content(path):
if (path is None or
app.get_relative_path(path) == app.get_relative_path('/')):
return chapters.home
elif app.get_relative_path(path) == app.get_relative_path('/page-1')
return chapters.page_1
elif app.get_relative_path(path) == app.get_relative_path('/page-2')
return chapters.page_2 Or, rather, the trailing slashes case: @app.callback(...)
def display_content(path):
if path is None or app.get_relative_path(path) == app.get_relative_path("/"):
return chapters.home
elif app.get_relative_path(path) in [
app.get_relative_path("/page-1"),
app.get_relative_path("/page-1/"),
]:
return chapters.page_1
elif app.get_relative_path(path) in [
app.get_relative_path("/page-2"),
app.get_relative_path("/page-2/"),
]:
return chapters.page_2 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That last example actually exposes another missing case, The documented example should actually be @app.callback(...)
def display_content(path):
if path is None or app.get_relative_path(path) in [
app.get_relative_path(""),
app.get_relative_path("/"),
]:
return chapters.home
elif app.get_relative_path(path) in [
app.get_relative_path("/page-1"),
app.get_relative_path("/page-1/"),
]:
return chapters.page_1
elif app.get_relative_path(path) in [
app.get_relative_path("/page-2"),
app.get_relative_path("/page-2/"),
]:
return chapters.page_2 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suppose, but folks may appreciate simplifying it (including stripping the trailing @app.callback(...)
def display_content(path):
page_name = app.trim_path(path)
if not page_name: # None or ''
return chapters.home
elif page_name == 'page-1':
return chapters.page_1
if page_name == "page-2":
return chapters.page_2 At which point you could even avoid having to reference all the pages individually, since @app.callback(...)
def display_content(path):
page_name = app.trim_path(path)
if not page_name: # None or ''
return chapters.home
return getattr(chapters, page_name.replace("-", "_"))
# or make chapters be a dict that maps without the replacement There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, that's way better! I'll work on a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alright, I added |
||
""" | ||
Return a path with `requests_pathname_prefix` prefixed before it. | ||
Use this function when specifying local URL paths that will work | ||
in environments regardless of what `requests_pathname_prefix` is. | ||
In some deployment environments, like Dash Enterprise, | ||
`requests_pathname_prefix` is set to the application name, | ||
e.g. `my-dash-app`. | ||
When working locally, `requests_pathname_prefix` might be unset and | ||
so a relative URL like `/page-2` can just be `/page-2`. | ||
However, when the app is deployed to a URL like `/my-dash-app`, then | ||
`app.get_relative_path('/page-2')` will return `/my-dash-app/page-2`. | ||
This can be used as an alternative to `get_asset_url` as well with | ||
`app.get_relative_path('/assets/logo.png')` | ||
""" | ||
asset = _get_relative_path( | ||
self.config.requests_pathname_prefix, | ||
path, | ||
) | ||
|
||
return asset | ||
|
||
def strip_relative_path(self, path): | ||
""" | ||
Return a path with `requests_pathname_prefix` and leading and trailing | ||
slashes stripped from it. Also, if None is passed in, None is returned. | ||
Use this function in callbacks that deal with `dcc.Location` `pathname` | ||
routing. | ||
That is, your usage may look like: | ||
``` | ||
app.layout = html.Div([ | ||
dcc.Location(id='url'), | ||
html.Div(id='content') | ||
]) | ||
@app.callback(Output('content', 'children'), [Input('url', 'pathname')]) | ||
def display_content(path): | ||
page_name = app.strip_relative_path(path) | ||
if not page_name: # None or '' | ||
return html.Div([ | ||
dcc.Link(href=app.get_relative_path('/page-1')), | ||
dcc.Link(href=app.get_relative_path('/page-2')), | ||
]) | ||
elif page_name == 'page-1': | ||
return chapters.page_1 | ||
if page_name == "page-2": | ||
return chapters.page_2 | ||
``` | ||
Note that `chapters.page_1` will be served if the user visits `/page-1` | ||
_or_ `/page-1/` since `strip_relative_path` removes the trailing slash. | ||
|
||
Also note that `strip_relative_path` is compatible with | ||
`get_relative_path` in environments where `requests_pathname_prefix` set. | ||
In some deployment environments, like Dash Enterprise, | ||
`requests_pathname_prefix` is set to the application name, e.g. `my-dash-app`. | ||
When working locally, `requests_pathname_prefix` might be unset and | ||
so a relative URL like `/page-2` can just be `/page-2`. | ||
However, when the app is deployed to a URL like `/my-dash-app`, then | ||
`app.get_relative_path('/page-2')` will return `/my-dash-app/page-2` | ||
|
||
The `pathname` property of `dcc.Location` will return '`/my-dash-app/page-2`' | ||
to the callback. | ||
In this case, `app.strip_relative_path('/my-dash-app/page-2')` | ||
will return `'page-2'` | ||
|
||
For nested URLs, slashes are still included: | ||
`app.strip_relative_path('/page-1/sub-page-1/')` will return | ||
`page-1/sub-page-1` | ||
``` | ||
""" | ||
return _strip_relative_path( | ||
self.config.requests_pathname_prefix, | ||
path, | ||
) | ||
|
||
def _setup_dev_tools(self, **kwargs): | ||
debug = kwargs.get("debug", False) | ||
dev_tools = self._dev_tools = _AttributeDict() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any reason we'd get here with
path
not starting withrequests_pathname
? Should it be an error if we do?And regardless of that, we should only remove
requests_pathname
at the beginning ofpath
, right? I mean, you could imagine an app hosted atapp/
with a pathapp/sub-app/
or whatever.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the
dcc.Location.pathname
usage, I can't imagine a case where we'd get a path that doesn't start withrequests_pathname
. We could error out.Good point re beginning of path. The only exception is if the prefix is
/my-dash-app/
but then the app might be served under/my-dash-app
anddcc.Location
might pass/my-dash-app
through. In Dash Enterprise, we redirect to the trailing slash version (e.g. https://dash-demo.plotly.host/ddk-oil-and-gas-demo redirects to https://dash-demo.plotly.host/ddk-oil-and-gas-demo/) but I could imagine that that redirect logic could inadvertently change in the future sometime.I updated the logic and added a test case for this in 685cd69