Skip to content

Commit 9705ae5

Browse files
authored
Merge pull request #2276 from plotly/prop-typing
Add typing to component init
2 parents 9566578 + d13b343 commit 9705ae5

File tree

15 files changed

+637
-54
lines changed

15 files changed

+637
-54
lines changed

@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: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,37 @@ def _jupyter_nbextension_paths():
5353
"require": "dash/main",
5454
}
5555
]
56+
57+
58+
__all__ = [
59+
"Input",
60+
"Output",
61+
"State",
62+
"ClientsideFunction",
63+
"MATCH",
64+
"ALLSMALLER",
65+
"ALL",
66+
"development",
67+
"exceptions",
68+
"dcc",
69+
"html",
70+
"dash_table",
71+
"__version__",
72+
"callback_context",
73+
"set_props",
74+
"callback",
75+
"get_app",
76+
"get_asset_url",
77+
"get_relative_path",
78+
"strip_relative_path",
79+
"CeleryManager",
80+
"DiskcacheManager",
81+
"register_page",
82+
"page_registry",
83+
"Dash",
84+
"no_update",
85+
"page_container",
86+
"Patch",
87+
"jupyter_dash",
88+
"ctx",
89+
]

dash/_callback.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import collections
22
import hashlib
33
from functools import wraps
4-
from typing import Callable, Optional, Any
4+
from typing import Callable, Optional, Any, List, Tuple
55

66
import flask
77

88
from .dependencies import (
99
handle_callback_args,
1010
handle_grouped_callback_args,
1111
Output,
12+
Input,
1213
)
1314
from .development.base_component import ComponentRegistry
1415
from .exceptions import (
@@ -62,14 +63,14 @@ def is_no_update(obj):
6263
# pylint: disable=too-many-locals
6364
def callback(
6465
*_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,
66+
background: bool = False,
67+
interval: int = 1000,
68+
progress: Optional[Output] = None,
69+
progress_default: Any = None,
70+
running: Optional[List[Tuple[Output, Any, Any]]] = None,
71+
cancel: Optional[List[Input]] = None,
72+
manager: Optional[BaseLongCallbackManager] = None,
73+
cache_args_to_ignore: Optional[list] = None,
7374
on_error: Optional[Callable[[Exception], Any]] = None,
7475
**_kwargs,
7576
):
@@ -156,7 +157,7 @@ def callback(
156157
callback_list = _kwargs.pop("callback_list", GLOBAL_CALLBACK_LIST)
157158

158159
if background:
159-
long_spec = {
160+
long_spec: Any = {
160161
"interval": interval,
161162
}
162163

dash/development/_py_components_generation.py

Lines changed: 73 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,34 @@
11
from collections import OrderedDict
22
import copy
3+
import numbers
34
import os
5+
import typing
46
from textwrap import fill, dedent
57

8+
from typing_extensions import TypedDict, NotRequired, Literal
69
from dash.development.base_component import _explicitize_args
710
from dash.exceptions import NonExistentEventException
811
from ._all_keywords import python_keywords
912
from ._collect_nodes import collect_nodes, filter_base_nodes
10-
from .base_component import Component
13+
from ._py_prop_typing import get_prop_typing, shapes, custom_imports
14+
from .base_component import Component, ComponentType
1115

16+
import_string = """# AUTO GENERATED FILE - DO NOT EDIT
1217
13-
# pylint: disable=unused-argument,too-many-locals
18+
import typing # noqa: F401
19+
import numbers # noqa: F401
20+
from typing_extensions import TypedDict, NotRequired, Literal # noqa: F401
21+
from dash.development.base_component import Component, _explicitize_args
22+
try:
23+
from dash.development.base_component import ComponentType # noqa: F401
24+
except ImportError:
25+
ComponentType = typing.TypeVar("ComponentType", bound=Component)
26+
27+
28+
"""
29+
30+
31+
# pylint: disable=unused-argument,too-many-locals,too-many-branches
1432
def generate_class_string(
1533
typename,
1634
props,
@@ -54,8 +72,12 @@ def generate_class_string(
5472
_base_nodes = {base_nodes}
5573
_namespace = '{namespace}'
5674
_type = '{typename}'
75+
{shapes}
5776
@_explicitize_args
58-
def __init__(self, {default_argtext}):
77+
def __init__(
78+
self,
79+
{default_argtext}
80+
):
5981
self._prop_names = {list_of_valid_keys}
6082
self._valid_wildcard_attributes =\
6183
{list_of_valid_wildcard_attr_prefixes}
@@ -94,7 +116,9 @@ def __init__(self, {default_argtext}):
94116
prop_keys = list(props.keys())
95117
if "children" in props and "children" in list_of_valid_keys:
96118
prop_keys.remove("children")
97-
default_argtext = "children=None, "
119+
# TODO For dash 3.0, remove the Optional and = None for proper typing.
120+
# Also add the other required props after children.
121+
default_argtext = f"children: typing.Optional[{get_prop_typing('node', '', '', {})}] = None,\n "
98122
args = "{k: _locals[k] for k in _explicit_args if k != 'children'}"
99123
argtext = "children=children, **args"
100124
else:
@@ -118,15 +142,31 @@ def __init__(self, {default_argtext}):
118142
raise TypeError('Required argument children was not specified.')
119143
"""
120144

121-
default_arglist = [
122-
(
123-
f"{p:s}=Component.REQUIRED"
124-
if props[p]["required"]
125-
else f"{p:s}=Component.UNDEFINED"
126-
)
127-
for p in prop_keys
128-
if not p.endswith("-*") and p not in python_keywords and p != "setProps"
129-
]
145+
default_arglist = []
146+
147+
for prop_key in prop_keys:
148+
prop = props[prop_key]
149+
if (
150+
prop_key.endswith("-*")
151+
or prop_key in python_keywords
152+
or prop_key == "setProps"
153+
):
154+
continue
155+
156+
type_info = prop.get("type")
157+
158+
if not type_info:
159+
print(f"Invalid prop type for typing: {prop_key}")
160+
default_arglist.append(f"{prop_key} = None")
161+
continue
162+
163+
type_name = type_info.get("name")
164+
165+
typed = get_prop_typing(type_name, typename, prop_key, type_info, namespace)
166+
167+
arg_value = f"{prop_key}: typing.Optional[{typed}] = None"
168+
169+
default_arglist.append(arg_value)
130170

131171
if max_props:
132172
final_max_props = max_props - (1 if "children" in props else 0)
@@ -139,7 +179,7 @@ def __init__(self, {default_argtext}):
139179
"they may still be used as keyword arguments."
140180
)
141181

142-
default_argtext += ", ".join(default_arglist + ["**kwargs"])
182+
default_argtext += ",\n ".join(default_arglist + ["**kwargs"])
143183
nodes = collect_nodes({k: v for k, v in props.items() if k != "children"})
144184

145185
return dedent(
@@ -156,6 +196,7 @@ def __init__(self, {default_argtext}):
156196
required_validation=required_validation,
157197
children_props=nodes,
158198
base_nodes=filter_base_nodes(nodes) + ["children"],
199+
shapes="\n".join(shapes.get(typename, {}).values()),
159200
)
160201
)
161202

@@ -179,20 +220,22 @@ def generate_class_file(
179220
Returns
180221
-------
181222
"""
182-
import_string = (
183-
"# AUTO GENERATED FILE - DO NOT EDIT\n\n"
184-
+ "from dash.development.base_component import "
185-
+ "Component, _explicitize_args\n\n\n"
186-
)
223+
imports = import_string
187224

188225
class_string = generate_class_string(
189226
typename, props, description, namespace, prop_reorder_exceptions, max_props
190227
)
228+
229+
custom_imp = custom_imports[namespace][typename]
230+
if custom_imp:
231+
imports += "\n".join(custom_imp)
232+
imports += "\n\n"
233+
191234
file_name = f"{typename:s}.py"
192235

193236
file_path = os.path.join(namespace, file_name)
194237
with open(file_path, "w", encoding="utf-8") as f:
195-
f.write(import_string)
238+
f.write(imports)
196239
f.write(class_string)
197240

198241
print(f"Generated {file_name}")
@@ -242,7 +285,16 @@ def generate_class(
242285
string = generate_class_string(
243286
typename, props, description, namespace, prop_reorder_exceptions
244287
)
245-
scope = {"Component": Component, "_explicitize_args": _explicitize_args}
288+
scope = {
289+
"Component": Component,
290+
"ComponentType": ComponentType,
291+
"_explicitize_args": _explicitize_args,
292+
"typing": typing,
293+
"numbers": numbers,
294+
"TypedDict": TypedDict,
295+
"NotRequired": NotRequired,
296+
"Literal": Literal,
297+
}
246298
# pylint: disable=exec-used
247299
exec(string, scope)
248300
result = scope[typename]

0 commit comments

Comments
 (0)