diff --git a/vizro-core/changelog.d/20240625_114610_antony.milne_make_vizro_callable.md b/vizro-core/changelog.d/20240625_114610_antony.milne_make_vizro_callable.md new file mode 100644 index 000000000..e35846d9f --- /dev/null +++ b/vizro-core/changelog.d/20240625_114610_antony.milne_make_vizro_callable.md @@ -0,0 +1,47 @@ + + + + + +### Added + +- Vizro app itself implements WSGI interface as a shortcut to `app.dash.server`. ([#580](https://github.com/mckinsey/vizro/pull/580)) + + + + + diff --git a/vizro-core/docs/pages/user-guides/run.md b/vizro-core/docs/pages/user-guides/run.md index 11f2236c1..81ee62c9a 100644 --- a/vizro-core/docs/pages/user-guides/run.md +++ b/vizro-core/docs/pages/user-guides/run.md @@ -105,19 +105,18 @@ The dashboard application can be launched in a Jupyter environment in `inline`, ) dashboard = vm.Dashboard(pages=[page]) - app = Vizro().build(dashboard) - server = app.dash.server # (1)! + app = Vizro().build(dashboard) # (1)! if __name__ == "__main__": # (2)! app.run() ``` - 1. Expose the underlying Flask app through `app.dash.server`; this will be used by Gunicorn. + 1. The Vizro `app` object is a WSGI application that exposes the underlying Flask app; this will be used by Gunicorn. 2. Enable the same app to still be run using the built-in Flask server with `python app.py` for development purposes. To run using Gunicorn with four worker processes, execute ```bash -gunicorn app:server --workers 4 +gunicorn app:app --workers 4 ``` in the command line. For more Gunicorn configuration options, refer to [Gunicorn documentation](https://docs.gunicorn.org/). @@ -132,6 +131,6 @@ A Vizro app wraps a Dash app, which itself wraps a Flask app. Hence to deploy a - [Flask deployment documentation](https://flask.palletsprojects.com/en/2.0.x/deploying/) - [Dash deployment documentation](https://dash.plotly.com/deployment) -In particular, `app = Vizro()` exposes the Flask app through `app.dash.server`. As in the [above example with Gunicorn](#gunicorn), this provides the application instance to a [WSGI](https://werkzeug.palletsprojects.com/en/3.0.x/terms/#wsgi) server. +Internally, `app = Vizro()` contains a Flask app in `app.dash.server`. However, as a convenience, the Vizro `app` itself implements the [WSGI application interface](https://werkzeug.palletsprojects.com/en/3.0.x/terms/#wsgi) as a shortcut to the underlying Flask app. This means that, as in the [above example with Gunicorn](#gunicorn), the Vizro `app` object can be directly supplied to the WSGI server. [`Vizro`][vizro.Vizro] accepts `**kwargs` that are passed through to `Dash`. This enables you to configure the underlying Dash app using the same [arguments that are available](https://dash.plotly.com/reference#dash.dash) in `Dash`. For example, in a deployment context, you might like to specify a custom `url_base_pathname` to serve your Vizro app at a specific URL rather than at your domain root. diff --git a/vizro-core/src/vizro/_vizro.py b/vizro-core/src/vizro/_vizro.py index 890f0674a..78f20f378 100644 --- a/vizro-core/src/vizro/_vizro.py +++ b/vizro-core/src/vizro/_vizro.py @@ -1,7 +1,9 @@ +from __future__ import annotations + import logging import warnings from pathlib import Path -from typing import List +from typing import TYPE_CHECKING, Iterable, List import dash import flask @@ -13,6 +15,10 @@ logger = logging.getLogger(__name__) +if TYPE_CHECKING: + # These are built into wsgiref.types for Python 3.11 onwards. + from _typeshed.wsgi import StartResponse, WSGIEnvironment + class Vizro: """The main class of the `vizro` package.""" @@ -118,6 +124,13 @@ def _pre_build(): if hasattr(model, "pre_build"): model.pre_build() + def __call__(self, environ: WSGIEnvironment, start_response: StartResponse) -> Iterable[bytes]: + """Implements WSGI application interface. + + This means you can do e.g. gunicorn app:app without needing to manually define server = app.dash.server. + """ + return self.dash.server(environ, start_response) + @staticmethod def _reset(): """Private method that clears all state in the `Vizro` app.