add typed dict type checking (#4340)
* add typed dict type checking * technically it has to be a mapping, not specifically a dict
This commit is contained in:
parent
1f9a17539c
commit
bcea79cd45
@ -186,6 +186,23 @@ ComponentStyle = Dict[
|
|||||||
ComponentChild = Union[types.PrimitiveType, Var, BaseComponent]
|
ComponentChild = Union[types.PrimitiveType, Var, BaseComponent]
|
||||||
|
|
||||||
|
|
||||||
|
def satisfies_type_hint(obj: Any, type_hint: Any) -> bool:
|
||||||
|
"""Check if an object satisfies a type hint.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
obj: The object to check.
|
||||||
|
type_hint: The type hint to check against.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Whether the object satisfies the type hint.
|
||||||
|
"""
|
||||||
|
if isinstance(obj, LiteralVar):
|
||||||
|
return types._isinstance(obj._var_value, type_hint)
|
||||||
|
if isinstance(obj, Var):
|
||||||
|
return types._issubclass(obj._var_type, type_hint)
|
||||||
|
return types._isinstance(obj, type_hint)
|
||||||
|
|
||||||
|
|
||||||
class Component(BaseComponent, ABC):
|
class Component(BaseComponent, ABC):
|
||||||
"""A component with style, event trigger and other props."""
|
"""A component with style, event trigger and other props."""
|
||||||
|
|
||||||
@ -460,8 +477,7 @@ class Component(BaseComponent, ABC):
|
|||||||
)
|
)
|
||||||
) or (
|
) or (
|
||||||
# Else just check if the passed var type is valid.
|
# Else just check if the passed var type is valid.
|
||||||
not passed_types
|
not passed_types and not satisfies_type_hint(value, expected_type)
|
||||||
and not types._issubclass(passed_type, expected_type, value)
|
|
||||||
):
|
):
|
||||||
value_name = value._js_expr if isinstance(value, Var) else value
|
value_name = value._js_expr if isinstance(value, Var) else value
|
||||||
|
|
||||||
|
@ -14,9 +14,11 @@ from typing import (
|
|||||||
Callable,
|
Callable,
|
||||||
ClassVar,
|
ClassVar,
|
||||||
Dict,
|
Dict,
|
||||||
|
FrozenSet,
|
||||||
Iterable,
|
Iterable,
|
||||||
List,
|
List,
|
||||||
Literal,
|
Literal,
|
||||||
|
Mapping,
|
||||||
Optional,
|
Optional,
|
||||||
Sequence,
|
Sequence,
|
||||||
Tuple,
|
Tuple,
|
||||||
@ -29,6 +31,7 @@ from typing import (
|
|||||||
from typing import get_origin as get_origin_og
|
from typing import get_origin as get_origin_og
|
||||||
|
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
|
from typing_extensions import is_typeddict
|
||||||
|
|
||||||
import reflex
|
import reflex
|
||||||
from reflex.components.core.breakpoints import Breakpoints
|
from reflex.components.core.breakpoints import Breakpoints
|
||||||
@ -494,6 +497,14 @@ def _issubclass(cls: GenericType, cls_check: GenericType, instance: Any = None)
|
|||||||
if isinstance(instance, Breakpoints):
|
if isinstance(instance, Breakpoints):
|
||||||
return _breakpoints_satisfies_typing(cls_check, instance)
|
return _breakpoints_satisfies_typing(cls_check, instance)
|
||||||
|
|
||||||
|
if isinstance(cls_check_base, tuple):
|
||||||
|
cls_check_base = tuple(
|
||||||
|
cls_check_one if not is_typeddict(cls_check_one) else dict
|
||||||
|
for cls_check_one in cls_check_base
|
||||||
|
)
|
||||||
|
if is_typeddict(cls_check_base):
|
||||||
|
cls_check_base = dict
|
||||||
|
|
||||||
# Check if the types match.
|
# Check if the types match.
|
||||||
try:
|
try:
|
||||||
return cls_check_base == Any or issubclass(cls_base, cls_check_base)
|
return cls_check_base == Any or issubclass(cls_base, cls_check_base)
|
||||||
@ -503,6 +514,36 @@ def _issubclass(cls: GenericType, cls_check: GenericType, instance: Any = None)
|
|||||||
raise TypeError(f"Invalid type for issubclass: {cls_base}") from te
|
raise TypeError(f"Invalid type for issubclass: {cls_base}") from te
|
||||||
|
|
||||||
|
|
||||||
|
def does_obj_satisfy_typed_dict(obj: Any, cls: GenericType) -> bool:
|
||||||
|
"""Check if an object satisfies a typed dict.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
obj: The object to check.
|
||||||
|
cls: The typed dict to check against.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Whether the object satisfies the typed dict.
|
||||||
|
"""
|
||||||
|
if not isinstance(obj, Mapping):
|
||||||
|
return False
|
||||||
|
|
||||||
|
key_names_to_values = get_type_hints(cls)
|
||||||
|
required_keys: FrozenSet[str] = getattr(cls, "__required_keys__", frozenset())
|
||||||
|
|
||||||
|
if not all(
|
||||||
|
isinstance(key, str)
|
||||||
|
and key in key_names_to_values
|
||||||
|
and _isinstance(value, key_names_to_values[key])
|
||||||
|
for key, value in obj.items()
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# TODO in 3.14: Implement https://peps.python.org/pep-0728/ if it's approved
|
||||||
|
|
||||||
|
# required keys are all present
|
||||||
|
return required_keys.issubset(required_keys)
|
||||||
|
|
||||||
|
|
||||||
def _isinstance(obj: Any, cls: GenericType, nested: bool = False) -> bool:
|
def _isinstance(obj: Any, cls: GenericType, nested: bool = False) -> bool:
|
||||||
"""Check if an object is an instance of a class.
|
"""Check if an object is an instance of a class.
|
||||||
|
|
||||||
@ -529,6 +570,12 @@ def _isinstance(obj: Any, cls: GenericType, nested: bool = False) -> bool:
|
|||||||
origin = get_origin(cls)
|
origin = get_origin(cls)
|
||||||
|
|
||||||
if origin is None:
|
if origin is None:
|
||||||
|
# cls is a typed dict
|
||||||
|
if is_typeddict(cls):
|
||||||
|
if nested:
|
||||||
|
return does_obj_satisfy_typed_dict(obj, cls)
|
||||||
|
return isinstance(obj, dict)
|
||||||
|
|
||||||
# cls is a simple class
|
# cls is a simple class
|
||||||
return isinstance(obj, cls)
|
return isinstance(obj, cls)
|
||||||
|
|
||||||
@ -553,7 +600,7 @@ def _isinstance(obj: Any, cls: GenericType, nested: bool = False) -> bool:
|
|||||||
and len(obj) == len(args)
|
and len(obj) == len(args)
|
||||||
and all(_isinstance(item, arg) for item, arg in zip(obj, args))
|
and all(_isinstance(item, arg) for item, arg in zip(obj, args))
|
||||||
)
|
)
|
||||||
if origin is dict:
|
if origin in (dict, Breakpoints):
|
||||||
return isinstance(obj, dict) and all(
|
return isinstance(obj, dict) and all(
|
||||||
_isinstance(key, args[0]) and _isinstance(value, args[1])
|
_isinstance(key, args[0]) and _isinstance(value, args[1])
|
||||||
for key, value in obj.items()
|
for key, value in obj.items()
|
||||||
|
Loading…
Reference in New Issue
Block a user