Skip to content
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

Improve typing. #3259

Merged
merged 5 commits into from
Apr 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion dash/_callback.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def callback(
cache_ignore_triggered=True,
on_error: Optional[Callable[[Exception], Any]] = None,
**_kwargs,
):
) -> Callable[..., Any]:
"""
Normally used as a decorator, `@dash.callback` provides a server-side
callback relating the values of one or more `Output` items to one or
Expand Down
2 changes: 1 addition & 1 deletion dash/dash.py
Original file line number Diff line number Diff line change
Expand Up @@ -1269,7 +1269,7 @@ def clientside_callback(self, clientside_function, *args, **kwargs):
**kwargs,
)

def callback(self, *_args, **_kwargs):
def callback(self, *_args, **_kwargs) -> Callable[..., Any]:
"""
Normally used as a decorator, `@app.callback` provides a server-side
callback relating the values of one or more `Output` items to one or
Expand Down
33 changes: 24 additions & 9 deletions dash/dependencies.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
from typing import Union, Sequence

from dash.development.base_component import Component

from ._validate import validate_callback
from ._grouping import flatten_grouping, make_grouping_by_index
from ._utils import stringify_id


ComponentIdType = Union[str, Component, dict]


class _Wildcard: # pylint: disable=too-few-public-methods
def __init__(self, name):
def __init__(self, name: str):
self._name = name

def __str__(self):
Expand All @@ -15,7 +20,7 @@ def __str__(self):
def __repr__(self):
return f"<{self}>"

def to_json(self):
def to_json(self) -> str:
# used in serializing wildcards - arrays are not allowed as
# id values, so make the wildcards look like length-1 arrays.
return f'["{self._name}"]'
Expand All @@ -27,7 +32,12 @@ def to_json(self):


class DashDependency: # pylint: disable=too-few-public-methods
def __init__(self, component_id, component_property):
component_id: ComponentIdType
allow_duplicate: bool
component_property: str
allowed_wildcards: Sequence[_Wildcard]

def __init__(self, component_id: ComponentIdType, component_property: str):

if isinstance(component_id, Component):
self.component_id = component_id._set_random_id()
Expand All @@ -43,10 +53,10 @@ def __str__(self):
def __repr__(self):
return f"<{self.__class__.__name__} `{self}`>"

def component_id_str(self):
def component_id_str(self) -> str:
return stringify_id(self.component_id)

def to_dict(self):
def to_dict(self) -> dict:
return {"id": self.component_id_str(), "property": self.component_property}

def __eq__(self, other):
Expand All @@ -61,7 +71,7 @@ def __eq__(self, other):
and self._id_matches(other)
)

def _id_matches(self, other):
def _id_matches(self, other) -> bool:
my_id = self.component_id
other_id = other.component_id
self_dict = isinstance(my_id, dict)
Expand Down Expand Up @@ -96,7 +106,7 @@ def _id_matches(self, other):
def __hash__(self):
return hash(str(self))

def has_wildcard(self):
def has_wildcard(self) -> bool:
"""
Return true if id contains a wildcard (MATCH, ALL, or ALLSMALLER)
"""
Expand All @@ -112,7 +122,12 @@ class Output(DashDependency): # pylint: disable=too-few-public-methods

allowed_wildcards = (MATCH, ALL)

def __init__(self, component_id, component_property, allow_duplicate=False):
def __init__(
self,
component_id: ComponentIdType,
component_property: str,
allow_duplicate: bool = False,
):
super().__init__(component_id, component_property)
self.allow_duplicate = allow_duplicate

Expand All @@ -130,7 +145,7 @@ class State(DashDependency): # pylint: disable=too-few-public-methods


class ClientsideFunction: # pylint: disable=too-few-public-methods
def __init__(self, namespace=None, function_name=None):
def __init__(self, namespace: str, function_name: str):

if namespace.startswith("_dashprivate_"):
raise ValueError("Namespaces cannot start with '_dashprivate_'.")
Expand Down
5 changes: 3 additions & 2 deletions dash/development/_py_components_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
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
from dash.development.base_component import Component
try:
from dash.development.base_component import ComponentType # noqa: F401
except ImportError:
Expand Down Expand Up @@ -80,7 +80,8 @@ def generate_class_string(
_namespace = '{namespace}'
_type = '{typename}'
{shapes}
@_explicitize_args
_explicitize_dash_init = True

def __init__(
self,
{default_argtext}
Expand Down
18 changes: 14 additions & 4 deletions dash/development/base_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,21 @@ class ComponentMeta(abc.ABCMeta):

# pylint: disable=arguments-differ
def __new__(mcs, name, bases, attributes):
component = abc.ABCMeta.__new__(mcs, name, bases, attributes)
module = attributes["__module__"].split(".")[0]

if attributes.get("_explicitize_dash_init", False):
# We only want to patch the new generated component without
# the `@_explicitize_args` decorator for mypy support
# See issue: https://github.com/plotly/dash/issues/3226
attributes["__init__"] = _explicitize_args(attributes["__init__"])

_component = abc.ABCMeta.__new__(mcs, name, bases, attributes)

if name == "Component" or module == "builtins":
# Don't do the base component
# Don't add to the registry the base component
# and the components loaded dynamically by load_component
# as it doesn't have the namespace.
return component
return _component

_namespace = attributes.get("_namespace", module)
ComponentRegistry.namespace_to_package[_namespace] = module
Expand All @@ -66,7 +74,7 @@ def __new__(mcs, name, bases, attributes):
"_children_props"
)

return component
return _component


def is_number(s):
Expand Down Expand Up @@ -435,6 +443,8 @@ def _validate_deprecation(self):

ComponentType = typing.TypeVar("ComponentType", bound=Component)

ComponentTemplate = typing.TypeVar("ComponentTemplate")


# This wrapper adds an argument given to generated Component.__init__
# with the actual given parameters by the user as a list of string.
Expand Down
2 changes: 1 addition & 1 deletion requirements/ci.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ pyzmq==25.1.2
xlrd>=2.0.1
pytest-rerunfailures
jupyterlab<4.0.0
pyright==1.1.376;python_version>="3.7"
pyright==1.1.398;python_version>="3.7"
6 changes: 4 additions & 2 deletions tests/integration/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,15 +223,17 @@ def assert_pyright_output(
"obj={}",
{
"expected_status": 1,
"expected_outputs": ['"dict[Any, Any]" is incompatible with "Obj"'],
"expected_outputs": [
'"dict[Any, Any]" cannot be assigned to parameter "obj" of type "Obj | None"'
],
},
),
(
"obj={'value': 'a', 'label': 1}",
{
"expected_status": 1,
"expected_outputs": [
'"dict[str, str | int]" is incompatible with "Obj"'
'"dict[str, str | int]" cannot be assigned to parameter "obj" of type "Obj | None"'
],
},
),
Expand Down
5 changes: 3 additions & 2 deletions tests/unit/development/metadata_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
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
from dash.development.base_component import Component
try:
from dash.development.base_component import ComponentType # noqa: F401
except ImportError:
Expand Down Expand Up @@ -131,7 +131,8 @@ class Table(Component):
}
)

@_explicitize_args
_explicitize_dash_init = True

def __init__(
self,
children: typing.Optional[typing.Union[str, int, float, ComponentType, typing.Sequence[typing.Union[str, int, float, ComponentType]]]] = None,
Expand Down