Skip to content

Commit daccac8

Browse files
authored
Merge branch 'dash-3.0' into feat/tsx-prop-types
2 parents 8930494 + 5fe20c7 commit daccac8

23 files changed

+1177
-90
lines changed

.pylintrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ disable=fixme,
7575
unnecessary-lambda-assignment,
7676
broad-exception-raised,
7777
consider-using-generator,
78+
too-many-ancestors
7879

7980

8081
# Enable the message, report, category or checker with the given id(s). You can

@plotly/dash-generator-test-component-typescript/base/py.typed

Whitespace-only changes.

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ include dash/dash-renderer/build/*.js
1212
include dash/dash-renderer/build/*.map
1313
include dash/labextension/dist/dash-jupyterlab.tgz
1414
include dash/labextension/package.json
15+
include dash/py.typed

dash/__init__.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
from ._patch import Patch # noqa: F401,E402
4242
from ._jupyter import jupyter_dash # noqa: F401,E402
4343

44+
from ._hooks import hooks # noqa: F401,E402
45+
4446
ctx = callback_context
4547

4648

@@ -53,3 +55,37 @@ def _jupyter_nbextension_paths():
5355
"require": "dash/main",
5456
}
5557
]
58+
59+
60+
__all__ = [
61+
"Input",
62+
"Output",
63+
"State",
64+
"ClientsideFunction",
65+
"MATCH",
66+
"ALLSMALLER",
67+
"ALL",
68+
"development",
69+
"exceptions",
70+
"dcc",
71+
"html",
72+
"dash_table",
73+
"__version__",
74+
"callback_context",
75+
"set_props",
76+
"callback",
77+
"get_app",
78+
"get_asset_url",
79+
"get_relative_path",
80+
"strip_relative_path",
81+
"CeleryManager",
82+
"DiskcacheManager",
83+
"register_page",
84+
"page_registry",
85+
"Dash",
86+
"no_update",
87+
"page_container",
88+
"Patch",
89+
"jupyter_dash",
90+
"ctx",
91+
]

dash/_callback.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
import collections
22
import hashlib
33
from functools import wraps
4-
from typing import Callable, Optional, Any
4+
5+
from typing import Callable, Optional, Any, List, Tuple, Union
6+
57

68
import flask
79

810
from .dependencies import (
911
handle_callback_args,
1012
handle_grouped_callback_args,
1113
Output,
14+
ClientsideFunction,
15+
Input,
1216
)
1317
from .development.base_component import ComponentRegistry
1418
from .exceptions import (
@@ -62,14 +66,14 @@ def is_no_update(obj):
6266
# pylint: disable=too-many-locals
6367
def callback(
6468
*_args,
65-
background=False,
66-
interval=1000,
67-
progress=None,
68-
progress_default=None,
69-
running=None,
70-
cancel=None,
71-
manager=None,
72-
cache_args_to_ignore=None,
69+
background: bool = False,
70+
interval: int = 1000,
71+
progress: Optional[Output] = None,
72+
progress_default: Any = None,
73+
running: Optional[List[Tuple[Output, Any, Any]]] = None,
74+
cancel: Optional[List[Input]] = None,
75+
manager: Optional[BaseLongCallbackManager] = None,
76+
cache_args_to_ignore: Optional[list] = None,
7377
on_error: Optional[Callable[[Exception], Any]] = None,
7478
**_kwargs,
7579
):
@@ -156,7 +160,7 @@ def callback(
156160
callback_list = _kwargs.pop("callback_list", GLOBAL_CALLBACK_LIST)
157161

158162
if background:
159-
long_spec = {
163+
long_spec: Any = {
160164
"interval": interval,
161165
}
162166

@@ -209,7 +213,10 @@ def validate_long_inputs(deps):
209213
)
210214

211215

212-
def clientside_callback(clientside_function, *args, **kwargs):
216+
ClientsideFuncType = Union[str, ClientsideFunction]
217+
218+
219+
def clientside_callback(clientside_function: ClientsideFuncType, *args, **kwargs):
213220
return register_clientside_callback(
214221
GLOBAL_CALLBACK_LIST,
215222
GLOBAL_CALLBACK_MAP,
@@ -596,7 +603,7 @@ def register_clientside_callback(
596603
callback_map,
597604
config_prevent_initial_callbacks,
598605
inline_scripts,
599-
clientside_function,
606+
clientside_function: ClientsideFuncType,
600607
*args,
601608
**kwargs,
602609
):

dash/_hooks.py

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
import typing as _t
2+
3+
from importlib import metadata as _importlib_metadata
4+
5+
import typing_extensions as _tx
6+
import flask as _f
7+
8+
from .exceptions import HookError
9+
from .resources import ResourceType
10+
from ._callback import ClientsideFuncType
11+
12+
if _t.TYPE_CHECKING:
13+
from .dash import Dash
14+
from .development.base_component import Component
15+
16+
ComponentType = _t.TypeVar("ComponentType", bound=Component)
17+
LayoutType = _t.Union[ComponentType, _t.List[ComponentType]]
18+
else:
19+
LayoutType = None
20+
ComponentType = None
21+
Dash = None
22+
23+
24+
HookDataType = _tx.TypeVar("HookDataType")
25+
26+
27+
# pylint: disable=too-few-public-methods
28+
class _Hook(_tx.Generic[HookDataType]):
29+
def __init__(self, func, priority=0, final=False, data: HookDataType = None):
30+
self.func = func
31+
self.final = final
32+
self.data = data
33+
self.priority = priority
34+
35+
def __call__(self, *args, **kwargs):
36+
return self.func(*args, **kwargs)
37+
38+
39+
class _Hooks:
40+
def __init__(self) -> None:
41+
self._ns = {
42+
"setup": [],
43+
"layout": [],
44+
"routes": [],
45+
"error": [],
46+
"callback": [],
47+
"index": [],
48+
}
49+
self._js_dist = []
50+
self._css_dist = []
51+
self._clientside_callbacks: _t.List[
52+
_t.Tuple[ClientsideFuncType, _t.Any, _t.Any]
53+
] = []
54+
55+
# final hooks are a single hook added to the end of regular hooks.
56+
self._finals = {}
57+
58+
def add_hook(
59+
self,
60+
hook: str,
61+
func: _t.Callable,
62+
priority: _t.Optional[int] = None,
63+
final=False,
64+
data=None,
65+
):
66+
if final:
67+
existing = self._finals.get(hook)
68+
if existing:
69+
raise HookError("Final hook already present")
70+
self._finals[hook] = _Hook(func, final, data=data)
71+
return
72+
hks = self._ns.get(hook, [])
73+
74+
p = 0
75+
if not priority and len(hks):
76+
priority_max = max(h.priority for h in hks)
77+
p = priority_max - 1
78+
79+
hks.append(_Hook(func, priority=p, data=data))
80+
self._ns[hook] = sorted(hks, reverse=True, key=lambda h: h.priority)
81+
82+
def get_hooks(self, hook: str) -> _t.List[_Hook]:
83+
final = self._finals.get(hook, None)
84+
if final:
85+
final = [final]
86+
else:
87+
final = []
88+
return self._ns.get(hook, []) + final
89+
90+
def layout(self, priority: _t.Optional[int] = None, final: bool = False):
91+
"""
92+
Run a function when serving the layout, the return value
93+
will be used as the layout.
94+
"""
95+
96+
def _wrap(func: _t.Callable[[LayoutType], LayoutType]):
97+
self.add_hook("layout", func, priority=priority, final=final)
98+
return func
99+
100+
return _wrap
101+
102+
def setup(self, priority: _t.Optional[int] = None, final: bool = False):
103+
"""
104+
Can be used to get a reference to the app after it is instantiated.
105+
"""
106+
107+
def _setup(func: _t.Callable[[Dash], None]):
108+
self.add_hook("setup", func, priority=priority, final=final)
109+
return func
110+
111+
return _setup
112+
113+
def route(
114+
self,
115+
name: _t.Optional[str] = None,
116+
methods: _t.Sequence[str] = ("GET",),
117+
priority: _t.Optional[int] = None,
118+
final=False,
119+
):
120+
"""
121+
Add a route to the Dash server.
122+
"""
123+
124+
def wrap(func: _t.Callable[[], _f.Response]):
125+
_name = name or func.__name__
126+
self.add_hook(
127+
"routes",
128+
func,
129+
priority=priority,
130+
final=final,
131+
data=dict(name=_name, methods=methods),
132+
)
133+
return func
134+
135+
return wrap
136+
137+
def error(self, priority: _t.Optional[int] = None, final=False):
138+
"""Automatically add an error handler to the dash app."""
139+
140+
def _error(func: _t.Callable[[Exception], _t.Any]):
141+
self.add_hook("error", func, priority=priority, final=final)
142+
return func
143+
144+
return _error
145+
146+
def callback(self, *args, priority: _t.Optional[int] = None, final=False, **kwargs):
147+
"""
148+
Add a callback to all the apps with the hook installed.
149+
"""
150+
151+
def wrap(func):
152+
self.add_hook(
153+
"callback",
154+
func,
155+
priority=priority,
156+
final=final,
157+
data=(list(args), dict(kwargs)),
158+
)
159+
return func
160+
161+
return wrap
162+
163+
def clientside_callback(
164+
self, clientside_function: ClientsideFuncType, *args, **kwargs
165+
):
166+
"""
167+
Add a callback to all the apps with the hook installed.
168+
"""
169+
self._clientside_callbacks.append((clientside_function, args, kwargs))
170+
171+
def script(self, distribution: _t.List[ResourceType]):
172+
"""Add js scripts to the page."""
173+
self._js_dist.extend(distribution)
174+
175+
def stylesheet(self, distribution: _t.List[ResourceType]):
176+
"""Add stylesheets to the page."""
177+
self._css_dist.extend(distribution)
178+
179+
def index(self, priority: _t.Optional[int] = None, final=False):
180+
"""Modify the index of the apps."""
181+
182+
def wrap(func):
183+
self.add_hook(
184+
"index",
185+
func,
186+
priority=priority,
187+
final=final,
188+
)
189+
return func
190+
191+
return wrap
192+
193+
194+
hooks = _Hooks()
195+
196+
197+
class HooksManager:
198+
# Flag to only run `register_setuptools` once
199+
_registered = False
200+
hooks = hooks
201+
202+
# pylint: disable=too-few-public-methods
203+
class HookErrorHandler:
204+
def __init__(self, original):
205+
self.original = original
206+
207+
def __call__(self, err: Exception):
208+
result = None
209+
if self.original:
210+
result = self.original(err)
211+
hook_result = None
212+
for hook in HooksManager.get_hooks("error"):
213+
hook_result = hook(err)
214+
return result or hook_result
215+
216+
@classmethod
217+
def get_hooks(cls, hook: str):
218+
return cls.hooks.get_hooks(hook)
219+
220+
@classmethod
221+
def register_setuptools(cls):
222+
if cls._registered:
223+
# Only have to register once.
224+
return
225+
226+
for dist in _importlib_metadata.distributions():
227+
for entry in dist.entry_points:
228+
# Look for setup.py entry points named `dash-hooks`
229+
if entry.group != "dash-hooks":
230+
continue
231+
entry.load()

0 commit comments

Comments
 (0)