1616import base64
1717import traceback
1818from urllib .parse import urlparse
19+ from typing import Union
1920
2021import flask
2122
5253 to_json ,
5354 convert_to_AttributeDict ,
5455 gen_salt ,
56+ hooks_to_js_object ,
5557)
5658from . import _callback
5759from . import _get_paths
7072 _import_layouts_from_pages ,
7173)
7274from ._jupyter import jupyter_dash , JupyterDisplayMode
75+ from .types import RendererHooks
7376
7477# Add explicit mapping for map files
7578mimetypes .add_type ("application/json" , ".map" , True )
134137
135138
136139def _get_traceback (secret , error : Exception ):
137-
138140 try :
139141 # pylint: disable=import-outside-toplevel
140142 from werkzeug .debug import tbtools
@@ -339,6 +341,10 @@ class Dash:
339341
340342 :param add_log_handler: Automatically add a StreamHandler to the app logger
341343 if not added previously.
344+
345+ :param hooks: Extend Dash renderer functionality by passing a dictionary of
346+ javascript functions. To hook into the layout, use dict keys "layout_pre" and
347+ "layout_post". To hook into the callbacks, use keys "request_pre" and "request_post"
342348 """
343349
344350 _plotlyjs_url : str
@@ -375,6 +381,7 @@ def __init__( # pylint: disable=too-many-statements
375381 long_callback_manager = None ,
376382 background_callback_manager = None ,
377383 add_log_handler = True ,
384+ hooks : Union [RendererHooks , None ] = None ,
378385 ** obsolete ,
379386 ):
380387 _validate .check_obsolete (obsolete )
@@ -468,7 +475,7 @@ def __init__( # pylint: disable=too-many-statements
468475 self ._favicon = None
469476
470477 # default renderer string
471- self .renderer = "var renderer = new DashRenderer();"
478+ self .renderer = f "var renderer = new DashRenderer({ hooks_to_js_object ( hooks ) } );"
472479
473480 # static files from the packages
474481 self .css = Css (serve_locally )
@@ -1327,7 +1334,6 @@ def _setup_server(self):
13271334
13281335 # Copy over global callback data structures assigned with `dash.callback`
13291336 for k in list (_callback .GLOBAL_CALLBACK_MAP ):
1330-
13311337 if k in self .callback_map :
13321338 raise DuplicateCallback (
13331339 f"The callback `{ k } ` provided with `dash.callback` was already "
@@ -1354,7 +1360,6 @@ def _setup_server(self):
13541360
13551361 if cancels :
13561362 for cancel_input , manager in cancels .items ():
1357-
13581363 # pylint: disable=cell-var-from-loop
13591364 @self .callback (
13601365 Output (cancel_input .component_id , "id" ),
@@ -1745,7 +1750,6 @@ def enable_dev_tools(
17451750 _reload .watch_thread .start ()
17461751
17471752 if debug :
1748-
17491753 if jupyter_dash .active :
17501754 jupyter_dash .configure_callback_exception_handling (
17511755 self , dev_tools .prune_errors
@@ -1779,7 +1783,6 @@ def _after_request(response):
17791783 dash_total ["dur" ] = round ((time .time () - dash_total ["dur" ]) * 1000 )
17801784
17811785 for name , info in timing_information .items ():
1782-
17831786 value = name
17841787 if info .get ("desc" ) is not None :
17851788 value += f';desc="{ info ["desc" ]} "'
0 commit comments