Skip to content

Commit 715dae8

Browse files
authored
Merge pull request #3798 from plotly/fix/init-flask-run-command
fix blueprint registering and double init
2 parents 3ef77c5 + 8cdf6fc commit 715dae8

3 files changed

Lines changed: 53 additions & 1 deletion

File tree

dash/backends/_quart.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@ def create_app(
122122
def register_assets_blueprint(
123123
self, blueprint_name: str, assets_url_path: str, assets_folder: str # type: ignore[name-defined]
124124
):
125-
126125
bp = Blueprint(
127126
blueprint_name,
128127
__name__,

dash/dash.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@
135135
_ID_STORE = "_pages_store"
136136
_ID_DUMMY = "_pages_dummy"
137137

138+
_UNINITIALIZED = object() # Sentinel for tracking init_app state
139+
138140
DASH_VERSION_URL = "https://dash-version.plotly.com:8080/current_version"
139141

140142
# Handles the case in a newly cloned environment where the components are not yet generated.
@@ -731,6 +733,17 @@ def init_app(self, app: Optional[Any] = None, **kwargs) -> None:
731733
)
732734
if app is not None:
733735
self.server = app
736+
# Also update the backend's server reference so routes are registered
737+
# on the correct server (important when using server=False pattern)
738+
self.backend.server = app
739+
740+
# Skip registration if already initialized on this server
741+
# This prevents double registration when init_app() is called multiple times
742+
# (e.g., with flask run pattern where __init__ calls init_app, then user does too)
743+
if getattr(self, "_initialized_server", _UNINITIALIZED) is self.server:
744+
return
745+
self._initialized_server = self.server
746+
734747
bp_prefix = config.routes_pathname_prefix.replace("/", "_").replace(".", "_")
735748
assets_blueprint_name = f"{bp_prefix}dash_assets"
736749
self.backend.register_assets_blueprint(

tests/unit/test_configs.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,3 +524,43 @@ def test_csrf_config_read_only():
524524
app.config.csrf_token_name = "something_else"
525525
with pytest.raises(AttributeError):
526526
app.config.csrf_header_name = "something_else"
527+
528+
529+
def test_init_app_with_flask_run_pattern():
530+
"""Test that init_app works correctly with the flask run pattern.
531+
532+
This tests the fix for https://github.com/plotly/dash/issues/3787
533+
where using flask run would cause a ValueError about duplicate blueprint
534+
registration because init_app was called twice (once automatically in
535+
__init__ with server=True, and once by the user in their create_app factory).
536+
"""
537+
# Simulate the flask run pattern where:
538+
# 1. Dash app is created with server=True (default)
539+
# 2. User's create_app factory calls init_app with their Flask server
540+
external_server = Flask("external_test")
541+
app = Dash(__name__)
542+
543+
# This should NOT raise "ValueError: The name '_dash_assets' is already registered"
544+
app.init_app(external_server)
545+
546+
# Verify the backend now uses the external server
547+
assert app.server is external_server
548+
assert app.backend.server is external_server
549+
550+
551+
def test_init_app_server_false_pattern():
552+
"""Test that init_app works correctly when server=False is used.
553+
554+
This tests the fix for https://github.com/plotly/dash/issues/3787
555+
where using server=False and then calling init_app would result in
556+
404 errors because the backend's server reference was not updated.
557+
"""
558+
external_server = Flask("external_test_false")
559+
app = Dash(__name__, server=False)
560+
561+
# Call init_app with the external server
562+
app.init_app(external_server)
563+
564+
# Verify both the app and backend use the external server
565+
assert app.server is external_server
566+
assert app.backend.server is external_server

0 commit comments

Comments
 (0)