diff --git a/MANIFEST.in b/MANIFEST.in
index 0389382d65..9013dea51e 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -10,3 +10,4 @@ include dash/html/*
include dash/dash_table/*
include dash/dash-renderer/build/*.js
include dash/dash-renderer/build/*.map
+include dash/py.typed
diff --git a/dash/_callback.py b/dash/_callback.py
index 434613098f..7ce6032a76 100644
--- a/dash/_callback.py
+++ b/dash/_callback.py
@@ -1,7 +1,9 @@
import collections
from functools import wraps
+from typing import Any, List, Dict, Callable, Union
from .dependencies import (
+ ClientsideFunction,
handle_callback_args,
handle_grouped_callback_args,
Output,
@@ -27,9 +29,9 @@ class NoUpdate:
pass
-GLOBAL_CALLBACK_LIST = []
-GLOBAL_CALLBACK_MAP = {}
-GLOBAL_INLINE_SCRIPTS = []
+GLOBAL_CALLBACK_LIST: List[dict] = []
+GLOBAL_CALLBACK_MAP: Dict[str, dict] = {}
+GLOBAL_INLINE_SCRIPTS: List[str] = []
def callback(*_args, **_kwargs):
@@ -106,7 +108,7 @@ def insert_callback(
def register_callback(
callback_list, callback_map, config_prevent_initial_callbacks, *_args, **_kwargs
-):
+) -> Callable:
(
output,
flat_inputs,
@@ -137,9 +139,9 @@ def register_callback(
)
# pylint: disable=too-many-locals
- def wrap_func(func):
+ def wrap_func(func: Callable) -> Callable:
@wraps(func)
- def add_context(*args, **kwargs):
+ def add_context(*args, **kwargs): # type: ignore[no-untyped-def]
output_spec = kwargs.pop("outputs_list")
_validate.validate_output_spec(insert_output, output_spec, Output)
@@ -209,14 +211,14 @@ def add_context(*args, **kwargs):
def register_clientside_callback(
- callback_list,
- callback_map,
- config_prevent_initial_callbacks,
- inline_scripts,
- clientside_function,
- *args,
- **kwargs
-):
+ callback_list: List[dict],
+ callback_map: Dict[str, dict],
+ config_prevent_initial_callbacks: bool,
+ inline_scripts: List[str],
+ clientside_function: Union[ClientsideFunction, str],
+ *args: Any,
+ **kwargs: Any,
+) -> None:
output, inputs, state, prevent_initial_call = handle_callback_args(args, kwargs)
insert_callback(
callback_list,
@@ -238,8 +240,8 @@ def register_clientside_callback(
if isinstance(output, (list, tuple)):
out0 = output[0]
- namespace = "_dashprivate_{}".format(out0.component_id)
- function_name = "{}".format(out0.component_property)
+ namespace = f"_dashprivate_{out0.component_id}"
+ function_name = str(out0.component_property)
inline_scripts.append(
_inline_clientside_template.format(
diff --git a/dash/_callback_context.py b/dash/_callback_context.py
index 585de7fc47..ae62e90593 100644
--- a/dash/_callback_context.py
+++ b/dash/_callback_context.py
@@ -35,17 +35,17 @@ def __nonzero__(self):
# pylint: disable=no-init
class CallbackContext:
- @property
+ @property # type: ignore[misc]
@has_context
def inputs(self):
return getattr(flask.g, "input_values", {})
- @property
+ @property # type: ignore[misc]
@has_context
def states(self):
return getattr(flask.g, "state_values", {})
- @property
+ @property # type: ignore[misc]
@has_context
def triggered(self):
# For backward compatibility: previously `triggered` always had a
@@ -54,17 +54,17 @@ def triggered(self):
# look empty, but you can still do `triggered[0]["prop_id"].split(".")`
return getattr(flask.g, "triggered_inputs", []) or falsy_triggered
- @property
+ @property # type: ignore[misc]
@has_context
def args_grouping(self):
return getattr(flask.g, "args_grouping", [])
- @property
+ @property # type: ignore[misc]
@has_context
def outputs_grouping(self):
return getattr(flask.g, "outputs_grouping", [])
- @property
+ @property # type: ignore[misc]
@has_context
def outputs_list(self):
if self.using_outputs_grouping:
@@ -75,7 +75,7 @@ def outputs_list(self):
return getattr(flask.g, "outputs_list", [])
- @property
+ @property # type: ignore[misc]
@has_context
def inputs_list(self):
if self.using_args_grouping:
@@ -86,7 +86,7 @@ def inputs_list(self):
return getattr(flask.g, "inputs_list", [])
- @property
+ @property # type: ignore[misc]
@has_context
def states_list(self):
if self.using_args_grouping:
@@ -96,7 +96,7 @@ def states_list(self):
)
return getattr(flask.g, "states_list", [])
- @property
+ @property # type: ignore[misc]
@has_context
def response(self):
return getattr(flask.g, "dash_response")
@@ -125,7 +125,7 @@ def record_timing(name, duration=None, description=None):
setattr(flask.g, "timing_information", timing_information)
- @property
+ @property # type: ignore[misc]
@has_context
def using_args_grouping(self):
"""
@@ -134,7 +134,7 @@ def using_args_grouping(self):
"""
return getattr(flask.g, "using_args_grouping", [])
- @property
+ @property # type: ignore[misc]
@has_context
def using_outputs_grouping(self):
"""
diff --git a/dash/_configs.py b/dash/_configs.py
index 35a10b5b32..43dec9d521 100644
--- a/dash/_configs.py
+++ b/dash/_configs.py
@@ -1,11 +1,12 @@
import os
+from typing import Union, Optional, Tuple
# noinspection PyCompatibility
from . import exceptions
from ._utils import AttributeDict
-def load_dash_env_vars():
+def load_dash_env_vars() -> AttributeDict:
return AttributeDict(
{
var: os.getenv(var, os.getenv(var.lower()))
@@ -40,7 +41,9 @@ def load_dash_env_vars():
DASH_ENV_VARS = load_dash_env_vars() # used in tests
-def get_combined_config(name, val, default=None):
+def get_combined_config(
+ name: str, val: Optional[Union[bool, str]], default: Union[bool, str] = None
+) -> Optional[Union[bool, str]]:
"""Consolidate the config with priority from high to low provided init
value > OS environ > default."""
@@ -55,8 +58,10 @@ def get_combined_config(name, val, default=None):
def pathname_configs(
- url_base_pathname=None, routes_pathname_prefix=None, requests_pathname_prefix=None
-):
+ url_base_pathname: str = None,
+ routes_pathname_prefix: str = None,
+ requests_pathname_prefix: str = None,
+) -> Tuple[str, str, str]:
_pathname_config_error_message = """
{} This is ambiguous.
To fix this, set `routes_pathname_prefix` instead of `url_base_pathname`.
diff --git a/dash/_grouping.py b/dash/_grouping.py
index 89d62de8aa..1a6ec003b7 100644
--- a/dash/_grouping.py
+++ b/dash/_grouping.py
@@ -46,7 +46,7 @@ def flatten_grouping(grouping, schema=None):
return [grouping]
-def grouping_len(grouping):
+def grouping_len(grouping) -> int:
"""
Get the length of a grouping. The length equal to the number of scalar values
contained in the grouping, which is equivalent to the length of the list that would
diff --git a/dash/_utils.py b/dash/_utils.py
index 1c147065c4..de18f9a83a 100644
--- a/dash/_utils.py
+++ b/dash/_utils.py
@@ -9,6 +9,8 @@
import io
import json
from functools import wraps
+from typing import Dict
+
from . import exceptions
logger = logging.getLogger()
@@ -21,7 +23,7 @@ def to_json(value):
return to_json_plotly(value)
-def interpolate_str(template, **data):
+def interpolate_str(template, **data) -> str:
s = template
for k, v in data.items():
key = "{%" + k + "%}"
@@ -29,7 +31,13 @@ def interpolate_str(template, **data):
return s
-def format_tag(tag_name, attributes, inner="", closed=False, opened=False):
+def format_tag(
+ tag_name: str,
+ attributes: Dict[str, str],
+ inner: str = "",
+ closed: bool = False,
+ opened: bool = False,
+) -> str:
tag = "<{tag} {attributes}"
if closed:
tag += "/>"
diff --git a/dash/_watch.py b/dash/_watch.py
index 65c87e284a..75eff7acc2 100644
--- a/dash/_watch.py
+++ b/dash/_watch.py
@@ -2,18 +2,24 @@
import os
import re
import time
+from typing import List, Callable, DefaultDict
-def watch(folders, on_change, pattern=None, sleep_time=0.1):
- pattern = re.compile(pattern) if pattern else None
- watched = collections.defaultdict(lambda: -1)
+def watch(
+ folders: List[str],
+ on_change: Callable,
+ pattern: str = None,
+ sleep_time: float = 0.1,
+) -> None:
+ compiled_pattern = re.compile(pattern) if pattern else None
+ watched: DefaultDict[str, float] = collections.defaultdict(lambda: -1)
- def walk():
+ def walk() -> None:
walked = []
for folder in folders:
for current, _, files in os.walk(folder):
for f in files:
- if pattern and not pattern.search(f):
+ if compiled_pattern and not compiled_pattern.search(f):
continue
path = os.path.join(current, f)
diff --git a/dash/dash.py b/dash/dash.py
index b567e5b9d9..1bc6e4167f 100644
--- a/dash/dash.py
+++ b/dash/dash.py
@@ -12,6 +12,7 @@
import hashlib
import base64
from urllib.parse import urlparse
+from typing import Dict, Union, List, Optional, TypedDict
import flask
from flask_compress import Compress
@@ -104,6 +105,11 @@
_re_renderer_scripts_id = 'id="_dash-renderer', "new DashRenderer"
+class MetaTagDict(TypedDict):
+ name: str
+ content: str
+
+
class _NoUpdate:
# pylint: disable=too-few-public-methods
pass
@@ -127,33 +133,28 @@ class Dash:
your own ``server``, ``name`` will be used to help find assets.
Typically ``__name__`` (the magic global var, not a string) is the
best value to use. Default ``'__main__'``, env: ``DASH_APP_NAME``
- :type name: string
:param server: Sets the Flask server for your app. There are three options:
``True`` (default): Dash will create a new server
``False``: The server will be added later via ``app.init_app(server)``
where ``server`` is a ``flask.Flask`` instance.
``flask.Flask``: use this pre-existing Flask server.
- :type server: boolean or flask.Flask
:param assets_folder: a path, relative to the current working directory,
for extra files to be used in the browser. Default ``'assets'``.
All .js and .css files will be loaded immediately unless excluded by
``assets_ignore``, and other files such as images will be served if
requested.
- :type assets_folder: string
:param assets_url_path: The local urls for assets will be:
``requests_pathname_prefix + assets_url_path + '/' + asset_path``
where ``asset_path`` is the path to a file inside ``assets_folder``.
Default ``'assets'``.
- :type asset_url_path: string
:param assets_ignore: A regex, as a string to pass to ``re.compile``, for
assets to omit from immediate loading. Ignored files will still be
served if specifically requested. You cannot use this to prevent access
to sensitive files.
- :type assets_ignore: string
:param assets_external_path: an absolute URL from which to load assets.
Use with ``serve_locally=False``. assets_external_path is joined
@@ -163,49 +164,40 @@ class Dash:
but external serving can improve performance and reduce load on
the Dash server.
env: ``DASH_ASSETS_EXTERNAL_PATH``
- :type assets_external_path: string
:param include_assets_files: Default ``True``, set to ``False`` to prevent
immediate loading of any assets. Assets will still be served if
specifically requested. You cannot use this to prevent access
to sensitive files. env: ``DASH_INCLUDE_ASSETS_FILES``
- :type include_assets_files: boolean
:param url_base_pathname: A local URL prefix to use app-wide.
Default ``'/'``. Both `requests_pathname_prefix` and
`routes_pathname_prefix` default to `url_base_pathname`.
env: ``DASH_URL_BASE_PATHNAME``
- :type url_base_pathname: string
:param requests_pathname_prefix: A local URL prefix for file requests.
Defaults to `url_base_pathname`, and must end with
`routes_pathname_prefix`. env: ``DASH_REQUESTS_PATHNAME_PREFIX``
- :type requests_pathname_prefix: string
:param routes_pathname_prefix: A local URL prefix for JSON requests.
Defaults to ``url_base_pathname``, and must start and end
with ``'/'``. env: ``DASH_ROUTES_PATHNAME_PREFIX``
- :type routes_pathname_prefix: string
:param serve_locally: If ``True`` (default), assets and dependencies
(Dash and Component js and css) will be served from local URLs.
If ``False`` we will use CDN links where available.
- :type serve_locally: boolean
:param compress: Use gzip to compress files and data served by Flask.
Default ``False``
- :type compress: boolean
:param meta_tags: html tags to be added to the index page.
Each dict should have the attributes and values for one tag, eg:
``{'name': 'description', 'content': 'My App'}``
- :type meta_tags: list of dicts
:param index_string: Override the standard Dash index page.
Must contain the correct insertion markers to interpolate various
content into it depending on the app config and components used.
See https://dash.plotly.com/external-resources for details.
- :type index_string: string
:param external_scripts: Additional JS files to load with the page.
Each entry can be a string (the URL) or a dict with ``src`` (the URL)
@@ -223,7 +215,6 @@ class Dash:
ensure referenced IDs exist and props are valid. Set to ``True``
if your layout is dynamic, to bypass these checks.
env: ``DASH_SUPPRESS_CALLBACK_EXCEPTIONS``
- :type suppress_callback_exceptions: boolean
:param prevent_initial_callbacks: Default ``False``: Sets the default value
of ``prevent_initial_call`` for all callbacks added to the app.
@@ -236,12 +227,10 @@ class Dash:
:param show_undo_redo: Default ``False``, set to ``True`` to enable undo
and redo buttons for stepping through the history of the app state.
- :type show_undo_redo: boolean
:param extra_hot_reload_paths: A list of paths to watch for changes, in
addition to assets and known Python and JS code, if hot reloading is
enabled.
- :type extra_hot_reload_paths: list of strings
:param plugins: Extend Dash functionality by passing a list of objects
with a ``plug`` method, taking a single argument: this app, which will
@@ -264,30 +253,30 @@ class Dash:
def __init__(
self,
- name=None,
- server=True,
- assets_folder="assets",
- assets_url_path="assets",
- assets_ignore="",
- assets_external_path=None,
+ name: str = None,
+ server: Union[bool, flask.Flask] = True,
+ assets_folder: str = "assets",
+ assets_url_path: str = "assets",
+ assets_ignore: str = "",
+ assets_external_path: str = None,
eager_loading=False,
- include_assets_files=True,
- url_base_pathname=None,
- requests_pathname_prefix=None,
- routes_pathname_prefix=None,
- serve_locally=True,
- compress=None,
- meta_tags=None,
- index_string=_default_index,
+ include_assets_files: bool = True,
+ url_base_pathname: str = None,
+ requests_pathname_prefix: str = None,
+ routes_pathname_prefix: str = None,
+ serve_locally: bool = True,
+ compress: bool = None,
+ meta_tags: List[MetaTagDict] = None,
+ index_string: str = _default_index,
external_scripts=None,
external_stylesheets=None,
- suppress_callback_exceptions=None,
+ suppress_callback_exceptions: bool = None,
prevent_initial_callbacks=False,
- show_undo_redo=False,
- extra_hot_reload_paths=None,
+ show_undo_redo: bool = False,
+ extra_hot_reload_paths: List[str] = None,
plugins=None,
- title="Dash",
- update_title="Updating...",
+ title: str = "Dash",
+ update_title: str = "Updating...",
long_callback_manager=None,
**obsolete,
):
@@ -295,6 +284,9 @@ def __init__(
# 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)
+
+ self.server = Optional[flask.Flask]
+
if isinstance(server, flask.Flask):
self.server = server
if name is None:
@@ -378,7 +370,7 @@ def __init__(
self._callback_list = []
# list of inline scripts
- self._inline_scripts = []
+ self._inline_scripts: List[str] = []
# index_string has special setter so can't go in config
self._index_string = ""
@@ -411,7 +403,7 @@ def __init__(
changed_assets=[],
)
- self._assets_files = []
+ self._assets_files: List[str] = []
self._long_callback_count = 0
self._long_callback_manager = long_callback_manager
@@ -434,6 +426,11 @@ def init_app(self, app=None):
if app is not None:
self.server = app
+ if not isinstance(self.server, flask.Flask):
+ raise ValueError(
+ "app argument to init_app should be a flask.Flask instance"
+ )
+
assets_blueprint_name = "{}{}".format(
config.routes_pathname_prefix.replace("/", "_"), "dash_assets"
)
@@ -459,6 +456,21 @@ def _handle_error(_):
"""Handle a halted callback and return an empty 204 response."""
return "", 204
+ def _add_url(self, name, view_func, methods=("GET",)) -> None:
+
+ full_name = self.config.routes_pathname_prefix + name
+
+ self.server.add_url_rule(
+ full_name,
+ view_func=view_func,
+ endpoint=full_name,
+ methods=list(methods),
+ )
+
+ # record the url in Dash.routes so that it can be accessed later
+ # e.g. for adding authentication with flask_login
+ self.routes.append(full_name)
+
self.server.before_first_request(self._setup_server)
# add a handler for components suites errors to return 404
@@ -478,21 +490,6 @@ def _handle_error(_):
# catch-all for front-end routes, used by dcc.Location
self._add_url("", self.index)
- def _add_url(self, name, view_func, methods=("GET",)):
- full_name = self.config.routes_pathname_prefix + name
-
- self.server.add_url_rule(
- full_name, view_func=view_func, endpoint=full_name, methods=list(methods)
- )
-
- # record the url in Dash.routes so that it can be accessed later
- # e.g. for adding authentication with flask_login
- self.routes.append(full_name)
-
- @property
- def layout(self):
- return self._layout
-
def _layout_value(self):
layout = self._layout() if self._layout_is_function else self._layout
@@ -502,6 +499,10 @@ def _layout_value(self):
return layout
+ @property
+ def layout(self):
+ return self._layout
+
@layout.setter
def layout(self, value):
_validate.validate_layout_type(value)
@@ -552,7 +553,7 @@ def index_string(self, value):
_validate.validate_index("index string", checks, value)
self._index_string = value
- def serve_layout(self):
+ def serve_layout(self) -> flask.Response:
layout = self._layout_value()
# TODO - Set browser cache limit - pass hash into frontend
@@ -561,7 +562,7 @@ def serve_layout(self):
mimetype="application/json",
)
- def _config(self):
+ def _config(self) -> dict:
# pieces of config needed by the front end
config = {
"url_base_pathname": self.config.url_base_pathname,
@@ -674,7 +675,7 @@ def _relative_url_path(relative_package_path="", namespace=""):
srcs.append(static_url)
return srcs
- def _generate_css_dist_html(self):
+ def _generate_css_dist_html(self) -> str:
external_links = self.config.external_stylesheets
links = self._collect_and_register_resources(self.css.get_all_css())
@@ -687,7 +688,7 @@ def _generate_css_dist_html(self):
]
)
- def _generate_scripts_html(self):
+ def _generate_scripts_html(self) -> str:
# Dash renderer has dependencies like React which need to be rendered
# before every other script. However, the dash renderer bundle
# itself needs to be rendered after all of the component's
@@ -742,19 +743,17 @@ def _generate_scripts_html(self):
+ ["".format(src) for src in self._inline_scripts]
)
- def _generate_config_html(self):
- return ''.format(
- to_json(self._config())
- )
+ def _generate_config_html(self) -> str:
+ return f''
- def _generate_renderer(self):
+ def _generate_renderer(self) -> str:
return (
'"
- ).format(self.renderer)
+ )
- def _generate_meta_html(self):
+ def _generate_meta_html(self) -> str:
meta_tags = self.config.meta_tags
has_ie_compat = any(
x.get("http-equiv", "") == "X-UA-Compatible" for x in meta_tags
@@ -860,15 +859,15 @@ def index(self, *args, **kwargs): # pylint: disable=unused-argument
def interpolate_index(
self,
- metas="",
- title="",
- css="",
- config="",
- scripts="",
- app_entry="",
- favicon="",
- renderer="",
- ):
+ metas: str = "",
+ title: str = "",
+ css: str = "",
+ config: str = "",
+ scripts: str = "",
+ app_entry: str = "",
+ favicon: str = "",
+ renderer: str = "",
+ ) -> str:
"""Called to create the initial HTML string that is loaded on page.
Override this method to provide you own custom HTML.
@@ -1336,7 +1335,7 @@ def dispatch(self):
response.set_data(func(*args, outputs_list=outputs_list))
return response
- def _setup_server(self):
+ def _setup_server(self) -> None:
# Apply _force_eager_loading overrides from modules
eager_loading = self.config.eager_loading
for module_name in ComponentRegistry.registry:
@@ -1371,14 +1370,14 @@ def _setup_server(self):
self._callback_list.extend(_callback.GLOBAL_CALLBACK_LIST)
_callback.GLOBAL_CALLBACK_LIST.clear()
- def _add_assets_resource(self, url_path, file_path):
+ def _add_assets_resource(self, url_path, file_path) -> Dict[str, str]:
res = {"asset_path": url_path, "filepath": file_path}
if self.config.assets_external_path:
res["external_url"] = self.get_asset_url(url_path.lstrip("/"))
self._assets_files.append(file_path)
return res
- def _walk_assets_directory(self):
+ def _walk_assets_directory(self) -> None:
walk_dir = self.config.assets_folder
slash_splitter = re.compile(r"[\\/]+")
ignore_str = self.config.assets_ignore
@@ -1422,7 +1421,7 @@ def _serve_default_favicon():
pkgutil.get_data("dash", "favicon.ico"), content_type="image/x-icon"
)
- def csp_hashes(self, hash_algorithm="sha256"):
+ def csp_hashes(self, hash_algorithm: str = "sha256") -> List[str]:
"""Calculates CSP hashes (sha + base64) of all inline scripts, such that
one of the biggest benefits of CSP (disallowing general inline scripts)
can be utilized together with Dash clientside callbacks (inline scripts).
@@ -1471,7 +1470,7 @@ def get_asset_url(self, path):
return asset
- def get_relative_path(self, path):
+ def get_relative_path(self, path: str) -> str:
"""
Return a path with `requests_pathname_prefix` prefixed before it.
Use this function when specifying local URL paths that will work
@@ -1808,7 +1807,7 @@ def _after_request(response):
return debug
# noinspection PyProtectedMember
- def _on_assets_change(self, filename, modified, deleted):
+ def _on_assets_change(self, filename: str, modified, deleted: bool) -> None:
_reload = self._hot_reload
with _reload.lock:
_reload.hard = True
@@ -1861,19 +1860,19 @@ def delete_resource(resources):
def run_server(
self,
- host=os.getenv("HOST", "127.0.0.1"),
- port=os.getenv("PORT", "8050"),
- proxy=os.getenv("DASH_PROXY", None),
- debug=False,
- dev_tools_ui=None,
- dev_tools_props_check=None,
- dev_tools_serve_dev_bundles=None,
- dev_tools_hot_reload=None,
- dev_tools_hot_reload_interval=None,
- dev_tools_hot_reload_watch_interval=None,
- dev_tools_hot_reload_max_retry=None,
- dev_tools_silence_routes_logging=None,
- dev_tools_prune_errors=None,
+ host: str = os.getenv("HOST", "127.0.0.1"),
+ port: int = int(os.getenv("PORT", "8050")),
+ proxy: Optional[str] = os.getenv("DASH_PROXY", None),
+ debug: bool = False,
+ dev_tools_ui: bool = None,
+ dev_tools_props_check: bool = None,
+ dev_tools_serve_dev_bundles: bool = None,
+ dev_tools_hot_reload: bool = None,
+ dev_tools_hot_reload_interval: float = None,
+ dev_tools_hot_reload_watch_interval: float = None,
+ dev_tools_hot_reload_max_retry: int = None,
+ dev_tools_silence_routes_logging: bool = None,
+ dev_tools_prune_errors: bool = None,
**flask_run_options,
):
"""Start the flask server in local mode, you should not run this on a
@@ -1884,11 +1883,9 @@ def run_server(
:param host: Host IP used to serve the application
env: ``HOST``
- :type host: string
:param port: Port used to serve the application
env: ``PORT``
- :type port: int
:param proxy: If this application will be served to a different URL
via a proxy configured outside of Python, you can list it here
@@ -1896,65 +1893,55 @@ def run_server(
``"http://0.0.0.0:8050::https://my.domain.com"``
so that the startup message will display an accurate URL.
env: ``DASH_PROXY``
- :type proxy: string
-
- :param debug: Set Flask debug mode and enable dev tools.
- env: ``DASH_DEBUG``
- :type debug: bool
:param debug: Enable/disable all the dev tools unless overridden by the
arguments or environment variables. Default is ``True`` when
``enable_dev_tools`` is called directly, and ``False`` when called
- via ``run_server``. env: ``DASH_DEBUG``
- :type debug: bool
+ via ``run_server``.
+ env: ``DASH_DEBUG``
:param dev_tools_ui: Show the dev tools UI. env: ``DASH_UI``
- :type dev_tools_ui: bool
:param dev_tools_props_check: Validate the types and values of Dash
component props. env: ``DASH_PROPS_CHECK``
- :type dev_tools_props_check: bool
:param dev_tools_serve_dev_bundles: Serve the dev bundles. Production
bundles do not necessarily include all the dev tools code.
env: ``DASH_SERVE_DEV_BUNDLES``
- :type dev_tools_serve_dev_bundles: bool
:param dev_tools_hot_reload: Activate hot reloading when app, assets,
and component files change. env: ``DASH_HOT_RELOAD``
- :type dev_tools_hot_reload: bool
:param dev_tools_hot_reload_interval: Interval in seconds for the
client to request the reload hash. Default 3.
env: ``DASH_HOT_RELOAD_INTERVAL``
- :type dev_tools_hot_reload_interval: float
:param dev_tools_hot_reload_watch_interval: Interval in seconds for the
server to check asset and component folders for changes.
Default 0.5. env: ``DASH_HOT_RELOAD_WATCH_INTERVAL``
- :type dev_tools_hot_reload_watch_interval: float
:param dev_tools_hot_reload_max_retry: Maximum number of failed reload
hash requests before failing and displaying a pop up. Default 8.
env: ``DASH_HOT_RELOAD_MAX_RETRY``
- :type dev_tools_hot_reload_max_retry: int
:param dev_tools_silence_routes_logging: Silence the `werkzeug` logger,
will remove all routes logging. Enabled with debugging by default
because hot reload hash checks generate a lot of requests.
env: ``DASH_SILENCE_ROUTES_LOGGING``
- :type dev_tools_silence_routes_logging: bool
:param dev_tools_prune_errors: Reduce tracebacks to just user code,
stripping out Flask and Dash pieces. Only available with debugging.
`True` by default, set to `False` to see the complete traceback.
env: ``DASH_PRUNE_ERRORS``
- :type dev_tools_prune_errors: bool
:param flask_run_options: Given to `Flask.run`
:return:
"""
+
+ if not isinstance(self.server, flask.Flask):
+ raise ValueError("You need to run init_app first")
+
debug = self.enable_dev_tools(
debug,
dev_tools_ui,
@@ -1973,9 +1960,7 @@ def run_server(
port = int(port)
assert port in range(1, 65536)
except Exception as e:
- e.args = [
- "Expecting an integer from 1 to 65535, found port={}".format(repr(port))
- ]
+ e.args = [f"Expecting an integer from 1 to 65535, found port={repr(port)}"]
raise
# so we only see the "Running on" message once with hot reloading
@@ -1988,21 +1973,15 @@ def run_server(
if proxy:
served_url, proxied_url = map(urlparse, proxy.split("::"))
- def verify_url_part(served_part, url_part, part_name):
+ def verify_url_part(served_part, url_part, part_name) -> None:
if served_part != url_part:
raise ProxyError(
- """
- {0}: {1} is incompatible with the proxy:
- {3}
- To see your app at {4},
- you must use {0}: {2}
- """.format(
- part_name,
- url_part,
- served_part,
- proxy,
- proxied_url.geturl(),
- )
+ f"""
+ {part_name}: {url_part} is incompatible with the proxy:
+ {proxy}
+ To see your app at {proxied_url.geturl()},
+ you must use {part_name}: {served_part}
+ """
)
verify_url_part(served_url.scheme, protocol, "protocol")
@@ -2012,11 +1991,11 @@ def verify_url_part(served_part, url_part, part_name):
display_url = (
proxied_url.scheme,
proxied_url.hostname,
- (":{}".format(proxied_url.port) if proxied_url.port else ""),
+ (f":{proxied_url.port}" if proxied_url.port else ""),
path,
)
else:
- display_url = (protocol, host, ":{}".format(port), path)
+ display_url = (protocol, host, ":{port}", path)
self.logger.info("Dash is running on %s://%s%s%s\n", *display_url)
diff --git a/dash/exceptions.py b/dash/exceptions.py
index fd22dfa050..9dc6b9101e 100644
--- a/dash/exceptions.py
+++ b/dash/exceptions.py
@@ -2,7 +2,7 @@
class DashException(Exception):
- def __init__(self, msg=""):
+ def __init__(self, msg: str = "") -> None:
super().__init__(dedent(msg).strip())
diff --git a/dash/fingerprint.py b/dash/fingerprint.py
index f41efff262..d0deac1e57 100644
--- a/dash/fingerprint.py
+++ b/dash/fingerprint.py
@@ -1,10 +1,11 @@
import re
+from typing import Tuple
cache_regex = re.compile(r"^v[\w-]+m[0-9a-fA-F]+$")
version_clean = re.compile(r"[^\w-]")
-def build_fingerprint(path, version, hash_value):
+def build_fingerprint(path: str, version: str, hash_value: str) -> str:
path_parts = path.split("/")
filename, extension = path_parts[-1].split(".", 1)
@@ -16,7 +17,7 @@ def build_fingerprint(path, version, hash_value):
)
-def check_fingerprint(path):
+def check_fingerprint(path: str) -> Tuple[str, bool]:
path_parts = path.split("/")
name_parts = path_parts[-1].split(".")
diff --git a/dash/py.typed b/dash/py.typed
new file mode 100644
index 0000000000..517cb45ae0
--- /dev/null
+++ b/dash/py.typed
@@ -0,0 +1 @@
+# Marker file to indicate that this package supports typing, ref PEP 561
diff --git a/mypy.ini b/mypy.ini
new file mode 100644
index 0000000000..db3928823d
--- /dev/null
+++ b/mypy.ini
@@ -0,0 +1,43 @@
+# Global options
+
+[mypy]
+ignore_missing_imports = True
+disallow_untyped_defs = True
+show_error_codes = True
+
+#########################
+
+[mypy-dash.development.*]
+ignore_errors=True
+
+[mypy-dash.testing.*]
+ignore_errors=True
+
+[mypy-dash.dash]
+ignore_errors=True
+
+[mypy-dash.resources]
+ignore_errors=True
+
+[mypy-dash._callback_context]
+ignore_errors=True
+
+[mypy-dash._validate]
+ignore_errors=True
+
+[mypy-dash.dependencies]
+ignore_errors=True
+
+#########################
+
+[mypy-dash.html.*]
+ignore_errors=True
+
+[mypy-dash.dcc.*]
+ignore_errors=True
+
+[mypy-dash.dash_table.*]
+ignore_errors=True
+
+[mypy-dash.long_callback.managers.*]
+ignore_errors=True
diff --git a/requires-dev.txt b/requires-dev.txt
index 3773ced3c1..4e0b970d87 100644
--- a/requires-dev.txt
+++ b/requires-dev.txt
@@ -11,3 +11,4 @@ coloredlogs==15.0.1
flask-talisman==0.8.1
orjson==3.3.1;python_version<"3.7"
orjson==3.6.1;python_version>="3.7"
+mypy==0.910