Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add separate init_app function for providing the flask app #739

Merged
merged 6 commits into from
May 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions dash/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## Unreleased
### Changed
- [#739](https://github.com/plotly/dash/pull/739) Allow the Flask app to be provided to Dash after object initialization. This allows users to define Dash layouts etc when using the app factory pattern, or any other pattern that inhibits access to the app object. This broadly complies with the flask extension API, allowing Dash to be considered as a Flask extension where it needs to be.

- [#722](https://github.com/plotly/dash/pull/722) Assets are served locally by default. Both JS scripts and CSS files are affected. This improves robustness and flexibility in numerous situations, but in certain cases initial loading could be slowed. To restore the previous CDN serving, set `app.scripts.config.serve_locally = False` (and similarly with `app.css`, but this is generally less important).

- Undo/redo toolbar is removed by default, you can enable it with `app=Dash(show_undo_redo=true)`. The CSS hack `._dash-undo-redo:{display:none;}` is no longer needed [#724](https://github.com/plotly/dash/pull/724)
Expand Down
149 changes: 87 additions & 62 deletions dash/dash.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class Dash(object):
def __init__(
self,
name='__main__',
server=None,
server=True,
static_folder='static',
assets_folder='assets',
assets_url_path='/assets',
Expand All @@ -110,6 +110,10 @@ def __init__(
plugins=None,
**kwargs):

# Store some flask-related parameters for use in init_app()
self.compress = compress
self.name = name

# pylint-disable: too-many-instance-attributes
if 'csrf_protect' in kwargs:
warnings.warn('''
Expand All @@ -125,8 +129,17 @@ def __init__(
)
self._assets_url_path = assets_url_path

# allow users to supply their own flask server
self.server = server or Flask(name, static_folder=static_folder)
# We have 3 cases: server is either True (we create the server), False
# (defer server creation) or a Flask app instance (we use their server)
if isinstance(server, bool):
if server:
self.server = Flask(name, static_folder=static_folder)
else:
self.server = None
elif isinstance(server, Flask):
self.server = server
else:
raise ValueError('server must be a Flask app, or a boolean')

url_base_pathname, routes_pathname_prefix, requests_pathname_prefix = \
pathname_configs(
Expand Down Expand Up @@ -154,22 +167,6 @@ def __init__(
'show_undo_redo': show_undo_redo
})

assets_blueprint_name = '{}{}'.format(
self.config.routes_pathname_prefix.replace('/', '_'),
'dash_assets'
)

self.server.register_blueprint(
flask.Blueprint(
assets_blueprint_name, name,
static_folder=self._assets_folder,
static_url_path='{}{}'.format(
self.config.routes_pathname_prefix,
assets_url_path.lstrip('/')
)
)
)

# list of dependencies
self.callback_map = {}

Expand All @@ -181,15 +178,6 @@ def __init__(
# default renderer string
self.renderer = 'var renderer = new DashRenderer();'

if compress:
# gzip
Compress(self.server)

@self.server.errorhandler(exceptions.PreventUpdate)
def _handle_error(_):
"""Handle a halted callback and return an empty 204 response"""
return '', 204

# static files from the packages
self.css = Css()
self.scripts = Scripts()
Expand All @@ -204,8 +192,79 @@ def _handle_error(_):
# urls
self.routes = []

self._layout = None
self._cached_layout = None
self._dev_tools = _AttributeDict({
'serve_dev_bundles': False,
'hot_reload': False,
'hot_reload_interval': 3000,
'hot_reload_watch_interval': 0.5,
'hot_reload_max_retry': 8,
'ui': False,
'props_check': False,
})

self._assets_files = []

# hot reload
self._reload_hash = None
self._hard_reload = False
self._lock = threading.RLock()
self._watch_thread = None
self._changed_assets = []

self.logger = logging.getLogger(name)
self.logger.addHandler(logging.StreamHandler(stream=sys.stdout))

if isinstance(plugins, _patch_collections_abc('Iterable')):
for plugin in plugins:
plugin.plug(self)

if self.server is not None:
self.init_app()

def init_app(self, app=None):
"""
Initialize the parts of Dash that require a flask app
"""

if app is not None:
self.server = app

assets_blueprint_name = '{}{}'.format(
self.config.routes_pathname_prefix.replace('/', '_'),
'dash_assets'
)

self.server.register_blueprint(
flask.Blueprint(
assets_blueprint_name,
self.name,
static_folder=self._assets_folder,
static_url_path='{}{}'.format(
self.config.routes_pathname_prefix,
self._assets_url_path.lstrip('/')
)
)
)

if self.compress:
# gzip
Compress(self.server)

@self.server.errorhandler(exceptions.PreventUpdate)
def _handle_error(_):
"""Handle a halted callback and return an empty 204 response"""
return '', 204

prefix = self.config['routes_pathname_prefix']

self.server.before_first_request(self._setup_server)

# add a handler for components suites errors to return 404
self.server.errorhandler(exceptions.InvalidResourceError)(
self._invalid_resources_handler)

self._add_url('{}_dash-layout'.format(prefix), self.serve_layout)

self._add_url('{}_dash-dependencies'.format(prefix), self.dependencies)
Expand Down Expand Up @@ -236,40 +295,6 @@ def _handle_error(_):
'{}_favicon.ico'.format(prefix),
self._serve_default_favicon)

self.server.before_first_request(self._setup_server)

self._layout = None
self._cached_layout = None
self._dev_tools = _AttributeDict({
'serve_dev_bundles': False,
'hot_reload': False,
'hot_reload_interval': 3000,
'hot_reload_watch_interval': 0.5,
'hot_reload_max_retry': 8,
'ui': False,
'props_check': False,
})

# add a handler for components suites errors to return 404
self.server.errorhandler(exceptions.InvalidResourceError)(
self._invalid_resources_handler)

self._assets_files = []

# hot reload
self._reload_hash = None
self._hard_reload = False
self._lock = threading.RLock()
self._watch_thread = None
self._changed_assets = []

self.logger = logging.getLogger(name)
self.logger.addHandler(logging.StreamHandler(stream=sys.stdout))

if isinstance(plugins, _patch_collections_abc('Iterable')):
for plugin in plugins:
plugin.plug(self)

def _add_url(self, name, view_func, methods=('GET',)):
self.server.add_url_rule(
name,
Expand Down