diff --git a/@plotly/webpack-dash-dynamic-import/package.json b/@plotly/webpack-dash-dynamic-import/package.json index 4b7a432ca1..0463d24f6f 100644 --- a/@plotly/webpack-dash-dynamic-import/package.json +++ b/@plotly/webpack-dash-dynamic-import/package.json @@ -1,6 +1,6 @@ { "name": "@plotly/webpack-dash-dynamic-import", - "version": "1.1.1", + "version": "1.1.2", "description": "Webpack Plugin for Dynamic Import in Dash", "repository": { "type": "git", diff --git a/@plotly/webpack-dash-dynamic-import/src/index.js b/@plotly/webpack-dash-dynamic-import/src/index.js index b02a7edc70..6071c0a97b 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 45a5adff95..fa6827a4b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to `dash` will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [1.6.0] - 2019-11-04 +### 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 ### Fixed - [#987](https://github.com/plotly/dash/pull/987) Fix cache string handling for component suites with nested folders in their packages. diff --git a/dash/fingerprint.py b/dash/fingerprint.py index b9c23d3034..78df1d4917 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, ) diff --git a/dash/testing/application_runners.py b/dash/testing/application_runners.py index 44eeb07959..2b23fa7b1f 100644 --- a/dash/testing/application_runners.py +++ b/dash/testing/application_runners.py @@ -5,6 +5,7 @@ import uuid import shlex import threading +import shutil import subprocess import logging import inspect @@ -61,6 +62,7 @@ def __init__(self, keep_open, stop_timeout): self.started = None self.keep_open = keep_open self.stop_timeout = stop_timeout + self._tmp_app_path = None def start(self, *args, **kwargs): raise NotImplementedError # pragma: no cover @@ -103,6 +105,10 @@ def url(self): def is_windows(self): return sys.platform == "win32" + @property + def tmp_app_path(self): + return self._tmp_app_path + class ThreadedRunner(BaseDashRunner): """Runs a dash application in a thread. @@ -261,13 +267,18 @@ def start(self, app, start_timeout=2, cwd=None): cwd = os.path.dirname(app) logger.info("RRunner inferred cwd from app path: %s", cwd) else: - path = ( - "/tmp/app_{}.R".format(uuid.uuid4().hex) - if not self.is_windows - else os.path.join( - (os.getenv("TEMP"), "app_{}.R".format(uuid.uuid4().hex)) - ) + self._tmp_app_path = os.path.join( + "/tmp" if not self.is_windows else os.getenv("TEMP"), + uuid.uuid4().hex, ) + try: + os.mkdir(self.tmp_app_path) + except OSError: + logger.exception( + "cannot make temporary folder %s", self.tmp_app_path + ) + path = os.path.join(self.tmp_app_path, "app.R") + logger.info("RRunner start => app is R code chunk") logger.info("make a temporary R file for execution => %s", path) logger.debug("content of the dashR app") @@ -283,11 +294,11 @@ def start(self, app, start_timeout=2, cwd=None): for entry in inspect.stack(): if "/dash/testing/" not in entry[1].replace("\\", "/"): cwd = os.path.dirname(os.path.realpath(entry[1])) + logger.warning("get cwd from inspect => %s", cwd) break if cwd: logger.info( - "RRunner inferred cwd from the Python call stack: %s", - cwd + "RRunner inferred cwd from the Python call stack: %s", cwd ) else: logger.warning( @@ -297,16 +308,40 @@ def start(self, app, start_timeout=2, cwd=None): "dashr.run_server(app, cwd=os.path.dirname(__file__))" ) + # try copying all valid sub folders (i.e. assets) in cwd to tmp + # note that the R assets folder name can be any valid folder name + assets = [ + os.path.join(cwd, _) + for _ in os.listdir(cwd) + if not _.startswith("__") + and os.path.isdir(os.path.join(cwd, _)) + ] + + for asset in assets: + target = os.path.join( + self.tmp_app_path, os.path.basename(asset) + ) + if os.path.exists(target): + logger.debug("delete existing target %s", target) + shutil.rmtree(target) + logger.debug("copying %s => %s", asset, self.tmp_app_path) + shutil.copytree(asset, target) + logger.debug("copied with %s", os.listdir(target)) + logger.info("Run dashR app with Rscript => %s", app) + args = shlex.split( - "Rscript {}".format(os.path.realpath(app)), + "Rscript -e 'source(\"{}\")'".format(os.path.realpath(app)), posix=not self.is_windows, ) logger.debug("start dash process with %s", args) try: self.proc = subprocess.Popen( - args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd + args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=self.tmp_app_path if self.tmp_app_path else cwd, ) # wait until server is able to answer http request wait.until( diff --git a/dash/version.py b/dash/version.py index 51ed7c486c..bcd8d54efb 100644 --- a/dash/version.py +++ b/dash/version.py @@ -1 +1 @@ -__version__ = '1.5.1' +__version__ = '1.6.0' diff --git a/requires-install.txt b/requires-install.txt index 346a7c4f34..89ae565139 100644 --- a/requires-install.txt +++ b/requires-install.txt @@ -2,7 +2,7 @@ Flask>=1.0.2 flask-compress plotly dash_renderer==1.2.0 -dash-core-components==1.4.0 +dash-core-components==1.5.0 dash-html-components==1.0.1 dash-table==4.5.0 future \ No newline at end of file diff --git a/tests/integration/devtools/test_props_check.py b/tests/integration/devtools/test_props_check.py index 95d81349ac..b9d7155660 100644 --- a/tests/integration/devtools/test_props_check.py +++ b/tests/integration/devtools/test_props_check.py @@ -216,7 +216,7 @@ 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 36de80ddcc..97037e7bb5 100644 --- a/tests/unit/test_fingerprint.py +++ b/tests/unit/test_fingerprint.py @@ -17,6 +17,12 @@ "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"},