From 87e439ab12c20cb06a4ed691857ea41f50dfcd15 Mon Sep 17 00:00:00 2001 From: philippe Date: Fri, 14 Mar 2025 09:18:11 -0400 Subject: [PATCH 1/7] Improve proptypes.js generated size. --- dash/development/_generate_prop_types.py | 30 +++++++++++------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/dash/development/_generate_prop_types.py b/dash/development/_generate_prop_types.py index 7990fdc0c0..b2e5b8ef4b 100644 --- a/dash/development/_generate_prop_types.py +++ b/dash/development/_generate_prop_types.py @@ -25,27 +25,25 @@ prop_type_file_template = """// AUTOGENERATED FILE - DO NOT EDIT -var PropTypes = window.PropTypes; - +var pt = window.PropTypes; +var pk = window['{package_name}']; {components_prop_types} """ -component_prop_types_template = ( - "window['{package_name}'].{component_name}.propTypes = {prop_types}" -) +component_prop_types_template = "pk.{component_name}.propTypes = {prop_types};" def generate_type(type_name): def wrap(*_): - return f"PropTypes.{type_name}" + return f"pt.{type_name}" return wrap def generate_union(prop_info): types = [generate_prop_type(t) for t in prop_info["value"]] - return f"PropTypes.oneOfType([{','.join(types)}])" + return f"pt.oneOfType([{','.join(types)}])" def generate_shape(prop_info): @@ -53,30 +51,30 @@ def generate_shape(prop_info): for key, value in prop_info["value"].items(): props.append(f"{key}:{generate_prop_type(value)}") inner = "{" + ",".join(props) + "}" - return f"PropTypes.shape({inner})" + return f"pt.shape({inner})" def generate_array_of(prop_info): inner_type = generate_prop_type(prop_info["value"]) - return f"PropTypes.arrayOf({inner_type})" + return f"pt.arrayOf({inner_type})" def generate_any(*_): - return "PropTypes.any" + return "pt.any" def generate_enum(prop_info): values = str([v["value"] for v in prop_info["value"]]) - return f"PropTypes.oneOf({values})" + return f"pt.oneOf({values})" def generate_object_of(prop_info): - return f"PropTypes.objectOf({generate_prop_type(prop_info['value'])})" + return f"pt.objectOf({generate_prop_type(prop_info['value'])})" def generate_tuple(*_): # PropTypes don't have a tuple... just generate an array. - return "PropTypes.array" + return "pt.array" prop_types = { @@ -135,13 +133,13 @@ def generate_prop_types( props = [] for prop_name, prop_data in data.get("props", {}).items(): - props.append(f" {prop_name}:{generate_prop_type(prop_data['type'])}") + props.append(f"{prop_name}:{generate_prop_type(prop_data['type'])}") patched.append( component_prop_types_template.format( package_name=package_name, component_name=component_name, - prop_types="{" + ",\n".join(props) + "}", + prop_types="{" + ",\n ".join(props) + "}", ) ) @@ -151,7 +149,7 @@ def generate_prop_types( ) as f: f.write( prop_type_file_template.format( - components_prop_types="\n\n".join(patched) + package_name=package_name, components_prop_types="\n".join(patched) ) ) From 5fffe7ee7b4246dc5266ee5f350dc7d799f73883 Mon Sep 17 00:00:00 2001 From: philippe Date: Fri, 14 Mar 2025 10:15:49 -0400 Subject: [PATCH 2/7] Add ignore_props config to dash_prop_typing --- dash/development/_generate_prop_types.py | 11 ++++++++++- dash/development/_py_components_generation.py | 4 ++++ dash/development/_py_prop_typing.py | 8 ++++++++ dash/development/component_generator.py | 6 +++++- 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/dash/development/_generate_prop_types.py b/dash/development/_generate_prop_types.py index b2e5b8ef4b..8d6127d444 100644 --- a/dash/development/_generate_prop_types.py +++ b/dash/development/_generate_prop_types.py @@ -5,6 +5,8 @@ import os import re +from dash.development._py_prop_typing import get_custom_ignore + init_check_re = re.compile("proptypes.js") @@ -120,9 +122,12 @@ def check_init(namespace): def generate_prop_types( metadata, package_name, + custom_typing_module, ): patched = [] + custom_ignore = get_custom_ignore(custom_typing_module) + for component_path, data in metadata.items(): filename = component_path.split("/")[-1] extension = filename.split("/")[-1].split(".")[-1] @@ -133,7 +138,11 @@ def generate_prop_types( props = [] for prop_name, prop_data in data.get("props", {}).items(): - props.append(f"{prop_name}:{generate_prop_type(prop_data['type'])}") + if prop_name in custom_ignore: + prop_type = "pt.any" + else: + prop_type = generate_prop_type(prop_data["type"]) + props.append(f"{prop_name}:{prop_type}") patched.append( component_prop_types_template.format( diff --git a/dash/development/_py_components_generation.py b/dash/development/_py_components_generation.py index 26cd796bb2..df3b6beb42 100644 --- a/dash/development/_py_components_generation.py +++ b/dash/development/_py_components_generation.py @@ -11,6 +11,7 @@ from ._all_keywords import python_keywords from ._collect_nodes import collect_nodes, filter_base_nodes from ._py_prop_typing import ( + get_custom_ignore, get_custom_props, get_prop_typing, shapes, @@ -150,6 +151,8 @@ def __init__( default_arglist = [] + custom_ignore = get_custom_ignore(custom_typing_module) + for prop_key in prop_keys: prop = props[prop_key] if ( @@ -175,6 +178,7 @@ def __init__( prop_key, type_info, custom_props=custom_props, + custom_ignore=custom_ignore, ) arg_value = f"{prop_key}: typing.Optional[{typed}] = None" diff --git a/dash/development/_py_prop_typing.py b/dash/development/_py_prop_typing.py index 7c93d555a1..46b41ab576 100644 --- a/dash/development/_py_prop_typing.py +++ b/dash/development/_py_prop_typing.py @@ -34,6 +34,10 @@ def get_custom_props(module_name): return _get_custom(module_name, "custom_props", {}) +def get_custom_ignore(module_name): + return _get_custom(module_name, "ignore_props", ["style"]) + + def _clean_key(key): k = "" for ch in key: @@ -142,6 +146,7 @@ def get_prop_typing( prop_name: str, type_info, custom_props=None, + custom_ignore=None, ): if prop_name == "id": # Id is always the same either a string or a dict for pattern matching. @@ -152,6 +157,9 @@ def get_prop_typing( if special: return special(type_info, component_name, prop_name) + if custom_ignore and prop_name in custom_ignore: + return "typing.Any" + prop_type = PROP_TYPING.get(type_name, generate_any)( type_info, component_name, prop_name ) diff --git a/dash/development/component_generator.py b/dash/development/component_generator.py index c7f407aeb8..250fe9c0be 100644 --- a/dash/development/component_generator.py +++ b/dash/development/component_generator.py @@ -139,7 +139,11 @@ def generate_components( components = generate_classes_files(project_shortname, metadata, *generator_methods) - generate_prop_types(metadata, project_shortname) + generate_prop_types( + metadata, + project_shortname, + custom_typing_module=custom_typing_module, + ) with open( os.path.join(project_shortname, "metadata.json"), "w", encoding="utf-8" From 344624813040db12fbe8072df234b258b0666e90 Mon Sep 17 00:00:00 2001 From: philippe Date: Fri, 14 Mar 2025 10:19:14 -0400 Subject: [PATCH 3/7] Remove moved typing code --- dash/development/_py_prop_typing.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/dash/development/_py_prop_typing.py b/dash/development/_py_prop_typing.py index 46b41ab576..7665add419 100644 --- a/dash/development/_py_prop_typing.py +++ b/dash/development/_py_prop_typing.py @@ -166,26 +166,6 @@ def get_prop_typing( return prop_type -def generate_plotly_figure(*_): - custom_imports["dash_core_components"]["Graph"].append( - "from plotly.graph_objects import Figure" - ) - return "typing.Union[Figure, dict]" - - -def generate_datetime_prop(component, array=False): - if "import datetime" not in custom_imports["dash_core_components"][component]: - custom_imports["dash_core_components"][component].append("import datetime") - - def generator(*_): - datetime_type = "typing.Union[str, datetime.datetime]" - if array: - datetime_type = f"typing.Sequence[{datetime_type}]" - return datetime_type - - return generator - - PROP_TYPING = { "array": generate_type("typing.Sequence"), "arrayOf": generate_array_of, From 69d134fbbfdcba9bb4d709138f21e2c15978417e Mon Sep 17 00:00:00 2001 From: philippe Date: Fri, 14 Mar 2025 11:02:14 -0400 Subject: [PATCH 4/7] Add wildcard * support for custom prop typing --- dash/development/_py_components_generation.py | 4 +++- dash/development/_py_prop_typing.py | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/dash/development/_py_components_generation.py b/dash/development/_py_components_generation.py index df3b6beb42..6db6ba09b3 100644 --- a/dash/development/_py_components_generation.py +++ b/dash/development/_py_components_generation.py @@ -250,7 +250,9 @@ def generate_class_file( custom_typing_module, ) - custom_imp = get_custom_imports(custom_typing_module).get(typename) + custom_imp = get_custom_imports(custom_typing_module) + custom_imp = custom_imp.get(typename) or custom_imp.get("*") + if custom_imp: imports += "\n".join(custom_imp) imports += "\n\n" diff --git a/dash/development/_py_prop_typing.py b/dash/development/_py_prop_typing.py index 7665add419..331206415a 100644 --- a/dash/development/_py_prop_typing.py +++ b/dash/development/_py_prop_typing.py @@ -140,6 +140,11 @@ def generate_enum(type_info, *_): return f"Literal[{', '.join(values)}]" +def _get_custom_prop(custom_props, component_name, prop_name): + customs = custom_props.get(component_name) or custom_props.get("*", {}) + return customs.get(prop_name) + + def get_prop_typing( type_name: str, component_name: str, @@ -153,7 +158,7 @@ def get_prop_typing( return "typing.Union[str, dict]" if custom_props: - special = custom_props.get(component_name, {}).get(prop_name) + special = _get_custom_prop(custom_props, component_name, prop_name) if special: return special(type_info, component_name, prop_name) From 73fd47067cbf5930c2908cdae57cae3a956ee20e Mon Sep 17 00:00:00 2001 From: philippe Date: Fri, 14 Mar 2025 13:21:45 -0400 Subject: [PATCH 5/7] Also ignore props in docstring. --- dash/development/_py_components_generation.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/dash/development/_py_components_generation.py b/dash/development/_py_components_generation.py index 6db6ba09b3..c18db36780 100644 --- a/dash/development/_py_components_generation.py +++ b/dash/development/_py_components_generation.py @@ -107,11 +107,13 @@ def __init__( ) wildcard_prefixes = repr(parse_wildcards(props)) list_of_valid_keys = repr(list(map(str, filtered_props.keys()))) + custom_ignore = get_custom_ignore(custom_typing_module) docstring = create_docstring( component_name=typename, props=filtered_props, description=description, prop_reorder_exceptions=prop_reorder_exceptions, + ignored_props=custom_ignore, ).replace("\r\n", "\n") required_args = required_props(filtered_props) is_children_required = "children" in required_args @@ -151,8 +153,6 @@ def __init__( default_arglist = [] - custom_ignore = get_custom_ignore(custom_typing_module) - for prop_key in prop_keys: prop = props[prop_key] if ( @@ -340,7 +340,13 @@ def required_props(props): return [prop_name for prop_name, prop in list(props.items()) if prop["required"]] -def create_docstring(component_name, props, description, prop_reorder_exceptions=None): +def create_docstring( + component_name, + props, + description, + prop_reorder_exceptions=None, + ignored_props=tuple(), +): """Create the Dash component docstring. Parameters ---------- @@ -377,7 +383,7 @@ def create_docstring(component_name, props, description, prop_reorder_exceptions indent_num=0, is_flow_type="flowType" in prop and "type" not in prop, ) - for p, prop in filter_props(props).items() + for p, prop in filter_props(props, ignored_props).items() ) return ( @@ -442,7 +448,7 @@ def reorder_props(props): return OrderedDict(props1 + props2 + sorted(list(props.items()))) -def filter_props(props): +def filter_props(props, ignored_props=tuple()): """Filter props from the Component arguments to exclude: - Those without a "type" or a "flowType" field - Those with arg.type.name in {'func', 'symbol', 'instanceOf'} @@ -487,7 +493,7 @@ def filter_props(props): filtered_props = copy.deepcopy(props) for arg_name, arg in list(filtered_props.items()): - if "type" not in arg and "flowType" not in arg: + if arg_name in ignored_props or ("type" not in arg and "flowType" not in arg): filtered_props.pop(arg_name) continue From e9f49568692e64557129de89fd8888caa2edb1d1 Mon Sep 17 00:00:00 2001 From: philippe Date: Fri, 14 Mar 2025 14:09:57 -0400 Subject: [PATCH 6/7] Add literal prop typing support --- .../dash_prop_typing.py | 14 ++++++++++++++ .../generator.test.ts | 17 +++++++++++++++++ .../src/props.ts | 2 ++ dash/development/_generate_prop_types.py | 6 ++++++ dash/development/_py_prop_typing.py | 5 +++++ dash/extract-meta.js | 8 ++++++-- 6 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 @plotly/dash-generator-test-component-typescript/dash_prop_typing.py diff --git a/@plotly/dash-generator-test-component-typescript/dash_prop_typing.py b/@plotly/dash-generator-test-component-typescript/dash_prop_typing.py new file mode 100644 index 0000000000..fb5e92a481 --- /dev/null +++ b/@plotly/dash-generator-test-component-typescript/dash_prop_typing.py @@ -0,0 +1,14 @@ +ignore_props = ['ignored_prop'] + +custom_imports = { + "*": ["import json"] +} + +def generate_style(*_): + return "dict" + +custom_props = { + "*": { + "obj": generate_style + } +} diff --git a/@plotly/dash-generator-test-component-typescript/generator.test.ts b/@plotly/dash-generator-test-component-typescript/generator.test.ts index b8c390a574..266f343176 100644 --- a/@plotly/dash-generator-test-component-typescript/generator.test.ts +++ b/@plotly/dash-generator-test-component-typescript/generator.test.ts @@ -271,6 +271,23 @@ describe('Test Typescript component metadata generation', () => { ); expect(objectOfComponents).toBe("node"); } + ); + + test( + 'union and literal values', () => { + const propType = R.path( + propPath('TypeScriptComponent', 'union_enum').concat( + 'type' + ), + metadata + ); + expect(propType.name).toBe('union'); + expect(propType.value.length).toBe(3); + expect(propType.value[0].name).toBe('number'); + expect(propType.value[1].name).toBe('literal'); + expect(propType.value[2].name).toBe('literal'); + expect(propType.value[1].value).toBe('small'); + } ) }); diff --git a/@plotly/dash-generator-test-component-typescript/src/props.ts b/@plotly/dash-generator-test-component-typescript/src/props.ts index bbdc6b4773..56e16fa6b9 100644 --- a/@plotly/dash-generator-test-component-typescript/src/props.ts +++ b/@plotly/dash-generator-test-component-typescript/src/props.ts @@ -47,6 +47,8 @@ export type TypescriptComponentProps = { object_of_string?: {[k: string]: string}; object_of_components?: {[k: string]: JSX.Element}; + ignored_prop?: {ignore: {me: string}}; + union_enum?: number | 'small' | 'large' }; export type WrappedHTMLProps = { diff --git a/dash/development/_generate_prop_types.py b/dash/development/_generate_prop_types.py index 8d6127d444..92477ea0c5 100644 --- a/dash/development/_generate_prop_types.py +++ b/dash/development/_generate_prop_types.py @@ -2,6 +2,7 @@ # Generate it instead with the provided metadata.json # for them to be able to report invalid prop +import json import os import re @@ -79,6 +80,10 @@ def generate_tuple(*_): return "pt.array" +def generate_literal(prop_info): + return f"pt.oneOf([{json.dumps(prop_info['value'])}])" + + prop_types = { "array": generate_type("array"), "arrayOf": generate_array_of, @@ -97,6 +102,7 @@ def generate_tuple(*_): "enum": generate_enum, "objectOf": generate_object_of, "tuple": generate_tuple, + "literal": generate_literal, } diff --git a/dash/development/_py_prop_typing.py b/dash/development/_py_prop_typing.py index 331206415a..fcd4c58961 100644 --- a/dash/development/_py_prop_typing.py +++ b/dash/development/_py_prop_typing.py @@ -140,6 +140,10 @@ def generate_enum(type_info, *_): return f"Literal[{', '.join(values)}]" +def generate_literal(type_info, *_): + return f"Literal[{json.dumps(type_info['value'])}]" + + def _get_custom_prop(custom_props, component_name, prop_name): customs = custom_props.get(component_name) or custom_props.get("*", {}) return customs.get(prop_name) @@ -193,4 +197,5 @@ def get_prop_typing( "enum": generate_enum, "objectOf": generate_object_of, "tuple": generate_tuple, + "literal": generate_literal, } diff --git a/dash/extract-meta.js b/dash/extract-meta.js index 7a2acaef51..3427ef81d3 100755 --- a/dash/extract-meta.js +++ b/dash/extract-meta.js @@ -67,7 +67,7 @@ const BANNED_TYPES = [ 'ChildNode', 'ParentNode', ]; -const unionSupport = PRIMITIVES.concat('boolean', 'Element'); +const unionSupport = PRIMITIVES.concat('boolean', 'Element', 'enum'); const reArray = new RegExp(`(${unionSupport.join('|')})\\[\\]`); @@ -261,12 +261,16 @@ function gatherComponents(sources, components = {}) { typeName = 'object'; } } + if (t.value) { + // A literal value + return true; + } return ( unionSupport.includes(typeName) || isArray(checker.typeToString(t)) ); }) - .map(t => getPropType(t, propObj, parentType)); + .map(t => t.value ? {name: 'literal', value: t.value} : getPropType(t, propObj, parentType)); if (!value.length) { name = 'any'; From ec75aca5650876dc7280654d00b402d7f99925ac Mon Sep 17 00:00:00 2001 From: philippe Date: Fri, 14 Mar 2025 15:13:05 -0400 Subject: [PATCH 7/7] fix test_typing --- .../dash_prop_typing.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/@plotly/dash-generator-test-component-typescript/dash_prop_typing.py b/@plotly/dash-generator-test-component-typescript/dash_prop_typing.py index fb5e92a481..db903c1482 100644 --- a/@plotly/dash-generator-test-component-typescript/dash_prop_typing.py +++ b/@plotly/dash-generator-test-component-typescript/dash_prop_typing.py @@ -1,14 +1 @@ ignore_props = ['ignored_prop'] - -custom_imports = { - "*": ["import json"] -} - -def generate_style(*_): - return "dict" - -custom_props = { - "*": { - "obj": generate_style - } -}