Repository: plotly/dash

Issue:
[BUG] Handle missing `timing_information` gracefully
**Describe your context**
```
dash                      1.17.0                  
dash-core-components      1.13.0             
dash-html-components      1.1.1              
dash-renderer             1.8.3              
```

**Describe the bug**
An error is raised when the devtools before request is not fully executed. 
```AttributeError: '_AppCtxGlobals' object has no attribute 'timing_information'``` 
In our case this happens because we register our own "before request" to the flask instance, to handle user logins. Flask will not execute any other `before_request_funcs` when a function returns something other than None. If an user is not logged in, we will throw a redirect to the login page.

So during the "before_request" phase the `timing_information` is not set. During the "after_request" phase, it expects `timing_information` was set. If not it will throw an error.

The error is caused by these lines: https://github.com/plotly/dash/blob/master/dash/dash.py#L1465-L1489

**Expected behavior**
It should check if the `timing_information` is set, and not expect it is. If it is not it gracefully should handle the error.



Discussion:
Similar to #1475?
Indeed it is a duplicate of #1475
I will close this issue, and create a PR to fix it

Resolving patch:
diff --git a/dash/dash.py b/dash/dash.py
--- a/dash/dash.py
+++ b/dash/dash.py
@@ -1468,10 +1468,15 @@ def _before_request():
                 }
 
             def _after_request(response):
-                dash_total = flask.g.timing_information["__dash_server"]
-                dash_total["dur"] = round((time.time() - dash_total["dur"]) * 1000)
+                timing_information = flask.g.get("timing_information", None)
+                if timing_information is None:
+                    return response
 
-                for name, info in flask.g.timing_information.items():
+                dash_total = timing_information.get("__dash_server", None)
+                if dash_total is not None:
+                    dash_total["dur"] = round((time.time() - dash_total["dur"]) * 1000)
+
+                for name, info in timing_information.items():
 
                     value = name
                     if info.get("desc") is not None:


Test patch:
diff --git a/tests/integration/devtools/test_devtools_ui.py b/tests/integration/devtools/test_devtools_ui.py
--- a/tests/integration/devtools/test_devtools_ui.py
+++ b/tests/integration/devtools/test_devtools_ui.py
@@ -1,4 +1,5 @@
 from time import sleep
+import flask
 
 import dash_core_components as dcc
 import dash_html_components as html
@@ -224,3 +225,38 @@ def set_b(a):
 
     dash_duo.wait_for_text_to_equal("#b", "xyz")
     dash_duo.wait_for_no_elements("._dash-undo-redo")
+
+
+def test_dvui007_other_before_request_func(dash_thread_server, dash_br):
+    # won't use `bash_br`, because it expects an dash app, but it gets an static html page.
+    # we take only the selenium driver from `bash_br`, this driver has already been set-up.
+    driver = dash_br.driver
+
+    app = dash.Dash(__name__)
+    app.layout = html.Div(
+        [html.P(id="just_an_id", children="You should never see this")]
+    )
+
+    # create alternative response, for the endpoint '/'
+    # servering an alternative response, will disable further `before_request` functions e.g. those by dash
+    @app.server.before_request
+    def create_an_alternative_response():
+        if flask.request.endpoint == "/":
+            return flask.Response(
+                '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\n'
+                "<title>Alternative repsonse</title>\n"
+                '<h1 id="alternative_id">Alternative response header</h1>\n',
+                200,
+                mimetype="text/html",
+            )
+
+    dash_thread_server.start(
+        app,
+        debug=True,
+        use_reloader=False,
+        use_debugger=True,
+        dev_tools_hot_reload=False,
+    )
+
+    driver.get(dash_thread_server.url)
+    driver.find_element_by_id("alternative_id")