Skip to content

Commit 7c0a15f

Browse files
committed
Add typing to component init.
1 parent a9eb343 commit 7c0a15f

File tree

4 files changed

+147
-18
lines changed

4 files changed

+147
-18
lines changed

dash/development/_py_components_generation.py

+44-16
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
from collections import OrderedDict
22
import copy
33
import os
4+
import typing
45
from textwrap import fill, dedent
56

67
from dash.development.base_component import _explicitize_args
78
from dash.exceptions import NonExistentEventException
89
from ._all_keywords import python_keywords
910
from ._collect_nodes import collect_nodes, filter_base_nodes
11+
from ._py_prop_typing import get_prop_typing
1012
from .base_component import Component
1113

1214

13-
# pylint: disable=unused-argument,too-many-locals
15+
# pylint: disable=unused-argument,too-many-locals,too-many-branches
1416
def generate_class_string(
1517
typename,
1618
props,
@@ -55,7 +57,10 @@ def generate_class_string(
5557
_namespace = '{namespace}'
5658
_type = '{typename}'
5759
@_explicitize_args
58-
def __init__(self, {default_argtext}):
60+
def __init__(
61+
self,
62+
{default_argtext}
63+
):
5964
self._prop_names = {list_of_valid_keys}
6065
self._valid_wildcard_attributes =\
6166
{list_of_valid_wildcard_attr_prefixes}
@@ -94,7 +99,7 @@ def __init__(self, {default_argtext}):
9499
prop_keys = list(props.keys())
95100
if "children" in props and "children" in list_of_valid_keys:
96101
prop_keys.remove("children")
97-
default_argtext = "children=None, "
102+
default_argtext = "children=None,"
98103
args = "{k: _locals[k] for k in _explicit_args if k != 'children'}"
99104
argtext = "children=children, **args"
100105
else:
@@ -118,15 +123,33 @@ def __init__(self, {default_argtext}):
118123
raise TypeError('Required argument children was not specified.')
119124
"""
120125

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-
]
126+
default_arglist = []
127+
128+
for prop_key in prop_keys:
129+
prop = props[prop_key]
130+
if (
131+
prop_key.endswith("-*")
132+
or prop_key in python_keywords
133+
or prop_key == "setProps"
134+
):
135+
continue
136+
required = prop.get("required")
137+
type_info = prop.get("type")
138+
139+
if not type_info:
140+
print(f"Invalid prop type: {prop_key}")
141+
continue
142+
143+
type_name = type_info.get("name")
144+
145+
typed = get_prop_typing(type_name, type_info)
146+
147+
if required:
148+
arg_value = f"{prop_key}: {typed} = Component.REQUIRED"
149+
else:
150+
arg_value = f"{prop_key}: {typed} = Component.UNDEFINED"
151+
152+
default_arglist.append(arg_value)
130153

131154
if max_props:
132155
final_max_props = max_props - (1 if "children" in props else 0)
@@ -139,7 +162,7 @@ def __init__(self, {default_argtext}):
139162
"they may still be used as keyword arguments."
140163
)
141164

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

145168
return dedent(
@@ -181,8 +204,9 @@ def generate_class_file(
181204
"""
182205
import_string = (
183206
"# AUTO GENERATED FILE - DO NOT EDIT\n\n"
184-
+ "from dash.development.base_component import "
185-
+ "Component, _explicitize_args\n\n\n"
207+
"import typing # noqa: F401\n"
208+
"from dash.development.base_component import "
209+
"Component, _explicitize_args\n\n\n"
186210
)
187211

188212
class_string = generate_class_string(
@@ -242,7 +266,11 @@ def generate_class(
242266
string = generate_class_string(
243267
typename, props, description, namespace, prop_reorder_exceptions
244268
)
245-
scope = {"Component": Component, "_explicitize_args": _explicitize_args}
269+
scope = {
270+
"Component": Component,
271+
"_explicitize_args": _explicitize_args,
272+
"typing": typing,
273+
}
246274
# pylint: disable=exec-used
247275
exec(string, scope)
248276
result = scope[typename]

dash/development/_py_prop_typing.py

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
def generate_any(_):
2+
return "typing.Any"
3+
4+
5+
def generate_shape(t):
6+
props = []
7+
8+
for prop in t["value"].values():
9+
prop_type = PROP_TYPING.get(prop["name"], generate_any)(prop)
10+
if prop_type not in props:
11+
props.append(prop_type)
12+
13+
if len(props) == 0:
14+
return "typing.Any"
15+
16+
return f"typing.Dict[str, typing.Union[{', '.join(props)}]]"
17+
18+
19+
def generate_union(t):
20+
types = []
21+
for union in t["value"]:
22+
u_type = PROP_TYPING.get(union["name"], generate_any)(union)
23+
if u_type not in types:
24+
types.append(u_type)
25+
return f"typing.Union[{', '.join(types)}]"
26+
27+
28+
def generate_tuple(type_info):
29+
els = type_info.get("elements")
30+
elements = ", ".join(get_prop_typing(x.get("name"), x) for x in els)
31+
return f"typing.Tuple[{elements}]"
32+
33+
34+
def generate_array_of(t):
35+
typed = PROP_TYPING.get(t["value"]["name"], generate_any)(t["value"])
36+
return f"typing.List[{typed}]"
37+
38+
39+
def generate_object_of(t):
40+
typed = PROP_TYPING.get(t["value"]["name"], generate_any)(t["value"])
41+
return f"typing.Dict[str, {typed}]"
42+
43+
44+
def generate_type(typename):
45+
def type_handler(_):
46+
return typename
47+
48+
return type_handler
49+
50+
51+
def get_prop_typing(type_name: str, type_info):
52+
return PROP_TYPING.get(type_name, generate_any)(type_info)
53+
54+
55+
PROP_TYPING = {
56+
"array": generate_type("typing.List"),
57+
"arrayOf": generate_array_of,
58+
"object": generate_type("typing.Dict"),
59+
"shape": generate_shape,
60+
"exact": generate_shape,
61+
"string": generate_type("str"),
62+
"bool": generate_type("bool"),
63+
"number": generate_type("typing.Union[float, int]"),
64+
"node": generate_type(
65+
"typing.Union[str, int, float, Component,"
66+
" typing.List[typing.Union"
67+
"[str, int, float, Component]]]"
68+
),
69+
"func": generate_any,
70+
"element": generate_type("Component"),
71+
"union": generate_union,
72+
"any": generate_any,
73+
"custom": generate_any,
74+
"enum": generate_any,
75+
"objectOf": generate_object_of,
76+
"tuple": generate_tuple,
77+
}

tests/unit/development/metadata_test.py

+25-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# AUTO GENERATED FILE - DO NOT EDIT
22

3+
import typing # noqa: F401
34
from dash.development.base_component import Component, _explicitize_args
45

56

@@ -91,7 +92,30 @@ class Table(Component):
9192
_namespace = 'TableComponents'
9293
_type = 'Table'
9394
@_explicitize_args
94-
def __init__(self, children=None, optionalArray=Component.UNDEFINED, optionalBool=Component.UNDEFINED, optionalFunc=Component.UNDEFINED, optionalNumber=Component.UNDEFINED, optionalObject=Component.UNDEFINED, optionalString=Component.UNDEFINED, optionalSymbol=Component.UNDEFINED, optionalNode=Component.UNDEFINED, optionalElement=Component.UNDEFINED, optionalMessage=Component.UNDEFINED, optionalEnum=Component.UNDEFINED, optionalUnion=Component.UNDEFINED, optionalArrayOf=Component.UNDEFINED, optionalObjectOf=Component.UNDEFINED, optionalObjectWithExactAndNestedDescription=Component.UNDEFINED, optionalObjectWithShapeAndNestedDescription=Component.UNDEFINED, optionalAny=Component.UNDEFINED, customProp=Component.UNDEFINED, customArrayProp=Component.UNDEFINED, id=Component.UNDEFINED, **kwargs):
95+
def __init__(
96+
self,
97+
children=None,optionalArray: typing.List = Component.UNDEFINED,
98+
optionalBool: bool = Component.UNDEFINED,
99+
optionalFunc: typing.Any = Component.UNDEFINED,
100+
optionalNumber: typing.Union[float, int] = Component.UNDEFINED,
101+
optionalObject: typing.Dict = Component.UNDEFINED,
102+
optionalString: str = Component.UNDEFINED,
103+
optionalSymbol: typing.Any = Component.UNDEFINED,
104+
optionalNode: typing.Union[str, int, float, Component, typing.List[typing.Union[str, int, float, Component]]] = Component.UNDEFINED,
105+
optionalElement: Component = Component.UNDEFINED,
106+
optionalMessage: typing.Any = Component.UNDEFINED,
107+
optionalEnum: typing.Any = Component.UNDEFINED,
108+
optionalUnion: typing.Union[str, typing.Union[float, int], typing.Any] = Component.UNDEFINED,
109+
optionalArrayOf: typing.List[typing.Union[float, int]] = Component.UNDEFINED,
110+
optionalObjectOf: typing.Dict[str, typing.Union[float, int]] = Component.UNDEFINED,
111+
optionalObjectWithExactAndNestedDescription: typing.Any = Component.UNDEFINED,
112+
optionalObjectWithShapeAndNestedDescription: typing.Dict[str, typing.Union[str, typing.Union[float, int], typing.Dict[str, typing.Union[typing.List[typing.Dict], typing.Dict]]]] = Component.UNDEFINED,
113+
optionalAny: typing.Any = Component.UNDEFINED,
114+
customProp: typing.Any = Component.UNDEFINED,
115+
customArrayProp: typing.List[typing.Any] = Component.UNDEFINED,
116+
id: str = Component.UNDEFINED,
117+
**kwargs
118+
):
95119
self._prop_names = ['children', 'id', 'aria-*', 'customArrayProp', 'customProp', 'data-*', 'in', 'optionalAny', 'optionalArray', 'optionalArrayOf', 'optionalBool', 'optionalElement', 'optionalEnum', 'optionalNode', 'optionalNumber', 'optionalObject', 'optionalObjectOf', 'optionalObjectWithExactAndNestedDescription', 'optionalObjectWithShapeAndNestedDescription', 'optionalString', 'optionalUnion']
96120
self._valid_wildcard_attributes = ['data-', 'aria-']
97121
self.available_properties = ['children', 'id', 'aria-*', 'customArrayProp', 'customProp', 'data-*', 'in', 'optionalAny', 'optionalArray', 'optionalArrayOf', 'optionalBool', 'optionalElement', 'optionalEnum', 'optionalNode', 'optionalNumber', 'optionalObject', 'optionalObjectOf', 'optionalObjectWithExactAndNestedDescription', 'optionalObjectWithShapeAndNestedDescription', 'optionalString', 'optionalUnion']

tests/unit/development/test_generate_class_file.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
# Import string not included in generated class string
1515
import_string = (
16-
"# AUTO GENERATED FILE - DO NOT EDIT\n\n"
16+
"# AUTO GENERATED FILE - DO NOT EDIT\n\nimport typing # noqa: F401\n"
1717
+ "from dash.development.base_component import"
1818
+ " Component, _explicitize_args\n\n\n"
1919
)

0 commit comments

Comments
 (0)