ExplainerHub

If you are hosting multiple ExplainerDashboards it becomes convenient to host them at a single place. This is made easy with ExplainerHub.

Possible use cases are:

  1. Showcasing multiple models to compare and decide which one to put into production

  2. Keeping ExplainerDashboards up and running for all models in production in a single place

You can initialize an ExplainerHub by passing in a list of ExplainerDashboards:

db1 = ExplainerDashboard(explainer1)
db2 = ExplainerDashboard(explainer2)
hub = ExplainerHub([db1, db2])
hub.run()

Each dashboard is hosted on it’s own url path (e.g. localhost:8050/dashboard1), and a front end dashboard with links and descriptions for every dashboard is hosted at e.g. localhost:8050:

_images/explainerhub.png

Adjusting title and descriptions

You can adjust the title of the ExplainerHub and the description in the jumbotron, by passing title and description. You can also adjust the title and description of each ExplainerDashboard, and set the url path with name:

db1 = ExplainerDashboard(explainer1, title="Model One", name="db1",
            description="This is model option one")
db2 = ExplainerDashboard(explainer2, title="Model Two", name="db2",
            description="This is model option two")
hub = ExplainerHub([db1, db2], title="Model Comparison",
            description="Showing dashboards for both model one and two")
hub.run()

Adding dashboards

You can add additional dashboards to a hub with:

hub.add_dashboard(db2)

And remove them by passing their name:

hub.remove_dashboard("db2")

Adding dashboards using url

You can even add dashboards to a running dashboard by navigating to the /add_dashboard route and specifying the path to a .yaml file:

db2.to_yaml("dashboards/dashboard2.yaml", dump_explainer=True)
ExplainerHub([db1], add_dashboard_route=True).run()

If you then navigate to e.g. http://localhost:8050/add_dashboard/dashboards/dashboard2.yaml then this dashboard will be added to the hub. By default you can specify any .yaml file in any sub directory in which the hub is running.

This can be useful when you for example store explainers and dashboards as part of an MLOps or CI/CD flow.

If you store dashboards in a particular location, you can also specify a pattern to add dashboards:

ExplainerHub([db1],
        add_dashboard_route=True,
        add_dashboard_pattern="dashboards/{}.yaml").run()

Now you can simply nagivate to http://localhost:8050/add_dashboard/dashboard2 and it will find dashboards/dashboard2.yaml and add it.

You can also remove dashboards by navigating to e.g. http://localhost:8050/remove_dashboard/db2.

Note

Dashboards will be added to a particular instance of the hub that is running. So if you have a deployment with multiple workers/nodes, this method will not work for now.

Changing size, theme, etc

By default the hub fills the entire width of the browser, you can make it more slim by passing fluid=False. You can also pass other bootstrap themes: bootstrap=dbc.themes.SKETCHY. You can adjust the size of the iFrame with min_height=2000.

You can also build your own front end if you want. If you pass no_index=True, the index page and navbars routes will not get loaded, while the dashboards are still loaded on their respective routes. E.g.:

hub = ExplainerHub([db1, db2], no_index=True)
app = hub.flask_server()

@app.route("/")
def custom_index():
    return render_template("custom_index.html")

Managing users

You can manage logins and which usernames have access to particular dashboards by passing logins and db_users. Here we create two users (user1 and user2) and only give user1 access to the first dashboard, and only give user2 access to the second dashboard:

hub = ExplainerHub([db1, db2],
        logins=[['user1', 'password1'],['user2','password2']],
        db_users=dict(db1=['user1'], db2=['user2'])

If you had defined users in your ExplainerDashboard then these get automatically carried over to the`` ExplainerHub``:

db1 = ExplainerDashboard(explainer1, logins=[['user1', 'password1']])
db2 = ExplainerDashboard(explainer2, logins=[['user2', 'password2']])
hub = ExplainerHub([db1, db2])

You can also add users from the hub itself:

hub.add_user("user3", "password3")
hub.add_user_to_dashboard("db2", "user3")

User/Password pairs can also be stored to file (with passwords hashed). The filename can be set with the users_file parameter and defaults to users.yaml. When you store a hub.to_yaml("hub.yaml") all logins will automatically be exported to this file. This users_file can also be managed with explainerhub CLI tool (see below).

By default, if you define any user logins, then the hub will only be accesible after logging in. However you can also pass the parameter dbs_open_by_default=True, in which case the hub index and any dashboards for which no db_users have been defined will not force logins. Only dashboards for which you passed a list of db_users will be password locked.

Storing to config

You can store an ExplainerHub to disk with ExplainerHub.to_yaml(). This will also dump all the explainers to disk, store the configuration of dashboards that make up the hub to individual .yaml files, and store logins to users.yaml. You reload a hub with the from_config classmethod:

hub.to_yaml("hub.yaml")
hub2 = ExplainerHub.from_config("hub.yaml")

The hub.yaml file looks something like this:

explainerhub:
    title: ExplainerHub
    description: Showing dashboards for both model one and two
    masonry: false
    n_dashboard_cols: 3
    users_file: users.yaml
    db_users: null
    port: 8050
    kwargs: {}
    dashboards:
    - db1_dashboard.yaml
    - db2_dashboard.yaml

If you pass integrate_dashboard_yamls=True, then the configuration of the dashboards get integrated into a single hub.yaml file instead of being stored in separate files.

Storing to static html

You can store the hub front-end and the underlying dashboards to static html with e.g. hub.to_html(“hub.html”). This will also generate individual .html files for every dashboard e.g. dashboard1.html, dashboard2.html, etc, etc.

This might become a bit messy, so instead you can save straight to a zipfile with hub.to_zip(“hub.zip”).

explainerhub CLI

You can also use the explainerhub CLI tool to start your ExplainerHub and manage your users straight from the commandline:

$ explainerhub run hub.yaml
$ explainerhub add-user
$ explainerhub delete-user
$ explainerhub add-dashboard-user
$ explainerhub delete-dashboard-user

SECRET_KEY

In order to make the user session (and so logins) persist when you reboot the server, you need to pass a SECRET_KEY to the hub. Like with any Flask app you should be very careful not to store this key somewhere easily findable. Ususally people store it as an environmental variable:

$ export SECRET_KEY='5f352379324c22463451387a0aec5d2f'

Then you load it with the os module and pass it to the hub:

ExplainerHub([db1, db2], secret_key=os.environ.get("SECRET_KEY"))

If you do not pass a secret key, a random uuid key is generated each time you initialize the hub (which means you’ll have to log in every time).

class explainerdashboard.dashboards.ExplainerHub(dashboards, title='ExplainerHub', description=None, masonry=False, n_dashboard_cols=3, users_file='users.yaml', user_json=None, logins=None, db_users=None, dbs_open_by_default=False, port=8050, min_height=3000, secret_key=None, no_index=False, bootstrap=None, fluid=True, base_route='dashboards', index_to_base_route=False, static_to_base_route=False, max_dashboards=None, add_dashboard_route=False, add_dashboard_pattern=None, **kwargs)

ExplainerHub is a way to host multiple dashboards in a single point, and manage access through adding user accounts.

Example

hub = ExplainerHub([db1, db2], logins=[['user', 'password']], secret_key="SECRET") hub.run()

A frontend is hosted at e.g. localhost:8050, with summaries and links to each individual dashboard. Each ExplainerDashboard is hosted on its own url path, so that you can also find it directly, e.g.:

localhost:8050/dashboards/dashboard1 and localhost:8050/dashboards/dashboard2.

You can store the hub configuration, dashboard configurations, explainers and user database with a single command: hub.to_yaml('hub.yaml').

You can restore the hub with hub2 = ExplainerHub.from_config('hub.yaml')

You can start the hub from the command line using the explainerhub CLI command: $ explainerhub run hub.yaml. You can also use the CLI to add and delete users.

Note

Logins can be defined in multiple places: users.json, ExplainerHub.logins and ExplainerDashboard.logins for each dashboard in dashboards. When users with the same username are defined in multiple locations then passwords are looked up in the following order: hub.logins > dashboard.logins > user.json

Note

**kwargs will be forwarded to each dashboard in dashboards.

Parameters
  • dashboards (List[ExplainerDashboard]) – list of ExplainerDashboard to include in ExplainerHub.

  • title (str, optional) – title to display. Defaults to “ExplainerHub”.

  • description (str, optional) – Short description of ExplainerHub. Defaults to default text.

  • masonry (bool, optional) – Lay out dashboard cards in fluid bootstrap masonry responsive style. Defaults to False.

  • n_dashboard_cols (int, optional) – If masonry is False, organize cards in rows and columns. Defaults to 3 columns.

  • users_file (Path, optional) – a .yaml or .json file used to store user and (hashed) password data. Defaults to ‘users.yaml’.

  • user_json (Path, optional) – Deprecated! A .json file used to store user and (hashed) password data. Defaults to ‘users.json’. Was replaced by users_file which can also be a more readable .yaml.

  • logins (List[List[str, str]], optional) – List of [‘login’, ‘password’] pairs, e.g. logins = [[‘user1’, ‘password1’], [‘user2’, ‘password2’]]

  • db_users (dict, optional) – dictionary limiting access to certain dashboards to a subset of users, e.g dict(dashboard1=[‘user1’, ‘user2’], dashboard2=[‘user3’]).

  • dbs_open_by_default (bool, optional) – Only force logins for dashboard with defined db_users. All other dashboards and index no login required. Default to False,

  • port (int, optional) – Port to run hub on. Defaults to 8050.

  • min_height (int, optional) – Defaults to 3000 pixels.

  • secret_key (str) – Flask secret key to pass to dashboard in order to persist logins. Defaults to a new random uuid string every time you start the dashboard. (i.e. no persistence) You should store the secret key somewhere save, e.g. in a environmental variable.

  • no_index (bool, optional) – do not add the “/” route and “dashboards/_dashboard1” etc routes, but only mount the dashboards on e.g. dashboards/dashboard1. This allows you to add your own custom front_end.

  • bootstrap (str, optional) – url with custom bootstrap css, e.g. bootstrap=dbc.themes.FLATLY. Defaults to static bootstrap css.

  • fluid (bool, optional) – Let the bootstrap container fill the entire width of the browser. Defaults to True.

  • base_route (str, optional) – Base route for dashboard : /<base_route>/dashboard1. Defaults to “dashboards”.

  • index_to_base_route (bool, optional) – Dispatches Hub to “/base_route/index” instead of the default “/” and “/index”. Useful when the host root is not reserved for the ExplainerHub

  • static_to_base_route (bool, optional) – Dispatches Hub to “/base_route/static” instead of the default “/static”. Useful when the host root is not reserved for the ExplainerHub

  • max_dashboards (int, optional) – Max number of dashboards in the hub. Defaults to None (for no limitation). If set and you add an additional dashboard, the first dashboard in self.dashboards will be deleted!

  • add_dashboard_route (bool, optional) – open a route /add_dashboard and /remove_dashboard If a user navigates to e.g. /add_dashboard/dashboards/dashboard4.yaml, the hub will check if there exists a folder dashboards which contains a dashboard4.yaml file. If so load this dashboard and add it to the hub. You can remove it with e.g. /remove_dashboard/dashboard4 Alternatively you can specify a path pattern with add_dashboard_pattern. Warning: this will only work if you run the hub on a single worker or node!

  • add_dashboard_pattern (str, optional) – a str pattern with curly brackets in the place of where the dashboard.yaml file can be found. So e.g. if you keep your dashboards in a subdirectory dashboards with a subdirectory for the dashboard name and each yaml file called dashboard.yaml, you could set this to “dashboards/{}/dashboard.yaml”, and then navigate to /add_dashboard/dashboard5 to add dashboards/dashboard5/dashboard.yaml.

  • **kwargs – all kwargs will be forwarded to the constructors of each dashboard in dashboards dashboards.

classmethod from_config(config, **update_params)

Instantiate an ExplainerHub based on a config file.

Parameters
  • config (Union[dict, str, Path]) – either a dict or a .yaml config file to load

  • update_params – additional kwargs to override stored settings.

Returns

new instance of ExplainerHub according to the config.

Return type

ExplainerHub

to_yaml(filepath=None, dump_explainers=True, return_dict=False, integrate_dashboard_yamls=False, pickle_type='joblib')

Store ExplainerHub to configuration .yaml, store the users to users.json and dump the underlying dashboard .yamls and explainers.

If filepath is None, does not store yaml config to file, but simply return config yaml string.

If filepath provided and dump_explainers=True, then store all underlying explainers to disk.

Parameters
  • filepath (Path, optional) – .yaml file filepath. Defaults to None.

  • dump_explainers (bool, optional) – Store the explainers to disk along with the .yaml file. Defaults to True.

  • return_dict (bool, optional) – Instead of returning or storing yaml return a configuration dictionary. Returns a single dict as if separate_dashboard_yamls=True. Defaults to False.

  • integrate_dashboard_yamls (bool, optional) – Do not generate an individual .yaml file for each dashboard, but integrate them in hub.yaml.

  • pickle_type ({'joblib', 'dill', 'pkl'}, optional) – Defaults to “joblib”. Alternatives are “dill” and “pkl”.

Returns

{dict, yaml, None}

add_user(username, password, add_to_users_file=False)

add a user with username and password.

Parameters
  • username (str) – username

  • password (str) – password

  • add_to_users_file (bool, optional) – Add the user to the .yaml file defined in self.users_file instead of to self.logins. Defaults to False.

add_user_to_dashboard(dashboard, username, add_to_users_file=False)

add a user to a specific dashboard. If

Parameters
  • dashboard (str) – name of dashboard

  • username (str) – user to add to dashboard

  • add_to_users_file (bool, optional) – add the user to the .yaml or .json file defined in self.users_file instead of to self.db_users. Defaults to False.

get_dashboard_users(dashboard)

return all users that have been approved to use a specific dashboard

Parameters

dashboard (str) – dashboard

Returns

List

flask_server()

return the Flask server inside the class instance

run(port=None, host='0.0.0.0', use_waitress=False, **kwargs)

start the ExplainerHub.

Parameters
  • port (int, optional) – Override default port. Defaults to None.

  • host (str, optional) – host name to run dashboard. Defaults to ‘0.0.0.0’.

  • use_waitress (bool, optional) – Use the waitress python web server instead of the Flask development server. Defaults to False.

  • **kwargs – will be passed forward to either waitress.serve() or app.run()