import json
import os
from urllib.parse import urljoin
from dash import Dash, dcc, callback_context, _validate
from dash._pages import _page_meta_tags
from dash._utils import interpolate_str, format_tag
from dash.version import __version__
from dash.dash import (
_re_index_entry_id,
_re_index_config_id,
_re_index_scripts_id,
_re_renderer_scripts_id,
)
from visualization_toolkit.constants import APP_ENVIRONMENT
from visualization_toolkit.helpers.plotly.theme import FONT_URLS
ATLAS_CDN_HOST = APP_ENVIRONMENT.get(
"ATLAS_CDN_HOST", "https://atlas-assets.yipitdata.com"
)
class CustomDash(Dash):
_app_entry = """
<div id="react-entry-point">
<style>
._dash-loading {
display: none !important;
}
</style>
<div class="_dash-loading">
</div>
</div>
"""
def index(self, *args, **kwargs): # pylint: disable=unused-argument
scripts = self._generate_scripts_html()
css = self._generate_css_dist_html()
config = self._generate_config_html()
metas = self._generate_meta()
renderer = self._generate_renderer()
# use self.title instead of app.config.title for backwards compatibility
title = self.title
if self.use_pages and self.config.include_pages_meta:
metas = _page_meta_tags(self) + metas
if self._favicon:
favicon_mod_time = os.path.getmtime(
os.path.join(self.config.assets_folder, self._favicon)
)
favicon_url = f"{self.get_asset_url(self._favicon)}?m={favicon_mod_time}"
else:
prefix = self.config.requests_pathname_prefix
favicon_url = f"{prefix}_favicon.ico?v={__version__}"
favicon = format_tag(
"link",
{"rel": "icon", "type": "image/x-icon", "href": favicon_url},
opened=True,
)
tags = "\n ".join(
format_tag("meta", x, opened=True, sanitize=True) for x in metas
)
index = self.interpolate_index(
metas=tags,
title=title,
css=css,
config=config,
scripts=scripts,
app_entry=self._app_entry,
favicon=favicon,
renderer=renderer,
)
checks = (
_re_index_entry_id,
_re_index_config_id,
_re_index_scripts_id,
_re_renderer_scripts_id,
)
_validate.validate_index("index", checks, index)
return index
[docs]
def initialize_app(
name: str,
**kwargs,
) -> CustomDash:
"""
Intialize a Dash app with settings tuned for production scenarios. Settings enabled:
* Load Google fonts for use with YD theme
* Load custom plotly bundle to reduce bundle size
* Enable compression of JS assets served
* Use CDN when serving static files (enabled when DEBUG mode is off)
* Change base html file to remove unnecessary CSS styles
:param name: Name of the dash app to initialize
:param kwargs: Optional kwargs that will be passed to the `Dash` class instance
:return: `CustomDash` instance that is a subclass of `Dash`
"""
# Pop off external_stylesheets and add them to the end when starting the dash app
if "external_stylesheets" in kwargs:
external_stylesheets = kwargs.pop("external_stylesheets")
else:
external_stylesheets = []
if "external_scripts" in kwargs:
external_scripts = kwargs.pop("external_scripts")
else:
external_scripts = []
app = CustomDash(
name,
external_stylesheets=FONT_URLS + external_stylesheets,
external_scripts=external_scripts,
update_title="",
serve_locally=False,
compress=True,
**kwargs,
)
return app
[docs]
def get_triggered_id(index: int = 0) -> str | dict:
"""
Returns the HTML ID for the triggered element within a dash callback
:param index: index of triggered input element to get ID for. Defaults to 0 or first triggered eement
:return: str
"""
ctx = callback_context
trigger_id = ctx.triggered[index]["prop_id"].split(".")[0]
# For pattern-matching callbacks, trigger id is in nested JSON, return as dict
if trigger_id.startswith("{"):
parsed_id = json.loads(trigger_id)
else:
parsed_id = trigger_id
return parsed_id
[docs]
def resolve_html_id(component: str | dict) -> str:
"""
Returns the true HTML ID for a given component, ID, or patten-matching ID
This can be useful when working in JS,
as Dash will sometimes mutate the ID passed in to a component
:param component: Input element or string to generate the HTML ID
:return:
"""
if isinstance(component, str):
return component
elif isinstance(component, dict):
return json.dumps(component, sort_keys=True, separators=(",", ":"))
else:
return getattr(component, "id")