diff --git a/CHANGELOG.md b/CHANGELOG.md
index b036f014cf..700fce7b78 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
### Fixed
- [#1963](https://github.com/plotly/dash/pull/1963) Fix [#1780](https://github.com/plotly/dash/issues/1780) flask shutdown deprecation warning when running dashduo threaded tests.
+- [#1995](https://github.com/plotly/dash/pull/1995) Fix [#1992](https://github.com/plotly/dash/issues/1992) ImportError: cannot import name 'get_current_traceback' from 'werkzeug.debug.tbtools'.
## [2.3.0] - 2022-03-13
diff --git a/dash/_utils.py b/dash/_utils.py
index 31de7e88cb..83b2895554 100644
--- a/dash/_utils.py
+++ b/dash/_utils.py
@@ -8,6 +8,8 @@
import logging
import io
import json
+import secrets
+import string
from functools import wraps
logger = logging.getLogger()
@@ -206,3 +208,9 @@ def _wrapper(*args, **kwargs):
return _wrapper
return wrapper
+
+
+def gen_salt(chars):
+ return "".join(
+ secrets.choice(string.ascii_letters + string.digits) for _ in range(chars)
+ )
diff --git a/dash/dash-renderer/src/components/error/FrontEnd/FrontEndError.react.js b/dash/dash-renderer/src/components/error/FrontEnd/FrontEndError.react.js
index 5703add4db..49939ea1de 100644
--- a/dash/dash-renderer/src/components/error/FrontEnd/FrontEndError.react.js
+++ b/dash/dash-renderer/src/components/error/FrontEnd/FrontEndError.react.js
@@ -110,7 +110,7 @@ function UnconnectedErrorContent({error, base}) {
)}
{/* Backend Error */}
{typeof error.html !== 'string' ? null : error.html.indexOf(
- '
diff --git a/dash/dash.py b/dash/dash.py
index c0b6cb410d..cc04b48b10 100644
--- a/dash/dash.py
+++ b/dash/dash.py
@@ -11,11 +11,12 @@
import mimetypes
import hashlib
import base64
+import traceback
from urllib.parse import urlparse
import flask
from flask_compress import Compress
-from werkzeug.debug.tbtools import get_current_traceback
+
from pkg_resources import get_distribution, parse_version
from dash import dcc
from dash import html
@@ -48,6 +49,7 @@
patch_collections_abc,
split_callback_id,
to_json,
+ gen_salt,
)
from . import _callback
from . import _get_paths
@@ -102,6 +104,42 @@
_re_renderer_scripts_id = 'id="_dash-renderer', "new DashRenderer"
+def _get_traceback(secret, error: Exception):
+
+ try:
+ # pylint: disable=import-outside-toplevel
+ from werkzeug.debug import tbtools
+ except ImportError:
+ tbtools = None
+
+ def _get_skip(text, divider=2):
+ skip = 0
+ for i, line in enumerate(text):
+ if "%% callback invoked %%" in line:
+ skip = int((i + 1) / divider)
+ break
+ return skip
+
+ # werkzeug<2.1.0
+ if hasattr(tbtools, "get_current_traceback"):
+ tb = tbtools.get_current_traceback()
+ skip = _get_skip(tb.plaintext.splitlines())
+ return tbtools.get_current_traceback(skip=skip).render_full()
+
+ if hasattr(tbtools, "DebugTraceback"):
+ tb = tbtools.DebugTraceback(error) # pylint: disable=no-member
+ skip = _get_skip(tb.render_traceback_text().splitlines())
+
+ # pylint: disable=no-member
+ return tbtools.DebugTraceback(error, skip=skip).render_debugger_html(
+ True, secret, True
+ )
+
+ tb = traceback.format_exception(type(error), error, error.__traceback__)
+ skip = _get_skip(tb, 1)
+ return tb[0] + "".join(tb[skip:])
+
+
class _NoUpdate:
# pylint: disable=too-few-public-methods
pass
@@ -1756,19 +1794,16 @@ def enable_dev_tools(
if debug and dev_tools.prune_errors:
+ secret = gen_salt(20)
+
@self.server.errorhandler(Exception)
- def _wrap_errors(_):
+ def _wrap_errors(error):
# find the callback invocation, if the error is from a callback
# and skip the traceback up to that point
# if the error didn't come from inside a callback, we won't
# skip anything.
- tb = get_current_traceback()
- skip = 0
- for i, line in enumerate(tb.plaintext.splitlines()):
- if "%% callback invoked %%" in line:
- skip = int((i + 1) / 2)
- break
- return get_current_traceback(skip=skip).render_full(), 500
+ tb = _get_traceback(secret, error)
+ return tb, 500
if debug and dev_tools.ui: