From 335e03873132abe45688e4ea8d377b4c6a6750bd Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Thu, 9 Aug 2018 16:42:54 -0400 Subject: [PATCH 01/29] Add reload route and renderer config. --- dash/dash.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/dash/dash.py b/dash/dash.py index 8c3c7d4221..30c90f7ca8 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -6,6 +6,7 @@ import importlib import json import pkgutil +import uuid import warnings import re @@ -102,6 +103,8 @@ def __init__( ) self._assets_url_path = assets_url_path + self._reload_hash = str(uuid.uuid4().hex).strip('-') + # allow users to supply their own flask server self.server = server or Flask(name, static_folder=static_folder) @@ -202,6 +205,9 @@ def _handle_error(error): self.config['routes_pathname_prefix'], self.index) + add_url('{}_reload-hash'.format(self.config['routes_pathname_prefix']), + self.serve_reload_hash) + # catch-all for front-end routes, used by dcc.Location self._add_url( '{}'.format(self.config['routes_pathname_prefix']), @@ -294,10 +300,14 @@ def serve_layout(self): def _config(self): return { + 'reload_hash': self._reload_hash, 'url_base_pathname': self.url_base_pathname, 'requests_pathname_prefix': self.config['requests_pathname_prefix'] } + def serve_reload_hash(self): + return flask.jsonify({'reloadHash': self._reload_hash}) + def serve_routes(self): return flask.Response( json.dumps(self.routes, From 572353378e79fa916fc717beaaf8dc789caee2f4 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Thu, 9 Aug 2018 17:18:02 -0400 Subject: [PATCH 02/29] Add reload interval config. --- dash/dash.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/dash/dash.py b/dash/dash.py index 30c90f7ca8..975a471e39 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -84,6 +84,8 @@ def __init__( index_string=_default_index, external_scripts=None, external_stylesheets=None, + hot_reload=False, + hot_reload_interval=1000, suppress_callback_exceptions=None, components_cache_max_age=None, **kwargs): @@ -103,7 +105,9 @@ def __init__( ) self._assets_url_path = assets_url_path - self._reload_hash = str(uuid.uuid4().hex).strip('-') + self._reload_hash = str(uuid.uuid4().hex).strip('-')\ + if hot_reload else '' + self._reload_interval = hot_reload_interval # allow users to supply their own flask server self.server = server or Flask(name, static_folder=static_folder) @@ -299,11 +303,16 @@ def serve_layout(self): ) def _config(self): - return { - 'reload_hash': self._reload_hash, + config = { 'url_base_pathname': self.url_base_pathname, 'requests_pathname_prefix': self.config['requests_pathname_prefix'] } + if self._reload_hash: + config['hot_reload'] = { + 'hash': self._reload_hash, + 'interval': self._reload_interval + } + return config def serve_reload_hash(self): return flask.jsonify({'reloadHash': self._reload_hash}) From 5b10597b4e168173eda7be313bdd8b280cf1b2b0 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Wed, 15 Aug 2018 17:44:39 -0400 Subject: [PATCH 03/29] Add assets filewatcher to reload on assets changes. --- dash/_utils.py | 8 +++++++- dash/_watch.py | 26 ++++++++++++++++++++++++++ dash/dash.py | 33 +++++++++++++++++++++++++++------ 3 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 dash/_watch.py diff --git a/dash/_utils.py b/dash/_utils.py index 17dc247ebe..811da08ebe 100644 --- a/dash/_utils.py +++ b/dash/_utils.py @@ -1,3 +1,6 @@ +import uuid + + def interpolate_str(template, **data): s = template for k, v in data.items(): @@ -20,12 +23,15 @@ def format_tag(tag_name, attributes, inner='', closed=False, opened=False): '{}="{}"'.format(k, v) for k, v in attributes.items()])) +def generate_hash(): + return str(uuid.uuid4().hex).strip('-') + + def get_asset_path( requests_pathname, routes_pathname, asset_path, asset_url_path): - i = requests_pathname.rfind(routes_pathname) req = requests_pathname[:i] diff --git a/dash/_watch.py b/dash/_watch.py new file mode 100644 index 0000000000..e165cf0e33 --- /dev/null +++ b/dash/_watch.py @@ -0,0 +1,26 @@ +import collections +import os +import re +import time + + +def watch(folders, on_change, pattern=None, sleep_time=0.1): + pattern = re.compile(pattern) if pattern else None + watched = collections.defaultdict(lambda: -1) + + def walk(): + for folder in folders: + for current, _, files, in os.walk(folder): + for f in files: + if pattern and not pattern.search(f): + continue + path = os.path.join(current, f) + info = os.stat(path) + new_time = info.st_mtime + if new_time > watched[path] > 0: + on_change(path) + watched[path] = new_time + + while True: + walk() + time.sleep(sleep_time) diff --git a/dash/dash.py b/dash/dash.py index 975a471e39..e738c188af 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -6,7 +6,7 @@ import importlib import json import pkgutil -import uuid +import threading import warnings import re @@ -25,6 +25,8 @@ from ._utils import AttributeDict as _AttributeDict from ._utils import interpolate_str as _interpolate from ._utils import format_tag as _format_tag +from ._utils import generate_hash as _generate_hash +from . import _watch from ._utils import get_asset_path as _get_asset_path from . import _configs @@ -105,10 +107,6 @@ def __init__( ) self._assets_url_path = assets_url_path - self._reload_hash = str(uuid.uuid4().hex).strip('-')\ - if hot_reload else '' - self._reload_interval = hot_reload_interval - # allow users to supply their own flask server self.server = server or Flask(name, static_folder=static_folder) @@ -233,6 +231,20 @@ def _handle_error(error): self.server.errorhandler(exceptions.InvalidResourceError)( self._invalid_resources_handler) + # hot reload + self._reload_hash = _generate_hash() \ + if hot_reload else '' + self._reload_interval = hot_reload_interval + self._hard_reload = False + self._lock = threading.RLock() + self._watch_thread = None + if hot_reload: + self._watch_thread = threading.Thread( + target=lambda: _watch.watch([self._assets_folder], + self._on_assets_change, + sleep_time=0.5)) + self._watch_thread.start() + def _add_url(self, name, view_func, methods=('GET',)): self.server.add_url_rule( name, @@ -315,7 +327,10 @@ def _config(self): return config def serve_reload_hash(self): - return flask.jsonify({'reloadHash': self._reload_hash}) + return flask.jsonify({ + 'reloadHash': self._reload_hash, + 'hard': self._hard_reload + }) def serve_routes(self): return flask.Response( @@ -1054,6 +1069,12 @@ def enable_dev_tools(self, ) return debug + def _on_assets_change(self, _): + self._lock.acquire() + self._hard_reload = True + self._reload_hash = _generate_hash() + self._lock.release() + def run_server(self, port=8050, debug=False, From cc6c21cc191e61d09e829c23b6dccdb8be97ca87 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Fri, 31 Aug 2018 16:54:16 -0400 Subject: [PATCH 04/29] Always set debug to true when hot_reload=True. Remove hash from configs. --- dash/dash.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dash/dash.py b/dash/dash.py index e738c188af..0e96a636fc 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -87,7 +87,7 @@ def __init__( external_scripts=None, external_stylesheets=None, hot_reload=False, - hot_reload_interval=1000, + hot_reload_interval=3000, suppress_callback_exceptions=None, components_cache_max_age=None, **kwargs): @@ -243,8 +243,12 @@ def _handle_error(error): target=lambda: _watch.watch([self._assets_folder], self._on_assets_change, sleep_time=0.5)) + self._watch_thread.daemon = True self._watch_thread.start() + if not self.server.debug: + self.server.debug = True + def _add_url(self, name, view_func, methods=('GET',)): self.server.add_url_rule( name, @@ -321,7 +325,6 @@ def _config(self): } if self._reload_hash: config['hot_reload'] = { - 'hash': self._reload_hash, 'interval': self._reload_interval } return config From 493f99d248693858591eaa18061fe66210c08a71 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Wed, 12 Sep 2018 10:41:18 -0400 Subject: [PATCH 05/29] Add comment on the filename in _on_assets_change. --- dash/dash.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dash/dash.py b/dash/dash.py index 0e96a636fc..fe0d7a0787 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -1073,6 +1073,8 @@ def enable_dev_tools(self, return debug def _on_assets_change(self, _): + # The `_` argument is the name of the file that changed. + # If we ever setup a logging system, we could use the parameter. self._lock.acquire() self._hard_reload = True self._reload_hash = _generate_hash() From 23882f532a4e48d7922a747ddacecad24b8b73b4 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Wed, 12 Sep 2018 10:56:49 -0400 Subject: [PATCH 06/29] Reset hard reload to false when serving the hash. --- dash/dash.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dash/dash.py b/dash/dash.py index fe0d7a0787..10615873cd 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -330,9 +330,14 @@ def _config(self): return config def serve_reload_hash(self): + hard = self._hard_reload + self._lock.acquire() + self._hard_reload = False + self._lock.release() + return flask.jsonify({ 'reloadHash': self._reload_hash, - 'hard': self._hard_reload + 'hard': hard }) def serve_routes(self): From ab533302927e86123ae0c7a06b55e9b3da445176 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Thu, 13 Sep 2018 16:49:58 -0400 Subject: [PATCH 07/29] Fix activate debug when hot_reload is true. --- dash/dash.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dash/dash.py b/dash/dash.py index 10615873cd..0b57208b39 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -238,6 +238,8 @@ def _handle_error(error): self._hard_reload = False self._lock = threading.RLock() self._watch_thread = None + self._hot_reload = hot_reload + if hot_reload: self._watch_thread = threading.Thread( target=lambda: _watch.watch([self._assets_folder], From 13202e5d94a1161dec3924ed2a9f4e1cd09e9b0d Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Thu, 13 Sep 2018 16:51:27 -0400 Subject: [PATCH 08/29] Disable pylint37 too-many-statements. --- .pylintrc37 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.pylintrc37 b/.pylintrc37 index 8ff5bee0db..57c45836cd 100644 --- a/.pylintrc37 +++ b/.pylintrc37 @@ -146,7 +146,8 @@ disable=invalid-name, no-else-return, useless-object-inheritance, possibly-unused-variable, - too-many-lines + too-many-lines, + too-many-statements # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option From 94bf7c08ea9a23b81947231c4c2def48a31ae041 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Fri, 14 Sep 2018 14:06:03 -0400 Subject: [PATCH 09/29] Hot-reload on adding/removing assets files. --- dash/_watch.py | 12 +++++++++- dash/dash.py | 60 +++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 58 insertions(+), 14 deletions(-) diff --git a/dash/_watch.py b/dash/_watch.py index e165cf0e33..ebaf8cc0fd 100644 --- a/dash/_watch.py +++ b/dash/_watch.py @@ -9,17 +9,27 @@ def watch(folders, on_change, pattern=None, sleep_time=0.1): watched = collections.defaultdict(lambda: -1) def walk(): + walked = [] for folder in folders: for current, _, files, in os.walk(folder): for f in files: if pattern and not pattern.search(f): continue path = os.path.join(current, f) + info = os.stat(path) new_time = info.st_mtime + if new_time > watched[path] > 0: - on_change(path) + on_change(path, new_time, False) + watched[path] = new_time + walked.append(path) + + # Look for deleted files + for w in (x for x in watched.keys() if x not in walked): + del watched[w] + on_change(w, -1, True) while True: walk() diff --git a/dash/dash.py b/dash/dash.py index 0b57208b39..8f028685c9 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -231,6 +231,8 @@ def _handle_error(error): self.server.errorhandler(exceptions.InvalidResourceError)( self._invalid_resources_handler) + self._assets_files = [] + # hot reload self._reload_hash = _generate_hash() \ if hot_reload else '' @@ -989,19 +991,20 @@ def _setup_server(self): self._generate_scripts_html() self._generate_css_dist_html() + def _add_assets_resource(self, url_path, file_path): + res = {'asset_path': url_path, 'filepath': file_path} + if self.config.assets_external_path: + res['external_url'] = '{}{}'.format( + self.config.assets_external_path, url_path) + self._assets_files.append(file_path) + return res + def _walk_assets_directory(self): walk_dir = self._assets_folder slash_splitter = re.compile(r'[\\/]+') ignore_filter = re.compile(self.assets_ignore) \ if self.assets_ignore else None - def add_resource(p, filepath): - res = {'asset_path': p, 'filepath': filepath} - if self.config.assets_external_path: - res['external_url'] = '{}{}'.format( - self.config.assets_external_path, path) - return res - for current, _, files in os.walk(walk_dir): if current == walk_dir: base = '' @@ -1026,9 +1029,9 @@ def add_resource(p, filepath): if f.endswith('js'): self.scripts.append_script( - add_resource(path, full)) + self._add_assets_resource(path, full)) elif f.endswith('css'): - self.css.append_css(add_resource(path, full)) + self.css.append_css(self._add_assets_resource(path, full)) elif f == 'favicon.ico': self._favicon = path @@ -1079,12 +1082,43 @@ def enable_dev_tools(self, ) return debug - def _on_assets_change(self, _): - # The `_` argument is the name of the file that changed. - # If we ever setup a logging system, we could use the parameter. + # noinspection PyProtectedMember + def _on_assets_change(self, filename, _, deleted): + # The `_` argument is the time modified, to be used later. self._lock.acquire() - self._hard_reload = True + self._hard_reload = True # filename.endswith('js') self._reload_hash = _generate_hash() + + asset_path = filename.replace(self._assets_folder, '')\ + .replace('\\', '/').lstrip('/') + + if filename not in self._assets_files and not deleted: + res = self._add_assets_resource(asset_path, filename) + if filename.endswith('js'): + self.scripts.append_script(res) + elif filename.endswith('css'): + self.css.append_css(res) + + if deleted: + if filename in self._assets_files: + self._assets_files.remove(filename) + + def delete_resource(resources): + to_delete = None + for r in resources: + if r.get('asset_path') == asset_path: + to_delete = r + break + if to_delete: + resources.remove(to_delete) + + if filename.endswith('js'): + # pylint: disable=protected-access + delete_resource(self.scripts._resources._resources) + elif filename.endswith('css'): + # pylint: disable=protected-access + delete_resource(self.css._resources._resources) + self._lock.release() def run_server(self, From 7c77bc8ce1f15d6f38006c150f74b6b369970604 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Fri, 14 Sep 2018 14:51:29 -0400 Subject: [PATCH 10/29] Add package list to the reload response. --- dash/dash.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dash/dash.py b/dash/dash.py index 8f028685c9..352299f467 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -341,7 +341,8 @@ def serve_reload_hash(self): return flask.jsonify({ 'reloadHash': self._reload_hash, - 'hard': hard + 'hard': hard, + 'packages': self.registered_paths.keys() }) def serve_routes(self): From 8c993fa82c2cf0b6cbe836e99ffe4567eebb6bb8 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Mon, 17 Sep 2018 14:23:43 -0400 Subject: [PATCH 11/29] Support reloading css only. --- dash/dash.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/dash/dash.py b/dash/dash.py index 352299f467..794cc76aef 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -241,6 +241,7 @@ def _handle_error(error): self._lock = threading.RLock() self._watch_thread = None self._hot_reload = hot_reload + self._changed_assets = [] if hot_reload: self._watch_thread = threading.Thread( @@ -335,14 +336,17 @@ def _config(self): def serve_reload_hash(self): hard = self._hard_reload + changed = self._changed_assets self._lock.acquire() self._hard_reload = False + self._changed_assets = [] self._lock.release() return flask.jsonify({ 'reloadHash': self._reload_hash, 'hard': hard, - 'packages': self.registered_paths.keys() + 'packages': self.registered_paths.keys(), + 'files': list(changed) }) def serve_routes(self): @@ -1084,15 +1088,20 @@ def enable_dev_tools(self, return debug # noinspection PyProtectedMember - def _on_assets_change(self, filename, _, deleted): - # The `_` argument is the time modified, to be used later. + def _on_assets_change(self, filename, modified, deleted): self._lock.acquire() - self._hard_reload = True # filename.endswith('js') + self._hard_reload = True self._reload_hash = _generate_hash() asset_path = filename.replace(self._assets_folder, '')\ .replace('\\', '/').lstrip('/') + self._changed_assets.append({ + 'url': self.get_asset_url(asset_path), + 'modified': int(modified), + 'is_css': filename.endswith('css') + }) + if filename not in self._assets_files and not deleted: res = self._add_assets_resource(asset_path, filename) if filename.endswith('js'): From 631da7bea177fc175fbafc4cd455b05e690783ab Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Tue, 18 Sep 2018 12:51:39 -0400 Subject: [PATCH 12/29] Add configurable watch_interval. --- dash/dash.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dash/dash.py b/dash/dash.py index 794cc76aef..25e6a53832 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -88,6 +88,7 @@ def __init__( external_stylesheets=None, hot_reload=False, hot_reload_interval=3000, + hot_reload_watch_interval=0.5, suppress_callback_exceptions=None, components_cache_max_age=None, **kwargs): @@ -245,9 +246,11 @@ def _handle_error(error): if hot_reload: self._watch_thread = threading.Thread( - target=lambda: _watch.watch([self._assets_folder], - self._on_assets_change, - sleep_time=0.5)) + target=lambda: _watch.watch( + [self._assets_folder], + self._on_assets_change, + sleep_time=hot_reload_watch_interval) + ) self._watch_thread.daemon = True self._watch_thread.start() From 08bbdc13ac0c424a29a75c6671f9c22d76118d35 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Fri, 21 Sep 2018 13:40:29 -0400 Subject: [PATCH 13/29] Integrate hot reload with enable_dev_tools. --- dash/_configs.py | 5 +++- dash/dash.py | 59 ++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/dash/_configs.py b/dash/_configs.py index e5087bd534..e16f89a6e3 100644 --- a/dash/_configs.py +++ b/dash/_configs.py @@ -22,7 +22,10 @@ def env_configs(): 'DASH_COMPONENTS_CACHE_MAX_AGE', 'DASH_INCLUDE_ASSETS_FILES', 'DASH_SERVE_DEV_BUNDLES', - 'DASH_DEBUG' + 'DASH_DEBUG', + 'DASH_HOT_RELOAD', + 'DASH_HOT_RELOAD_INTERVAL', + 'DASH_HOT_RELOAD_WATCH_INTERVAL' )}) diff --git a/dash/dash.py b/dash/dash.py index 25e6a53832..ac72f92242 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -86,9 +86,6 @@ def __init__( index_string=_default_index, external_scripts=None, external_stylesheets=None, - hot_reload=False, - hot_reload_interval=3000, - hot_reload_watch_interval=0.5, suppress_callback_exceptions=None, components_cache_max_age=None, **kwargs): @@ -225,7 +222,10 @@ def _handle_error(error): self._layout = None self._cached_layout = None self._dev_tools = _AttributeDict({ - 'serve_dev_bundles': False + 'serve_dev_bundles': False, + 'hot_reload': False, + 'hot_reload_interval': 3000, + 'hot_reload_watch_interval': 0.5 }) # add a handler for components suites errors to return 404 @@ -235,13 +235,10 @@ def _handle_error(error): self._assets_files = [] # hot reload - self._reload_hash = _generate_hash() \ - if hot_reload else '' - self._reload_interval = hot_reload_interval + self._reload_hash = None self._hard_reload = False self._lock = threading.RLock() self._watch_thread = None - self._hot_reload = hot_reload self._changed_assets = [] if hot_reload: @@ -331,9 +328,9 @@ def _config(self): 'url_base_pathname': self.url_base_pathname, 'requests_pathname_prefix': self.config['requests_pathname_prefix'] } - if self._reload_hash: + if self._dev_tools.hot_reload: config['hot_reload'] = { - 'interval': self._reload_interval + 'interval': self._dev_tools.hot_reload_interval } return config @@ -1067,7 +1064,10 @@ def get_asset_url(self, path): def enable_dev_tools(self, debug=False, - dev_tools_serve_dev_bundles=None): + dev_tools_serve_dev_bundles=None, + dev_tools_hot_reload=None, + dev_tools_hot_reload_interval=None, + dev_tools_hot_reload_watch_interval=None): """ Activate the dev tools, called by `run_server`. If your application is served by wsgi and you want to activate the dev tools, you can call @@ -1077,7 +1077,15 @@ def enable_dev_tools(self, :type debug: bool :param dev_tools_serve_dev_bundles: Serve the dev bundles. :type dev_tools_serve_dev_bundles: bool - :return: + :param dev_tools_hot_reload: Activate the hot reloading. + :type dev_tools_hot_reload: bool + :param dev_tools_hot_reload_interval: Interval at which the client will + request the reload hash. + :type dev_tools_hot_reload_interval: int + :param dev_tools_hot_reload_watch_interval: Interval at which the + assets folder are walked for changes. + :type dev_tools_hot_reload_watch_interval: float + :return: debug """ env = _configs.env_configs() debug = debug or _configs.get_config('debug', None, env, debug, @@ -1088,6 +1096,33 @@ def enable_dev_tools(self, default=debug, is_bool=True ) + self._dev_tools['hot_reload'] = _configs.get_config( + 'hot_reload', dev_tools_hot_reload, env, + default=debug, + is_bool=True + ) + self._dev_tools['hot_reload_interval'] = int(_configs.get_config( + 'hot_reload_interval', dev_tools_hot_reload_interval, env, + default=3000 + )) + self._dev_tools['hot_reload_watch_interval'] = float(_configs.get_config( + 'hot_reload_watch_interval', + dev_tools_hot_reload_watch_interval, + env, + default=0.5 + )) + + if self._dev_tools.hot_reload: + self._reload_hash = _generate_hash() + self._watch_thread = threading.Thread( + target=lambda: _watch.watch( + [self._assets_folder], + self._on_assets_change, + sleep_time=self._dev_tools.hot_reload_watch_interval) + ) + self._watch_thread.daemon = True + self._watch_thread.start() + return debug # noinspection PyProtectedMember From 410865ea729015f32e19d534eb18fc6ce0aad0f7 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Fri, 21 Sep 2018 13:57:18 -0400 Subject: [PATCH 14/29] Line length fix & add more info to the dev tools docstring. --- dash/dash.py | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/dash/dash.py b/dash/dash.py index ac72f92242..a3e15f8e24 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -1073,17 +1073,27 @@ def enable_dev_tools(self, served by wsgi and you want to activate the dev tools, you can call this method out of `__main__`. - :param debug: If True, then activate all the tools unless specified. + If an argument is not provided, it can be set with environ variables. + + Available dev_tools environ variables: + + :param debug: If True, then activate all the tools unless specifically + disabled by the arguments or by environ variables. Available as + `DASH_DEBUG` environ var. :type debug: bool - :param dev_tools_serve_dev_bundles: Serve the dev bundles. + :param dev_tools_serve_dev_bundles: Serve the dev bundles. Available + as `DASH_SERVE_DEV_BUNDLES` environ var. :type dev_tools_serve_dev_bundles: bool - :param dev_tools_hot_reload: Activate the hot reloading. + :param dev_tools_hot_reload: Activate the hot reloading. Available as + `DASH_HOT_RELOAD` environ var. :type dev_tools_hot_reload: bool :param dev_tools_hot_reload_interval: Interval at which the client will - request the reload hash. + request the reload hash. Available as `DASH_HOT_RELOAD_INTERVAL` + environ var. :type dev_tools_hot_reload_interval: int :param dev_tools_hot_reload_watch_interval: Interval at which the - assets folder are walked for changes. + assets folder are walked for changes. Available as + `DASH_HOT_RELOAD_WATCH_INTERVAL` environ var. :type dev_tools_hot_reload_watch_interval: float :return: debug """ @@ -1105,12 +1115,14 @@ def enable_dev_tools(self, 'hot_reload_interval', dev_tools_hot_reload_interval, env, default=3000 )) - self._dev_tools['hot_reload_watch_interval'] = float(_configs.get_config( - 'hot_reload_watch_interval', - dev_tools_hot_reload_watch_interval, - env, - default=0.5 - )) + self._dev_tools['hot_reload_watch_interval'] = float( + _configs.get_config( + 'hot_reload_watch_interval', + dev_tools_hot_reload_watch_interval, + env, + default=0.5 + ) + ) if self._dev_tools.hot_reload: self._reload_hash = _generate_hash() @@ -1123,6 +1135,9 @@ def enable_dev_tools(self, self._watch_thread.daemon = True self._watch_thread.start() + if debug: + self.scripts.config.serve_locally = True + return debug # noinspection PyProtectedMember From 46383d180d7f051f48043b39e45dce84fe2d5357 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Fri, 21 Sep 2018 14:05:23 -0400 Subject: [PATCH 15/29] Add hot reload dev tools arguments to run_server. --- dash/dash.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/dash/dash.py b/dash/dash.py index a3e15f8e24..a08ac45a7c 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -1188,6 +1188,9 @@ def run_server(self, port=8050, debug=False, dev_tools_serve_dev_bundles=None, + dev_tools_hot_reload=None, + dev_tools_hot_reload_interval=None, + dev_tools_hot_reload_watch_interval=None, **flask_run_options): """ Start the flask server in local mode, you should not run this on a @@ -1199,9 +1202,21 @@ def run_server(self, :type debug: bool :param dev_tools_serve_dev_bundles: Serve the dev bundles of components :type dev_tools_serve_dev_bundles: bool + :param dev_tools_hot_reload: Enable the hot reload. + :type dev_tools_hot_reload: bool + :param dev_tools_hot_reload_interval: Reload request interval. + :type dev_tools_hot_reload_interval: int + :param dev_tools_hot_reload_watch_interval: + :type dev_tools_hot_reload_watch_interval: float :param flask_run_options: Given to `Flask.run` :return: """ - debug = self.enable_dev_tools(debug, dev_tools_serve_dev_bundles) + debug = self.enable_dev_tools( + debug, + dev_tools_serve_dev_bundles, + dev_tools_hot_reload, + dev_tools_hot_reload_interval, + dev_tools_hot_reload_watch_interval + ) self.server.run(port=port, debug=debug, **flask_run_options) From 1e4eae625faa1ae2dc414ce1d9e790cd45a56227 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Fri, 21 Sep 2018 14:06:44 -0400 Subject: [PATCH 16/29] Set to serve locally when dev tools are activated. --- dash/dash.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dash/dash.py b/dash/dash.py index a08ac45a7c..b50b1f7502 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -1135,7 +1135,8 @@ def enable_dev_tools(self, self._watch_thread.daemon = True self._watch_thread.start() - if debug: + if debug and self._dev_tools.serve_dev_bundles: + # Dev bundles only works locally. self.scripts.config.serve_locally = True return debug From e714af38a4eaed39e1bf93dc4da4d208032ef384 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Fri, 21 Sep 2018 14:30:37 -0400 Subject: [PATCH 17/29] Add silence routes logging to dev tools. --- dash/_configs.py | 3 ++- dash/dash.py | 37 +++++++++++++++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/dash/_configs.py b/dash/_configs.py index e16f89a6e3..30dff3cfe0 100644 --- a/dash/_configs.py +++ b/dash/_configs.py @@ -25,7 +25,8 @@ def env_configs(): 'DASH_DEBUG', 'DASH_HOT_RELOAD', 'DASH_HOT_RELOAD_INTERVAL', - 'DASH_HOT_RELOAD_WATCH_INTERVAL' + 'DASH_HOT_RELOAD_WATCH_INTERVAL', + 'DASH_SILENCE_ROUTES_LOGGING' )}) diff --git a/dash/dash.py b/dash/dash.py index b50b1f7502..4971ba2aa7 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -9,6 +9,7 @@ import threading import warnings import re +import logging from functools import wraps @@ -241,6 +242,9 @@ def _handle_error(error): self._watch_thread = None self._changed_assets = [] + self.logger = logging.getLogger(name) + self.logger.addHandler(logging.StreamHandler(stream=sys.stdout)) + if hot_reload: self._watch_thread = threading.Thread( target=lambda: _watch.watch( @@ -1067,7 +1071,9 @@ def enable_dev_tools(self, dev_tools_serve_dev_bundles=None, dev_tools_hot_reload=None, dev_tools_hot_reload_interval=None, - dev_tools_hot_reload_watch_interval=None): + dev_tools_hot_reload_watch_interval=None, + dev_tools_silence_routes_logging=None, + ): """ Activate the dev tools, called by `run_server`. If your application is served by wsgi and you want to activate the dev tools, you can call @@ -1095,6 +1101,9 @@ def enable_dev_tools(self, assets folder are walked for changes. Available as `DASH_HOT_RELOAD_WATCH_INTERVAL` environ var. :type dev_tools_hot_reload_watch_interval: float + :param dev_tools_silence_routes_logging: Silence the `werkzeug` logger, + will remove all routes logging. + :type dev_tools_silence_routes_logging: bool :return: debug """ env = _configs.env_configs() @@ -1123,6 +1132,15 @@ def enable_dev_tools(self, default=0.5 ) ) + self._dev_tools['silence_routes_logging'] = _configs.get_config( + 'silence_routes_logging', dev_tools_silence_routes_logging, env, + default=debug, + is_bool=True, + ) + + if self._dev_tools.silence_routes_logging: + logging.getLogger('werkzeug').setLevel(logging.ERROR) + self.logger.setLevel(logging.INFO) if self._dev_tools.hot_reload: self._reload_hash = _generate_hash() @@ -1192,6 +1210,7 @@ def run_server(self, dev_tools_hot_reload=None, dev_tools_hot_reload_interval=None, dev_tools_hot_reload_watch_interval=None, + dev_tools_silence_routes_logging=None, **flask_run_options): """ Start the flask server in local mode, you should not run this on a @@ -1209,6 +1228,8 @@ def run_server(self, :type dev_tools_hot_reload_interval: int :param dev_tools_hot_reload_watch_interval: :type dev_tools_hot_reload_watch_interval: float + :param dev_tools_silence_routes_logging: Silence the routes logs. + :type dev_tools_silence_routes_logging: bool :param flask_run_options: Given to `Flask.run` :return: """ @@ -1217,7 +1238,19 @@ def run_server(self, dev_tools_serve_dev_bundles, dev_tools_hot_reload, dev_tools_hot_reload_interval, - dev_tools_hot_reload_watch_interval + dev_tools_hot_reload_watch_interval, + dev_tools_silence_routes_logging ) + + if self._dev_tools.silence_routes_logging: + # Since it's silenced, the address don't show anymore. + host = flask_run_options.get('host', '127.0.0.1') + ssl_context = flask_run_options.get('ssl_context') + self.logger.info( + 'Running on {}://{}:{}{}'.format( + 'https' if ssl_context else 'http', + host, port, self.config.requests_pathname_prefix) + ) + self.server.run(port=port, debug=debug, **flask_run_options) From d74498fba4f4424b99be565cfd3080c869d90515 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Fri, 21 Sep 2018 14:57:28 -0400 Subject: [PATCH 18/29] Fix logging formatting. --- dash/dash.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/dash/dash.py b/dash/dash.py index 4971ba2aa7..84a44a8573 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -1072,8 +1072,7 @@ def enable_dev_tools(self, dev_tools_hot_reload=None, dev_tools_hot_reload_interval=None, dev_tools_hot_reload_watch_interval=None, - dev_tools_silence_routes_logging=None, - ): + dev_tools_silence_routes_logging=None): """ Activate the dev tools, called by `run_server`. If your application is served by wsgi and you want to activate the dev tools, you can call @@ -1247,9 +1246,9 @@ def run_server(self, host = flask_run_options.get('host', '127.0.0.1') ssl_context = flask_run_options.get('ssl_context') self.logger.info( - 'Running on {}://{}:{}{}'.format( - 'https' if ssl_context else 'http', - host, port, self.config.requests_pathname_prefix) + 'Running on %s://%s:%s%s', + 'https' if ssl_context else 'http', + host, port, self.config.requests_pathname_prefix ) self.server.run(port=port, debug=debug, From 494243e542f24bc239a92d75c76e0bcdb7bbf889 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Wed, 3 Oct 2018 13:50:56 -0400 Subject: [PATCH 19/29] Cast registered_paths keys to list, py3 compat. --- dash/dash.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dash/dash.py b/dash/dash.py index 84a44a8573..2335ba8462 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -349,7 +349,7 @@ def serve_reload_hash(self): return flask.jsonify({ 'reloadHash': self._reload_hash, 'hard': hard, - 'packages': self.registered_paths.keys(), + 'packages': list(self.registered_paths.keys()), 'files': list(changed) }) From 1ae7dcaccc1385b8f26900dd81911600c4c292f6 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Thu, 4 Oct 2018 23:00:55 -0400 Subject: [PATCH 20/29] Add hot_reload_max_retry. --- dash/dash.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/dash/dash.py b/dash/dash.py index 2335ba8462..5ecbde2be6 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -226,7 +226,8 @@ def _handle_error(error): 'serve_dev_bundles': False, 'hot_reload': False, 'hot_reload_interval': 3000, - 'hot_reload_watch_interval': 0.5 + 'hot_reload_watch_interval': 0.5, + 'hot_reload_max_retry': 8 }) # add a handler for components suites errors to return 404 @@ -334,7 +335,8 @@ def _config(self): } if self._dev_tools.hot_reload: config['hot_reload'] = { - 'interval': self._dev_tools.hot_reload_interval + 'interval': self._dev_tools.hot_reload_interval, + 'max_retry': self._dev_tools.hot_reload_max_retry } return config @@ -1072,6 +1074,7 @@ def enable_dev_tools(self, dev_tools_hot_reload=None, dev_tools_hot_reload_interval=None, dev_tools_hot_reload_watch_interval=None, + dev_tools_hot_reload_max_retry=None, dev_tools_silence_routes_logging=None): """ Activate the dev tools, called by `run_server`. If your application is @@ -1100,6 +1103,8 @@ def enable_dev_tools(self, assets folder are walked for changes. Available as `DASH_HOT_RELOAD_WATCH_INTERVAL` environ var. :type dev_tools_hot_reload_watch_interval: float + :param dev_tools_hot_reload_max_retry: Maximum amount of retries before + failing and display a pop up. Default 30. :param dev_tools_silence_routes_logging: Silence the `werkzeug` logger, will remove all routes logging. :type dev_tools_silence_routes_logging: bool @@ -1131,6 +1136,14 @@ def enable_dev_tools(self, default=0.5 ) ) + self._dev_tools['hot_reload_max_retry'] = int( + _configs.get_config( + 'hot_reload_max_retry', + dev_tools_hot_reload_max_retry, + env, + default=8 + ) + ) self._dev_tools['silence_routes_logging'] = _configs.get_config( 'silence_routes_logging', dev_tools_silence_routes_logging, env, default=debug, @@ -1209,6 +1222,7 @@ def run_server(self, dev_tools_hot_reload=None, dev_tools_hot_reload_interval=None, dev_tools_hot_reload_watch_interval=None, + dev_tools_hot_reload_max_retry=None, dev_tools_silence_routes_logging=None, **flask_run_options): """ @@ -1238,7 +1252,8 @@ def run_server(self, dev_tools_hot_reload, dev_tools_hot_reload_interval, dev_tools_hot_reload_watch_interval, - dev_tools_silence_routes_logging + dev_tools_hot_reload_max_retry, + dev_tools_silence_routes_logging, ) if self._dev_tools.silence_routes_logging: From f22ea7de535b00b3cbea2905d3a69905c0872f8c Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Tue, 9 Oct 2018 13:41:51 -0400 Subject: [PATCH 21/29] Generate a debugger PIN and print it when logs are silenced. --- dash/dash.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/dash/dash.py b/dash/dash.py index 5ecbde2be6..e3f9053a84 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -1,6 +1,8 @@ from __future__ import print_function +import itertools import os +import random import sys import collections import importlib @@ -1266,5 +1268,17 @@ def run_server(self, host, port, self.config.requests_pathname_prefix ) + # Generate a debugger pin and log it to the screen. + debugger_pin = os.environ['WERKZEUG_DEBUG_PIN'] = '-'.join( + itertools.chain( + ''.join([str(random.randint(0, 9)) for _ in range(3)]) + for _ in range(3)) + ) + + self.logger.info( + 'Debugger PIN: %s', + debugger_pin + ) + self.server.run(port=port, debug=debug, **flask_run_options) From 353d79c908138431bddd08b6864793bbc502b8c9 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Thu, 18 Oct 2018 13:33:39 -0400 Subject: [PATCH 22/29] Fix rebase. --- dash/dash.py | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/dash/dash.py b/dash/dash.py index e3f9053a84..d9d9055021 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -208,8 +208,9 @@ def _handle_error(error): self.config['routes_pathname_prefix'], self.index) - add_url('{}_reload-hash'.format(self.config['routes_pathname_prefix']), - self.serve_reload_hash) + self._add_url( + '{}_reload-hash'.format(self.config['routes_pathname_prefix']), + self.serve_reload_hash) # catch-all for front-end routes, used by dcc.Location self._add_url( @@ -248,19 +249,6 @@ def _handle_error(error): self.logger = logging.getLogger(name) self.logger.addHandler(logging.StreamHandler(stream=sys.stdout)) - if hot_reload: - self._watch_thread = threading.Thread( - target=lambda: _watch.watch( - [self._assets_folder], - self._on_assets_change, - sleep_time=hot_reload_watch_interval) - ) - self._watch_thread.daemon = True - self._watch_thread.start() - - if not self.server.debug: - self.server.debug = True - def _add_url(self, name, view_func, methods=('GET',)): self.server.add_url_rule( name, From 4654e5fb6ad3ead5cb457422f1d5925e0b82a096 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Thu, 18 Oct 2018 18:17:51 -0400 Subject: [PATCH 23/29] Add missing dev_tools docstring. --- dash/dash.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dash/dash.py b/dash/dash.py index d9d9055021..1a6be30193 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -1231,6 +1231,9 @@ def run_server(self, :type dev_tools_hot_reload_interval: int :param dev_tools_hot_reload_watch_interval: :type dev_tools_hot_reload_watch_interval: float + :param dev_tools_hot_reload_max_retry: The number of times the reloader + requests can fail before displaying an alert. + :type dev_tools_hot_reload_max_retry: int :param dev_tools_silence_routes_logging: Silence the routes logs. :type dev_tools_silence_routes_logging: bool :param flask_run_options: Given to `Flask.run` From beadd63461309aea804e3475ede2bb142c192a57 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Wed, 14 Nov 2018 11:35:21 -0500 Subject: [PATCH 24/29] Fix :bug: with assets watch deletion. --- dash/_watch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dash/_watch.py b/dash/_watch.py index ebaf8cc0fd..34c523478c 100644 --- a/dash/_watch.py +++ b/dash/_watch.py @@ -27,7 +27,7 @@ def walk(): walked.append(path) # Look for deleted files - for w in (x for x in watched.keys() if x not in walked): + for w in [x for x in watched.keys() if x not in walked]: del watched[w] on_change(w, -1, True) From 407e24c12f5f7eb842fca14f58ddffc91de9eb8a Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Wed, 14 Nov 2018 11:36:22 -0500 Subject: [PATCH 25/29] Proper path manipulation on assets change. --- dash/dash.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dash/dash.py b/dash/dash.py index 1a6be30193..0548ea16a2 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -1167,7 +1167,8 @@ def _on_assets_change(self, filename, modified, deleted): self._hard_reload = True self._reload_hash = _generate_hash() - asset_path = filename.replace(self._assets_folder, '')\ + asset_path = os.path.relpath( + filename, os.path.commonprefix([self._assets_folder, filename]))\ .replace('\\', '/').lstrip('/') self._changed_assets.append({ From 63033efb0f2b780aacb8946890a74a88be26878b Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Wed, 14 Nov 2018 11:40:51 -0500 Subject: [PATCH 26/29] :goat: environ -> environment. --- dash/dash.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/dash/dash.py b/dash/dash.py index 0548ea16a2..ec550e7d0e 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -1071,27 +1071,28 @@ def enable_dev_tools(self, served by wsgi and you want to activate the dev tools, you can call this method out of `__main__`. - If an argument is not provided, it can be set with environ variables. + If an argument is not provided, it can be set with environment + variables. - Available dev_tools environ variables: + Available dev_tools environment variables: :param debug: If True, then activate all the tools unless specifically disabled by the arguments or by environ variables. Available as - `DASH_DEBUG` environ var. + `DASH_DEBUG` environment variable. :type debug: bool :param dev_tools_serve_dev_bundles: Serve the dev bundles. Available - as `DASH_SERVE_DEV_BUNDLES` environ var. + as `DASH_SERVE_DEV_BUNDLES` environment variable. :type dev_tools_serve_dev_bundles: bool :param dev_tools_hot_reload: Activate the hot reloading. Available as - `DASH_HOT_RELOAD` environ var. + `DASH_HOT_RELOAD` environment variable. :type dev_tools_hot_reload: bool :param dev_tools_hot_reload_interval: Interval at which the client will request the reload hash. Available as `DASH_HOT_RELOAD_INTERVAL` - environ var. + environment variable. :type dev_tools_hot_reload_interval: int :param dev_tools_hot_reload_watch_interval: Interval at which the assets folder are walked for changes. Available as - `DASH_HOT_RELOAD_WATCH_INTERVAL` environ var. + `DASH_HOT_RELOAD_WATCH_INTERVAL` environment variable. :type dev_tools_hot_reload_watch_interval: float :param dev_tools_hot_reload_max_retry: Maximum amount of retries before failing and display a pop up. Default 30. From fb26306e4d287818fa4a169a84634187f9ac92d7 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Wed, 14 Nov 2018 11:47:07 -0500 Subject: [PATCH 27/29] Add DASH_HOT_RELOAD_MAX_RETRY environment variable. --- dash/_configs.py | 1 + dash/dash.py | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/dash/_configs.py b/dash/_configs.py index 30dff3cfe0..a2aa35df25 100644 --- a/dash/_configs.py +++ b/dash/_configs.py @@ -26,6 +26,7 @@ def env_configs(): 'DASH_HOT_RELOAD', 'DASH_HOT_RELOAD_INTERVAL', 'DASH_HOT_RELOAD_WATCH_INTERVAL', + 'DASH_HOT_RELOAD_MAX_RETRY', 'DASH_SILENCE_ROUTES_LOGGING' )}) diff --git a/dash/dash.py b/dash/dash.py index ec550e7d0e..3beb789871 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -1076,6 +1076,14 @@ def enable_dev_tools(self, Available dev_tools environment variables: + - DASH_DEBUG + - DASH_SERVE_DEV_BUNDLES + - DASH_HOT_RELOAD + - DASH_HOT_RELOAD_INTERVAL + - DASH_HOT_RELOAD_WATCH_INTERVAL + - DASH_HOT_RELOAD_MAX_RETRY + - DASH_SILENCE_ROUTES_LOGGING + :param debug: If True, then activate all the tools unless specifically disabled by the arguments or by environ variables. Available as `DASH_DEBUG` environment variable. @@ -1095,9 +1103,12 @@ def enable_dev_tools(self, `DASH_HOT_RELOAD_WATCH_INTERVAL` environment variable. :type dev_tools_hot_reload_watch_interval: float :param dev_tools_hot_reload_max_retry: Maximum amount of retries before - failing and display a pop up. Default 30. + failing and display a pop up. Default 30. Available as + `DASH_HOT_RELOAD_MAX_RETRY` environment variable. + :type dev_tools_hot_reload_max_retry: int :param dev_tools_silence_routes_logging: Silence the `werkzeug` logger, - will remove all routes logging. + will remove all routes logging. Available as + `DASH_SILENCE_ROUTES_LOGGING` environment variable. :type dev_tools_silence_routes_logging: bool :return: debug """ From 927d69bc5473cbac60d229d158a20933de4b1245 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Wed, 14 Nov 2018 14:35:02 -0500 Subject: [PATCH 28/29] :hocho: remove resources cache. --- dash/resources.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/dash/resources.py b/dash/resources.py index c01546df35..603bd37afe 100644 --- a/dash/resources.py +++ b/dash/resources.py @@ -11,7 +11,6 @@ def __init__(self, resource_name, layout): self._resources = [] self.resource_name = resource_name self.layout = layout - self._resources_cache = [] def append_resource(self, resource): self._resources.append(resource) @@ -59,15 +58,10 @@ def _filter_resources(self, all_resources, dev_bundles=False): return filtered_resources def get_all_resources(self, dev_bundles=False): - if self._resources_cache: - return self._resources_cache + lib_resources = ComponentRegistry.get_resources(self.resource_name) + all_resources = lib_resources + self._resources - all_resources = ComponentRegistry.get_resources(self.resource_name) - all_resources.extend(self._resources) - - self._resources_cache = res = \ - self._filter_resources(all_resources, dev_bundles) - return res + return self._filter_resources(all_resources, dev_bundles) class Css: # pylint: disable=old-style-class From d410c9c602bc354639206e01f91f791387d389f5 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Wed, 14 Nov 2018 15:14:52 -0500 Subject: [PATCH 29/29] Update version and changelog. --- CHANGELOG.md | 5 +++++ dash/version.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5936f46e20..1e82d00ea4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.30.0 - 2018-11-14 +## Added +- Hot reload from the browser [#362](https://github.com/plotly/dash/pull/362) +- Silence routes logging with `dev_tools_silence_routes_logging`. + ## 0.29.0 - 2018-11-06 ## Added - Added component namespaces registry, collect the resources needed by component library when they are imported instead of crawling the layout. [#444](https://github.com/plotly/dash/pull/444) diff --git a/dash/version.py b/dash/version.py index 9093e4e468..e187e0aa61 100644 --- a/dash/version.py +++ b/dash/version.py @@ -1 +1 @@ -__version__ = '0.29.0' +__version__ = '0.30.0'