Skip to content

Add separate init_app function for providing the flask app #739

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

Merged
merged 6 commits into from
May 28, 2019
Merged
Show file tree
Hide file tree
Changes from 3 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
142 changes: 82 additions & 60 deletions dash/dash.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 @@ -126,7 +130,13 @@ 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)
if server:
if isinstance(server, Flask):
self.server = server
else:
self.server = Flask(name, static_folder=static_folder)
else:
self.server = None
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the choice of True (auto), False (defer) and Flask(...) (use existing) but then the default value should be updated to True. I'd also say, given that we're now accepting multiple value types for server, that we should explicitly throw an error if we get neither a Flask nor a bool - I know @chriddyp likes making specific types in exceptions.py, but I'd be fine with just something like ValueError

So something like:

if isinstance(server, bool):
    self.server = Flask(...) if server else 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 +164,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 +175,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 +189,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 +292,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