|
2 | 2 | import requests
|
3 | 3 | import pytest
|
4 | 4 |
|
5 |
| -from dash import Dash, Input, Output, html, hooks, set_props, ctx |
| 5 | +from dash import Dash, Input, Output, html, hooks, set_props, ctx, get_app |
6 | 6 |
|
7 | 7 |
|
8 | 8 | @pytest.fixture
|
@@ -240,3 +240,103 @@ def cb(_):
|
240 | 240 | )
|
241 | 241 | dash_duo.wait_for_element("#devtool").click()
|
242 | 242 | dash_duo.wait_for_text_to_equal("#output", "hooked from devtools")
|
| 243 | + |
| 244 | + |
| 245 | +def test_hook012_get_app_available_in_hooks_on_routes(hook_cleanup, dash_duo): |
| 246 | + """Test that get_app() is available during hooks when @with_app_context decorated routes are called.""" |
| 247 | + |
| 248 | + # Track which hooks were able to access get_app() |
| 249 | + hook_access_results = { |
| 250 | + "layout_hook": False, |
| 251 | + "error_hook": False, |
| 252 | + "callback_hook": False, |
| 253 | + } |
| 254 | + |
| 255 | + @hooks.layout() |
| 256 | + def layout_hook(layout): |
| 257 | + try: |
| 258 | + retrieved_app = get_app() |
| 259 | + hook_access_results["layout_hook"] = retrieved_app is not None |
| 260 | + except Exception: |
| 261 | + hook_access_results["layout_hook"] = False |
| 262 | + return layout |
| 263 | + |
| 264 | + @hooks.error() |
| 265 | + def error_hook(error): |
| 266 | + try: |
| 267 | + retrieved_app = get_app() |
| 268 | + hook_access_results["error_hook"] = retrieved_app is not None |
| 269 | + except Exception: |
| 270 | + hook_access_results["error_hook"] = False |
| 271 | + |
| 272 | + @hooks.callback( |
| 273 | + Output("hook-output", "children"), |
| 274 | + Input("hook-button", "n_clicks"), |
| 275 | + prevent_initial_call=True, |
| 276 | + ) |
| 277 | + def callback_hook(n_clicks): |
| 278 | + try: |
| 279 | + retrieved_app = get_app() |
| 280 | + hook_access_results["callback_hook"] = retrieved_app is not None |
| 281 | + return f"Hook callback: {n_clicks}" |
| 282 | + except Exception as err: |
| 283 | + hook_access_results["callback_hook"] = False |
| 284 | + return f"Error in hook callback: {err}" |
| 285 | + |
| 286 | + app = Dash(__name__) |
| 287 | + app.layout = [ |
| 288 | + html.Div("Test get_app in hooks", id="main"), |
| 289 | + html.Button("Click for callback", id="button"), |
| 290 | + html.Div(id="output"), |
| 291 | + html.Button("Hook callback", id="hook-button"), |
| 292 | + html.Div(id="hook-output"), |
| 293 | + html.Button("Error", id="error-btn"), |
| 294 | + html.Div(id="error-output"), |
| 295 | + ] |
| 296 | + |
| 297 | + @app.callback( |
| 298 | + Output("output", "children"), |
| 299 | + Input("button", "n_clicks"), |
| 300 | + prevent_initial_call=True, |
| 301 | + ) |
| 302 | + def test_callback(n_clicks): |
| 303 | + return f"Clicked {n_clicks} times" |
| 304 | + |
| 305 | + @app.callback( |
| 306 | + Output("error-output", "children"), |
| 307 | + Input("error-btn", "n_clicks"), |
| 308 | + prevent_initial_call=True, |
| 309 | + ) |
| 310 | + def error_callback(n_clicks): |
| 311 | + if n_clicks: |
| 312 | + raise ValueError("Test error for hook") |
| 313 | + return "" |
| 314 | + |
| 315 | + dash_duo.start_server(app) |
| 316 | + |
| 317 | + # Test the @with_app_context decorated routes |
| 318 | + |
| 319 | + # 2. Test layout hook via index route (GET /) |
| 320 | + dash_duo.wait_for_text_to_equal("#main", "Test get_app in hooks") |
| 321 | + |
| 322 | + # 3. Test callback hook via dispatch route (POST /_dash-update-component) |
| 323 | + dash_duo.wait_for_element("#hook-button").click() |
| 324 | + dash_duo.wait_for_text_to_equal("#hook-output", "Hook callback: 1") |
| 325 | + |
| 326 | + # 4. Test error hook via dispatch route when error occurs |
| 327 | + dash_duo.wait_for_element("#error-btn").click() |
| 328 | + # Give error hook time to execute |
| 329 | + import time |
| 330 | + |
| 331 | + time.sleep(0.5) |
| 332 | + |
| 333 | + # Verify that get_app() worked in hooks during route calls with @with_app_context |
| 334 | + assert hook_access_results[ |
| 335 | + "layout_hook" |
| 336 | + ], "get_app() should be accessible in layout hook when serve_layout/index routes have @with_app_context" |
| 337 | + assert hook_access_results[ |
| 338 | + "callback_hook" |
| 339 | + ], "get_app() should be accessible in callback hook when dispatch route has @with_app_context" |
| 340 | + assert hook_access_results[ |
| 341 | + "error_hook" |
| 342 | + ], "get_app() should be accessible in error hook when dispatch route has @with_app_context" |
0 commit comments