Skip to content

Commit 0049cab

Browse files
authored
Merge pull request #3175 from plotly/external-children
3.0 RC3 changes.
2 parents 7abf17e + 4336b34 commit 0049cab

File tree

16 files changed

+181
-20
lines changed

16 files changed

+181
-20
lines changed

Diff for: @plotly/dash-test-components/src/components/ExternalComponent.js

+14-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react';
22
import PropTypes from 'prop-types';
33

44

5-
const ExternalComponent = ({ id, text, input_id }) => {
5+
const ExternalComponent = ({ id, text, input_id, extra_component }) => {
66
const ctx = window.dash_component_api.useDashContext();
77
const ExternalWrapper = window.dash_component_api.ExternalWrapper;
88

@@ -15,6 +15,14 @@ const ExternalComponent = ({ id, text, input_id }) => {
1515
value={text}
1616
componentPath={[...ctx.componentPath, 'external']}
1717
/>
18+
{
19+
extra_component &&
20+
<ExternalWrapper
21+
componentType={extra_component.type}
22+
componentNamespace={extra_component.namespace}
23+
componentPath={[...ctx.componentPath, 'extra']}
24+
{...extra_component.props}
25+
/>}
1826
</div>
1927
)
2028
}
@@ -23,6 +31,11 @@ ExternalComponent.propTypes = {
2331
id: PropTypes.string,
2432
text: PropTypes.string,
2533
input_id: PropTypes.string,
34+
extra_component: PropTypes.exact({
35+
type: PropTypes.string,
36+
namespace: PropTypes.string,
37+
props: PropTypes.object,
38+
}),
2639
};
2740

2841
export default ExternalComponent;

Diff for: CHANGELOG.md

+13
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@
22
All notable changes to `dash` will be documented in this file.
33
This project adheres to [Semantic Versioning](https://semver.org/).
44

5+
## [3.0.0-rc3] - 2025-02-21
6+
7+
## Added
8+
9+
- [#3121](https://github.com/plotly/dash/pull/3121) Restyle and add version checker to dev tools.
10+
- [#3175](https://github.com/plotly/dash/pull/3175) Add `custom_data` hook.
11+
- [#3175](https://github.com/plotly/dash/pull/3175) Improved error for removed Dash app attribute, run_server and long_callback
12+
- [#3175](https://github.com/plotly/dash/pull/3175) Expose `stringifyId` in `window.dash_component_api`.
13+
14+
## Fixed
15+
16+
- [#3175](https://github.com/plotly/dash/pull/3175) Fix `ExternalWrapper` rendering children and support pattern matching ids.
17+
518
## [3.0.0-rc2] - 2025-02-18
619

720
## Added

Diff for: dash/_callback_context.py

+8
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,14 @@ def origin(self):
304304
"""
305305
return _get_from_context("origin", "")
306306

307+
@property
308+
@has_context
309+
def custom_data(self):
310+
"""
311+
Custom data set by hooks.custom_data.
312+
"""
313+
return _get_from_context("custom_data", {})
314+
307315

308316
callback_context = CallbackContext()
309317

Diff for: dash/_dash_renderer.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import os
22

3-
__version__ = "2.0.1"
3+
__version__ = "2.0.2"
44

55
_available_react_versions = {"18.3.1", "18.2.0", "16.14.0"}
66
_available_reactdom_versions = {"18.3.1", "18.2.0", "16.14.0"}
@@ -64,7 +64,7 @@ def _set_react_version(v_react, v_reactdom=None):
6464
{
6565
"relative_package_path": "dash-renderer/build/dash_renderer.min.js",
6666
"dev_package_path": "dash-renderer/build/dash_renderer.dev.js",
67-
"external_url": "https://unpkg.com/[email protected].1"
67+
"external_url": "https://unpkg.com/[email protected].2"
6868
"/build/dash_renderer.min.js",
6969
"namespace": "dash",
7070
},

Diff for: dash/_hooks.py

+23
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ def __init__(self) -> None:
4545
"error": [],
4646
"callback": [],
4747
"index": [],
48+
"custom_data": [],
4849
}
4950
self._js_dist = []
5051
self._css_dist = []
@@ -191,6 +192,28 @@ def wrap(func):
191192

192193
return wrap
193194

195+
def custom_data(
196+
self, namespace: str, priority: _t.Optional[int] = None, final=False
197+
):
198+
"""
199+
Add data to the callback_context.custom_data property under the namespace.
200+
201+
The hook function takes the current context_value and before the ctx is set
202+
and has access to the flask request context.
203+
"""
204+
205+
def wrap(func: _t.Callable[[_t.Dict], _t.Any]):
206+
self.add_hook(
207+
"custom_data",
208+
func,
209+
priority=priority,
210+
final=final,
211+
data={"namespace": namespace},
212+
)
213+
return func
214+
215+
return wrap
216+
194217

195218
hooks = _Hooks()
196219

Diff for: dash/_obsolete.py

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# pylint: disable=too-few-public-methods
2+
from .exceptions import ObsoleteAttributeException
3+
4+
5+
class ObsoleteAttribute:
6+
def __init__(self, message: str, exc=ObsoleteAttributeException):
7+
self.message = message
8+
self.exc = exc
9+
10+
11+
class ObsoleteChecker:
12+
_obsolete_attributes = {
13+
"run_server": ObsoleteAttribute("app.run_server has been replaced by app.run"),
14+
"long_callback": ObsoleteAttribute(
15+
"app.long_callback has been removed, use app.callback(..., background=True) instead"
16+
),
17+
}
18+
19+
def __getattr__(self, name: str):
20+
if name in self._obsolete_attributes:
21+
err = self._obsolete_attributes[name]
22+
raise err.exc(err.message)
23+
return getattr(self, name)

Diff for: dash/dash-renderer/package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: dash/dash-renderer/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "dash-renderer",
3-
"version": "2.0.1",
3+
"version": "2.0.2",
44
"description": "render dash components in react",
55
"main": "build/dash_renderer.min.js",
66
"scripts": {
@@ -13,7 +13,7 @@
1313
"build:dev": "webpack",
1414
"build:local": "renderer build local",
1515
"build": "renderer build && npm run prepublishOnly",
16-
"postbuild": "es-check es2018 ../deps/*.js build/*.js",
16+
"postbuild": "es-check es2015 ../deps/*.js build/*.js",
1717
"test": "karma start karma.conf.js --single-run",
1818
"format": "run-s private::format.*",
1919
"lint": "run-s private::lint.* --continue-on-error"

Diff for: dash/dash-renderer/src/actions/index.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {getAction} from './constants';
66
import cookie from 'cookie';
77
import {validateCallbacksToLayout} from './dependencies';
88
import {includeObservers, getLayoutCallbacks} from './dependencies_ts';
9-
import {getPath} from './paths';
9+
import {computePaths, getPath} from './paths';
1010

1111
export const onError = createAction(getAction('ON_ERROR'));
1212
export const setAppLifecycle = createAction(getAction('SET_APP_LIFECYCLE'));
@@ -21,6 +21,14 @@ export const updateProps = createAction(getAction('ON_PROP_CHANGE'));
2121
export const insertComponent = createAction(getAction('INSERT_COMPONENT'));
2222
export const removeComponent = createAction(getAction('REMOVE_COMPONENT'));
2323

24+
export const addComponentToLayout = payload => (dispatch, getState) => {
25+
const {paths} = getState();
26+
dispatch(insertComponent(payload));
27+
dispatch(
28+
setPaths(computePaths(payload.component, payload.componentPath, paths))
29+
);
30+
};
31+
2432
export const dispatchError = dispatch => (message, lines) =>
2533
dispatch(
2634
onError({

Diff for: dash/dash-renderer/src/dashApi.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {DashContext, useDashContext} from './wrapper/DashContext';
33
import {getPath} from './actions/paths';
44
import {getStores} from './utils/stores';
55
import ExternalWrapper from './wrapper/ExternalWrapper';
6+
import {stringifyId} from './actions/dependencies';
67

78
/**
89
* Get the dash props from a component path or id.
@@ -32,5 +33,6 @@ function getLayout(componentPathOrId: string[] | string): any {
3233
ExternalWrapper,
3334
DashContext,
3435
useDashContext,
35-
getLayout
36+
getLayout,
37+
stringifyId
3638
};

Diff for: dash/dash-renderer/src/wrapper/ExternalWrapper.tsx

+18-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import React, {useState, useEffect} from 'react';
2-
import {useDispatch} from 'react-redux';
2+
import {batch, useDispatch} from 'react-redux';
33

44
import {DashLayoutPath} from '../types/component';
55
import DashWrapper from './DashWrapper';
6-
import {insertComponent, removeComponent} from '../actions';
6+
import {
7+
addComponentToLayout,
8+
notifyObservers,
9+
removeComponent,
10+
updateProps
11+
} from '../actions';
712

813
type Props = {
914
componentPath: DashLayoutPath;
@@ -21,18 +26,18 @@ function ExternalWrapper({
2126
componentPath,
2227
...props
2328
}: Props) {
24-
const dispatch = useDispatch();
29+
const dispatch: any = useDispatch();
2530
const [inserted, setInserted] = useState(false);
2631

2732
useEffect(() => {
2833
// Give empty props for the inserted components.
2934
// The props will come from the parent so they can be updated.
3035
dispatch(
31-
insertComponent({
36+
addComponentToLayout({
3237
component: {
3338
type: componentType,
3439
namespace: componentNamespace,
35-
props: {}
40+
props: props
3641
},
3742
componentPath
3843
})
@@ -43,10 +48,17 @@ function ExternalWrapper({
4348
};
4449
}, []);
4550

51+
useEffect(() => {
52+
batch(() => {
53+
dispatch(updateProps({itempath: componentPath, props}));
54+
dispatch(notifyObservers({id: props.id, props}));
55+
});
56+
}, [props]);
57+
4658
if (!inserted) {
4759
return null;
4860
}
4961
// Render a wrapper with the actual props.
50-
return <DashWrapper componentPath={componentPath} {...props} />;
62+
return <DashWrapper componentPath={componentPath} />;
5163
}
5264
export default ExternalWrapper;

Diff for: dash/dash.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
from . import _get_app
6969

7070
from ._grouping import map_grouping, grouping_len, update_args_group
71+
from ._obsolete import ObsoleteChecker
7172

7273
from . import _pages
7374
from ._pages import (
@@ -207,7 +208,7 @@ def _do_skip(error):
207208

208209
# pylint: disable=too-many-instance-attributes
209210
# pylint: disable=too-many-arguments, too-many-locals
210-
class Dash:
211+
class Dash(ObsoleteChecker):
211212
"""Dash is a framework for building analytical web applications.
212213
No JavaScript required.
213214
@@ -1388,6 +1389,10 @@ def dispatch(self):
13881389
g.path = flask.request.full_path
13891390
g.remote = flask.request.remote_addr
13901391
g.origin = flask.request.origin
1392+
g.custom_data = AttributeDict({})
1393+
1394+
for hook in self._hooks.get_hooks("custom_data"):
1395+
g.custom_data[hook.data["namespace"]] = hook(g)
13911396

13921397
except KeyError as missing_callback_function:
13931398
msg = f"Callback function not found for output '{output}', perhaps you forgot to prepend the '@'?"

Diff for: dash/exceptions.py

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ class ObsoleteKwargException(DashException):
1010
pass
1111

1212

13+
class ObsoleteAttributeException(DashException):
14+
pass
15+
16+
1317
class NoLayoutException(DashException):
1418
pass
1519

Diff for: dash/version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "3.0.0rc2"
1+
__version__ = "3.0.0rc3"

Diff for: tests/integration/renderer/test_external_component.py

+30-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from dash import Dash, html, dcc, html, Input, Output, State
1+
from dash import Dash, html, dcc, html, Input, Output, State, MATCH
22
from dash_test_components import ExternalComponent
33

44

@@ -8,7 +8,22 @@ def test_rext001_render_external_component(dash_duo):
88
[
99
dcc.Input(id="sync", value="synced"),
1010
html.Button("sync", id="sync-btn"),
11-
ExternalComponent("ext", input_id="external", text="external"),
11+
ExternalComponent(
12+
id="ext",
13+
input_id="external",
14+
text="external",
15+
extra_component={
16+
"type": "Div",
17+
"namespace": "dash_html_components",
18+
"props": {
19+
"id": "extra",
20+
"children": [
21+
html.Div("extra children", id={"type": "extra", "index": 1})
22+
],
23+
},
24+
},
25+
),
26+
html.Div(html.Div(id={"type": "output", "index": 1}), id="out"),
1227
]
1328
)
1429

@@ -21,7 +36,20 @@ def test_rext001_render_external_component(dash_duo):
2136
def on_sync(_, value):
2237
return value
2338

39+
@app.callback(
40+
Output({"type": "output", "index": MATCH}, "children"),
41+
Input({"type": "extra", "index": MATCH}, "n_clicks"),
42+
prevent_initial_call=True,
43+
)
44+
def click(*_):
45+
return "clicked"
46+
2447
dash_duo.start_server(app)
2548
dash_duo.wait_for_text_to_equal("#external", "external")
2649
dash_duo.find_element("#sync-btn").click()
2750
dash_duo.wait_for_text_to_equal("#external", "synced")
51+
52+
dash_duo.wait_for_text_to_equal("#extra", "extra children")
53+
54+
dash_duo.find_element("#extra > div").click()
55+
dash_duo.wait_for_text_to_equal("#out", "clicked")

0 commit comments

Comments
 (0)