Repository: plotly/dash

Issue:
[BUG] + in version string breaks fingerprint system
**Describe your context**
-  replace the result of `pip list | grep dash` below
```
dash                                  1.5.1                           
dash-core-components                  1.4.0                           
dash-daq                              0.2.2                           
dash-html-components                  1.0.1                           
dash-renderer                         1.2.0                           
dash-table                            4.5.0  
```

**Describe the bug**

When going from `dash==1.4` to `dash==1.5`, we experienced a breaking change in the custom Dash components we use.

It took some hours to debug, but the reason was found to be related to the new "fingerprint" system in Dash. In our project, we use the [setuptools_scm](https://github.com/pypa/setuptools_scm) package (by the Python Packaging Authority) in order to have a versioning system that automatically is linked to the git repo tags. This makes continuous deployment to e.g. Pypi easy and robust wrt. keeping versions consistent.

I.e. instead of
```python
__version__ = package['version']
```
in the component package, we use something like
```
__version__ = get_distribution(__name__).version
```
This worked until `dash==1.5`, then it broke on non-release-versions due to automatic tags of the type
`1.0.0.dev5+af4304c.d20191103`, where the tag includes a `+`. See [the default tag formats](https://github.com/pypa/setuptools_scm#default-versioning-scheme).

Changing the line above to
```
__version__ = get_distribution(__name__).version.replace("+", ".")
```
is one workaround that gets the third party components to also work on `dash==1.5`

**Expected behavior**

`setuptools_scm` provided versions to work also in `dash>=1.5`.

**Suggested solution**

Change [this line](https://github.com/plotly/dash/blob/40b5357f262ac207f94ac980e6cb928d94df65b7/dash/fingerprint.py#L12) in Dash's `build_fingerprint` to also replace `+` with `_`?

Discussion:

Patch:
diff --git a/@plotly/webpack-dash-dynamic-import/src/index.js b/@plotly/webpack-dash-dynamic-import/src/index.js
index b02a7edc..6071c0a9 100644
--- a/@plotly/webpack-dash-dynamic-import/src/index.js
+++ b/@plotly/webpack-dash-dynamic-import/src/index.js
@@ -5,7 +5,7 @@ function getFingerprint() {
     const packageJson = JSON.parse(package);
 
     const timestamp = Math.round(Date.now() / 1000);
-    const version = packageJson.version.replace(/[.]/g, '_');
+    const version = packageJson.version.replace(/[^\w-]/g, '_');
 
     return `"v${version}m${timestamp}"`;
 }
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b0698b3a..b28ab05f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 
 ## Unreleased
 ### Fixed
+- [#999](https://github.com/plotly/dash/pull/999) Fix fingerprint for component suites with `metadata` in version.
 - [#983](https://github.com/plotly/dash/pull/983) Fix the assets loading issues when dashR application runner is handling with an app defined by string chunk.
 
 ## [1.5.1] - 2019-10-29
diff --git a/dash/fingerprint.py b/dash/fingerprint.py
index b9c23d30..78df1d49 100644
--- a/dash/fingerprint.py
+++ b/dash/fingerprint.py
@@ -1,7 +1,7 @@
 import re
 
 cache_regex = re.compile(r"^v[\w-]+m[0-9a-fA-F]+$")
-
+version_clean = re.compile(r"[^\w-]")
 
 def build_fingerprint(path, version, hash_value):
     path_parts = path.split("/")
@@ -9,7 +9,7 @@ def build_fingerprint(path, version, hash_value):
 
     return "{}.v{}m{}.{}".format(
         "/".join(path_parts[:-1] + [filename]),
-        str(version).replace(".", "_"),
+        re.sub(version_clean, "_", str(version)),
         hash_value,
         extension,
     )


Test patch:
diff --git a/tests/integration/devtools/test_props_check.py b/tests/integration/devtools/test_props_check.py
index 95d81349..b9d71556 100644
--- a/tests/integration/devtools/test_props_check.py
+++ b/tests/integration/devtools/test_props_check.py
@@ -216,7 +216,7 @@ test_cases = {
 
 
 def test_dvpc001_prop_check_errors_with_path(dash_duo):
-    app = dash.Dash(__name__)
+    app = dash.Dash(__name__, eager_loading=True)
 
     app.layout = html.Div([html.Div(id="content"), dcc.Location(id="location")])
 
diff --git a/tests/unit/test_fingerprint.py b/tests/unit/test_fingerprint.py
index 36de80dd..97037e7b 100644
--- a/tests/unit/test_fingerprint.py
+++ b/tests/unit/test_fingerprint.py
@@ -17,6 +17,12 @@ valid_resources = [
         "version": "1.1.1-alpha.1",
         "hash": "1234567890abcdef",
     },
+    {
+        "path": "react@16.8.6.min.js",
+        "fingerprint": "react@16.v1_1_1-alpha_x_y_y_X_Y_Z_1_2_3_metadata_xx_yy_zz_XX_YY_ZZ_11_22_33_mmm1234567890abcdefABCDEF.8.6.min.js",
+        "version": "1.1.1-alpha.x.y.y.X.Y.Z.1.2.3+metadata.xx.yy.zz.XX.YY.ZZ.11.22.33.mm",
+        "hash": "1234567890abcdefABCDEF",
+    },
     {"path": "dash.plotly.js", "fingerprint": "dash.v1m1.plotly.js"},
     {"path": "dash.plotly.j_s", "fingerprint": "dash.v1m1.plotly.j_s"},
     {"path": "dash.plotly.css", "fingerprint": "dash.v1m1.plotly.css"},