Skip to content

Commit

Permalink
Merge pull request #2832 from plotly/add-startup-route-setup
Browse files Browse the repository at this point in the history
Add startup route setup
  • Loading branch information
danton267 committed Apr 19, 2024
2 parents d098e76 + 92f5fab commit 93195fa
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -6,6 +6,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).

## Added

- [#2832](https://github.com/plotly/dash/pull/2832) Add dash startup route setup on Dash init.
- [#2819](https://github.com/plotly/dash/pull/2819) Add dash subcomponents receive additional parameters passed by the parent component. Fixes [#2814](https://github.com/plotly/dash/issues/2814).
- [#2826](https://github.com/plotly/dash/pull/2826) When using Pages, allows for `app.title` and (new) `app.description` to be used as defaults for the page title and description. Fixes [#2811](https://github.com/plotly/dash/issues/2811).
- [#2795](https://github.com/plotly/dash/pull/2795) Allow list of components to be passed as layout.
Expand Down
35 changes: 35 additions & 0 deletions dash/dash.py
Expand Up @@ -372,6 +372,7 @@ class Dash:
"""

_plotlyjs_url: str
STARTUP_ROUTES: list = []

def __init__( # pylint: disable=too-many-statements
self,
Expand Down Expand Up @@ -556,6 +557,7 @@ def __init__( # pylint: disable=too-many-statements
"JupyterDash is deprecated, use Dash instead.\n"
"See https://dash.plotly.com/dash-in-jupyter for more details."
)
self.setup_startup_routes()

def init_app(self, app=None, **kwargs):
"""Initialize the parts of Dash that require a flask app."""
Expand Down Expand Up @@ -1626,6 +1628,39 @@ def display_content(path):
self.config.requests_pathname_prefix, path
)

@staticmethod
def add_startup_route(name, view_func, methods):
"""
Add a route to the app to be initialized at the end of Dash initialization.
Use this if the package requires a route to be added to the app, and you will not need to worry about at what point to add it.
:param name: The name of the route. eg "my-new-url/path".
:param view_func: The function to call when the route is requested. The function should return a JSON serializable object.
:param methods: The HTTP methods that the route should respond to. eg ["GET", "POST"] or either one.
"""
if not isinstance(name, str) or name.startswith("/"):
raise ValueError("name must be a string and should not start with '/'")

if not callable(view_func):
raise ValueError("view_func must be callable")

valid_methods = {"POST", "GET"}
if not set(methods).issubset(valid_methods):
raise ValueError(f"methods should only contain {valid_methods}")

if any(route[0] == name for route in Dash.STARTUP_ROUTES):
raise ValueError(f"Route name '{name}' is already in use.")

Dash.STARTUP_ROUTES.append((name, view_func, methods))

def setup_startup_routes(self):
"""
Initialize the startup routes stored in STARTUP_ROUTES.
"""
for _name, _view_func, _methods in self.STARTUP_ROUTES:
self._add_url(f"_dash_startup_route/{_name}", _view_func, _methods)
self.STARTUP_ROUTES = []

def _setup_dev_tools(self, **kwargs):
debug = kwargs.get("debug", False)
dev_tools = self._dev_tools = AttributeDict()
Expand Down
21 changes: 21 additions & 0 deletions tests/integration/test_integration.py
Expand Up @@ -511,3 +511,24 @@ def on_nested_click(n_clicks):

dash_duo.wait_for_element("#nested").click()
dash_duo.wait_for_text_to_equal("#nested-output", "Clicked 1 times")


def test_inin030_add_startup_route(dash_duo):
url = "my-new-route"

def my_route_f():
return "hello"

Dash.add_startup_route(url, my_route_f, ["POST"])

import requests

app = Dash(__name__)
Dash.STARTUP_ROUTES = []
app.layout = html.Div("Hello World")
dash_duo.start_server(app)

url = f"{dash_duo.server_url}{app.config.requests_pathname_prefix}_dash_startup_route/{url}"
response = requests.post(url)
assert response.status_code == 200
assert response.text == "hello"

0 comments on commit 93195fa

Please sign in to comment.