Skip to content

Add typing to component init #2276

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 61 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
5323742
Add typing to component init.
T4rk1n Oct 19, 2022
8626b3d
Add test_typing
T4rk1n Oct 25, 2022
c9bb168
Merge branch 'dev' into prop-typing
T4rk1n Oct 26, 2022
91cc0d6
Add py.typed
T4rk1n Oct 26, 2022
6bbafdc
Lock pyright for py>3.7
T4rk1n Oct 26, 2022
c620f58
Merge branch 'dev' into prop-typing
T4rk1n Oct 27, 2022
147798b
Skip pyright test on py 3.6
T4rk1n Oct 27, 2022
a5250e5
Merge branch 'dev' into prop-typing
T4rk1n Oct 28, 2022
6d50f15
Add numbers as dict key.
T4rk1n Oct 31, 2022
dee40cf
Change number to numbers.Number
T4rk1n Oct 31, 2022
24a8e90
Fix test_generate_class_file
T4rk1n Oct 31, 2022
1f4aed9
Generate enums
T4rk1n Nov 25, 2022
88ca3f3
Generate typed dicts for shapes.
T4rk1n Nov 25, 2022
c18f0fd
Add stringcase & typing_extensions to requires.
T4rk1n Nov 29, 2022
544dcab
Fix whitespace around extra types & runtime classes.
T4rk1n Nov 29, 2022
91a2a1d
Fix metadata test
T4rk1n Nov 29, 2022
ed360e1
Lock version of typing_extensions for 3.6
T4rk1n Nov 29, 2022
b3061a4
Fix keyword in enum & shapes, NoneType in enum + add enum suffix.
T4rk1n Nov 29, 2022
a4ea22e
Fix metadata test
T4rk1n Nov 29, 2022
8029f15
Fix test_attrs_match_forbidden_props
T4rk1n Nov 29, 2022
b0ed806
Add back base numbers.
T4rk1n Nov 30, 2022
ae345e0
Fix tests
T4rk1n Nov 30, 2022
d7136e3
Add pyright test cases for shapes
T4rk1n Nov 30, 2022
a3dc517
Merge branch 'dev' into prop-typing
T4rk1n Dec 1, 2022
a4f2c4a
Merge branch 'dev' into prop-typing
T4rk1n Dec 6, 2022
12c5319
Merge branch 'dev' into prop-typing
T4rk1n Apr 20, 2023
8e0dfea
Fix enum duplicate union type.
T4rk1n Apr 27, 2023
453834b
Add tuple to untyped array typing union.
T4rk1n Apr 27, 2023
13ff296
Add basic tuple as union of arrayOf props typing.
T4rk1n Apr 28, 2023
279950c
Fix typing tests.
T4rk1n May 1, 2023
e6843fd
Replace enum generation with Literal
T4rk1n May 1, 2023
6dc0fc1
Allow all keys in TypedDicts and postpone annotations declared on the…
T4rk1n May 1, 2023
709d1e3
Merge branch 'dev' into prop-typing
T4rk1n May 1, 2023
e6c2356
Relax version of typing_extensions>=4.1.1
T4rk1n May 2, 2023
7bc56b9
Fix test_rdrh003_refresh_jwt
T4rk1n May 2, 2023
9eee253
Move stringcase requirement to requires-install
T4rk1n May 2, 2023
2adb37f
Merge branch 'dev' into prop-typing
T4rk1n Aug 6, 2024
f228185
Fix lint on 12
T4rk1n Aug 8, 2024
7b0da8a
Remove py2 code from explicitize_args
T4rk1n Aug 8, 2024
fb8decb
Update browser-tools
T4rk1n Aug 8, 2024
197180c
Support plotly.Figure
T4rk1n Aug 8, 2024
8734e1b
Use ComponentType
T4rk1n Aug 9, 2024
d5b0ebe
Add Optional to every prop
T4rk1n Aug 12, 2024
4e583e3
Fix list[str] > List[str]
T4rk1n Aug 12, 2024
150c2a5
Fix test_typing
T4rk1n Aug 16, 2024
96b931a
Update pyright
T4rk1n Aug 16, 2024
4045a61
Update pyright
T4rk1n Aug 16, 2024
46d05be
Fix custom imports.
T4rk1n Aug 19, 2024
85c26b7
Include py.typed in manifest
T4rk1n Aug 20, 2024
40bd21a
Add typing to callback function
T4rk1n Aug 20, 2024
2de448e
Merge branch 'dev' into prop-typing
T4rk1n Aug 20, 2024
648a1c6
build
T4rk1n Aug 20, 2024
8d185c8
Update tests/integration/test_typing.py
T4rk1n Aug 26, 2024
ef6b054
Remove tuple from list typing
T4rk1n Aug 26, 2024
6b30d08
Deprecate runtime component loader
T4rk1n Aug 26, 2024
dd5cb44
Merge branch 'prop-typing' of github.com:plotly/dash into prop-typing
T4rk1n Aug 26, 2024
26a83e5
Backward compatible ComponentType.
T4rk1n Aug 26, 2024
18fad2d
Remove component loader deprecation from pr.
T4rk1n Sep 9, 2024
95d8b0d
Merge branch 'dev' into prop-typing
T4rk1n Sep 9, 2024
00da064
Merge branch 'dash-3.0' into prop-typing
T4rk1n Nov 8, 2024
d13b343
Fix type assertion
T4rk1n Nov 8, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ include dash/dash-renderer/build/*.js
include dash/dash-renderer/build/*.map
include dash/labextension/dist/dash-jupyterlab.tgz
include dash/labextension/package.json
include dash/py.typed
34 changes: 34 additions & 0 deletions dash/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,37 @@ def _jupyter_nbextension_paths():
"require": "dash/main",
}
]


__all__ = [
"Input",
"Output",
"State",
"ClientsideFunction",
"MATCH",
"ALLSMALLER",
"ALL",
"development",
"exceptions",
"dcc",
"html",
"dash_table",
"__version__",
"callback_context",
"set_props",
"callback",
"get_app",
"get_asset_url",
"get_relative_path",
"strip_relative_path",
"CeleryManager",
"DiskcacheManager",
"register_page",
"page_registry",
"Dash",
"no_update",
"page_container",
"Patch",
"jupyter_dash",
"ctx",
]
21 changes: 11 additions & 10 deletions dash/_callback.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import collections
import hashlib
from functools import wraps
from typing import Callable, Optional, Any
from typing import Callable, Optional, Any, List, Tuple

import flask

from .dependencies import (
handle_callback_args,
handle_grouped_callback_args,
Output,
Input,
)
from .development.base_component import ComponentRegistry
from .exceptions import (
Expand Down Expand Up @@ -62,14 +63,14 @@ def is_no_update(obj):
# pylint: disable=too-many-locals
def callback(
*_args,
background=False,
interval=1000,
progress=None,
progress_default=None,
running=None,
cancel=None,
manager=None,
cache_args_to_ignore=None,
background: bool = False,
interval: int = 1000,
progress: Optional[Output] = None,
progress_default: Any = None,
running: Optional[List[Tuple[Output, Any, Any]]] = None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

took me a moment to parse this :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's given as [(Output('id', 'prop), "on", "off")]

Copy link
Contributor

@eff-kay eff-kay Aug 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can just stop at a higher level Optional[List], and be les concrete, leave room for future modifications.

cancel: Optional[List[Input]] = None,
manager: Optional[BaseLongCallbackManager] = None,
cache_args_to_ignore: Optional[list] = None,
on_error: Optional[Callable[[Exception], Any]] = None,
**_kwargs,
):
Expand Down Expand Up @@ -156,7 +157,7 @@ def callback(
callback_list = _kwargs.pop("callback_list", GLOBAL_CALLBACK_LIST)

if background:
long_spec = {
long_spec: Any = {
"interval": interval,
}

Expand Down
94 changes: 73 additions & 21 deletions dash/development/_py_components_generation.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,34 @@
from collections import OrderedDict
import copy
import numbers
import os
import typing
from textwrap import fill, dedent

from typing_extensions import TypedDict, NotRequired, Literal
from dash.development.base_component import _explicitize_args
from dash.exceptions import NonExistentEventException
from ._all_keywords import python_keywords
from ._collect_nodes import collect_nodes, filter_base_nodes
from .base_component import Component
from ._py_prop_typing import get_prop_typing, shapes, custom_imports
from .base_component import Component, ComponentType

import_string = """# AUTO GENERATED FILE - DO NOT EDIT

# pylint: disable=unused-argument,too-many-locals
import typing # noqa: F401
import numbers # noqa: F401
from typing_extensions import TypedDict, NotRequired, Literal # noqa: F401
from dash.development.base_component import Component, _explicitize_args
try:
from dash.development.base_component import ComponentType # noqa: F401
except ImportError:
ComponentType = typing.TypeVar("ComponentType", bound=Component)


"""


# pylint: disable=unused-argument,too-many-locals,too-many-branches
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:-)

def generate_class_string(
typename,
props,
Expand Down Expand Up @@ -54,8 +72,12 @@ def generate_class_string(
_base_nodes = {base_nodes}
_namespace = '{namespace}'
_type = '{typename}'
{shapes}
@_explicitize_args
def __init__(self, {default_argtext}):
def __init__(
self,
{default_argtext}
):
self._prop_names = {list_of_valid_keys}
self._valid_wildcard_attributes =\
{list_of_valid_wildcard_attr_prefixes}
Expand Down Expand Up @@ -94,7 +116,9 @@ def __init__(self, {default_argtext}):
prop_keys = list(props.keys())
if "children" in props and "children" in list_of_valid_keys:
prop_keys.remove("children")
default_argtext = "children=None, "
# TODO For dash 3.0, remove the Optional and = None for proper typing.
# Also add the other required props after children.
default_argtext = f"children: typing.Optional[{get_prop_typing('node', '', '', {})}] = None,\n "
args = "{k: _locals[k] for k in _explicit_args if k != 'children'}"
argtext = "children=children, **args"
else:
Expand All @@ -118,15 +142,31 @@ def __init__(self, {default_argtext}):
raise TypeError('Required argument children was not specified.')
"""

default_arglist = [
(
f"{p:s}=Component.REQUIRED"
if props[p]["required"]
else f"{p:s}=Component.UNDEFINED"
)
for p in prop_keys
if not p.endswith("-*") and p not in python_keywords and p != "setProps"
]
default_arglist = []

for prop_key in prop_keys:
prop = props[prop_key]
if (
prop_key.endswith("-*")
or prop_key in python_keywords
or prop_key == "setProps"
):
continue

type_info = prop.get("type")

if not type_info:
print(f"Invalid prop type for typing: {prop_key}")
default_arglist.append(f"{prop_key} = None")
continue

type_name = type_info.get("name")

typed = get_prop_typing(type_name, typename, prop_key, type_info, namespace)

arg_value = f"{prop_key}: typing.Optional[{typed}] = None"

default_arglist.append(arg_value)

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

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

return dedent(
Expand All @@ -156,6 +196,7 @@ def __init__(self, {default_argtext}):
required_validation=required_validation,
children_props=nodes,
base_nodes=filter_base_nodes(nodes) + ["children"],
shapes="\n".join(shapes.get(typename, {}).values()),
)
)

Expand All @@ -179,20 +220,22 @@ def generate_class_file(
Returns
-------
"""
import_string = (
"# AUTO GENERATED FILE - DO NOT EDIT\n\n"
+ "from dash.development.base_component import "
+ "Component, _explicitize_args\n\n\n"
)
imports = import_string

class_string = generate_class_string(
typename, props, description, namespace, prop_reorder_exceptions, max_props
)

custom_imp = custom_imports[namespace][typename]
if custom_imp:
imports += "\n".join(custom_imp)
imports += "\n\n"

file_name = f"{typename:s}.py"

file_path = os.path.join(namespace, file_name)
with open(file_path, "w", encoding="utf-8") as f:
f.write(import_string)
f.write(imports)
f.write(class_string)

print(f"Generated {file_name}")
Expand Down Expand Up @@ -242,7 +285,16 @@ def generate_class(
string = generate_class_string(
typename, props, description, namespace, prop_reorder_exceptions
)
scope = {"Component": Component, "_explicitize_args": _explicitize_args}
scope = {
"Component": Component,
"ComponentType": ComponentType,
"_explicitize_args": _explicitize_args,
"typing": typing,
"numbers": numbers,
"TypedDict": TypedDict,
"NotRequired": NotRequired,
"Literal": Literal,
}
# pylint: disable=exec-used
exec(string, scope)
result = scope[typename]
Expand Down
Loading