Jelajahi Sumber

Add on progress typing to react player (#4211)

* add on progress typing to react player

* fix pyi file

* have the pyi here as well

* more pyi changes

* fix imports

* run pyi

* for some reason it want event on three lines no clue why

* simplify case for when type is in the same module

* run pyi

* remove last missing type for datadisplay
Khaleel Al-Adhami 7 bulan lalu
induk
melakukan
a65fc2e90b

+ 8 - 19
reflex/components/datadisplay/dataeditor.py

@@ -109,19 +109,6 @@ class DataEditorTheme(Base):
     text_medium: Optional[str] = None
     text_medium: Optional[str] = None
 
 
 
 
-def on_edit_spec(pos, data: dict[str, Any]):
-    """The on edit spec function.
-
-    Args:
-        pos: The position of the edit event.
-        data: The data of the edit event.
-
-    Returns:
-        The position and data.
-    """
-    return [pos, data]
-
-
 class Bounds(TypedDict):
 class Bounds(TypedDict):
     """The bounds of the group header."""
     """The bounds of the group header."""
 
 
@@ -149,7 +136,7 @@ class Rectangle(TypedDict):
 class GridSelectionCurrent(TypedDict):
 class GridSelectionCurrent(TypedDict):
     """The current selection."""
     """The current selection."""
 
 
-    cell: list[int]
+    cell: tuple[int, int]
     range: Rectangle
     range: Rectangle
     rangeStack: list[Rectangle]
     rangeStack: list[Rectangle]
 
 
@@ -167,7 +154,7 @@ class GroupHeaderClickedEventArgs(TypedDict):
 
 
     kind: str
     kind: str
     group: str
     group: str
-    location: list[int]
+    location: tuple[int, int]
     bounds: Bounds
     bounds: Bounds
     isEdge: bool
     isEdge: bool
     shiftKey: bool
     shiftKey: bool
@@ -178,7 +165,7 @@ class GroupHeaderClickedEventArgs(TypedDict):
     localEventY: int
     localEventY: int
     button: int
     button: int
     buttons: int
     buttons: int
-    scrollEdge: list[int]
+    scrollEdge: tuple[int, int]
 
 
 
 
 class GridCell(TypedDict):
 class GridCell(TypedDict):
@@ -306,10 +293,10 @@ class DataEditor(NoSSRComponent):
     on_cell_context_menu: EventHandler[identity_event(Tuple[int, int])]
     on_cell_context_menu: EventHandler[identity_event(Tuple[int, int])]
 
 
     # Fired when a cell is edited.
     # Fired when a cell is edited.
-    on_cell_edited: EventHandler[on_edit_spec]
+    on_cell_edited: EventHandler[identity_event(Tuple[int, int], GridCell)]
 
 
     # Fired when a group header is clicked.
     # Fired when a group header is clicked.
-    on_group_header_clicked: EventHandler[on_edit_spec]
+    on_group_header_clicked: EventHandler[identity_event(Tuple[int, int], GridCell)]
 
 
     # Fired when a group header is right-clicked.
     # Fired when a group header is right-clicked.
     on_group_header_context_menu: EventHandler[
     on_group_header_context_menu: EventHandler[
@@ -335,7 +322,9 @@ class DataEditor(NoSSRComponent):
     on_delete: EventHandler[identity_event(GridSelection)]
     on_delete: EventHandler[identity_event(GridSelection)]
 
 
     # Fired when editing is finished.
     # Fired when editing is finished.
-    on_finished_editing: EventHandler[identity_event(Union[GridCell, None], list[int])]
+    on_finished_editing: EventHandler[
+        identity_event(Union[GridCell, None], tuple[int, int])
+    ]
 
 
     # Fired when a row is appended.
     # Fired when a row is appended.
     on_row_appended: EventHandler[empty_event]
     on_row_appended: EventHandler[empty_event]

+ 6 - 8
reflex/components/datadisplay/dataeditor.pyi

@@ -78,8 +78,6 @@ class DataEditorTheme(Base):
     text_light: Optional[str]
     text_light: Optional[str]
     text_medium: Optional[str]
     text_medium: Optional[str]
 
 
-def on_edit_spec(pos, data: dict[str, Any]): ...
-
 class Bounds(TypedDict):
 class Bounds(TypedDict):
     x: int
     x: int
     y: int
     y: int
@@ -96,7 +94,7 @@ class Rectangle(TypedDict):
     height: int
     height: int
 
 
 class GridSelectionCurrent(TypedDict):
 class GridSelectionCurrent(TypedDict):
-    cell: list[int]
+    cell: tuple[int, int]
     range: Rectangle
     range: Rectangle
     rangeStack: list[Rectangle]
     rangeStack: list[Rectangle]
 
 
@@ -108,7 +106,7 @@ class GridSelection(TypedDict):
 class GroupHeaderClickedEventArgs(TypedDict):
 class GroupHeaderClickedEventArgs(TypedDict):
     kind: str
     kind: str
     group: str
     group: str
-    location: list[int]
+    location: tuple[int, int]
     bounds: Bounds
     bounds: Bounds
     isEdge: bool
     isEdge: bool
     shiftKey: bool
     shiftKey: bool
@@ -119,7 +117,7 @@ class GroupHeaderClickedEventArgs(TypedDict):
     localEventY: int
     localEventY: int
     button: int
     button: int
     buttons: int
     buttons: int
-    scrollEdge: list[int]
+    scrollEdge: tuple[int, int]
 
 
 class GridCell(TypedDict):
 class GridCell(TypedDict):
     span: Optional[List[int]]
     span: Optional[List[int]]
@@ -189,17 +187,17 @@ class DataEditor(NoSSRComponent):
         on_cell_activated: Optional[EventType[tuple[int, int]]] = None,
         on_cell_activated: Optional[EventType[tuple[int, int]]] = None,
         on_cell_clicked: Optional[EventType[tuple[int, int]]] = None,
         on_cell_clicked: Optional[EventType[tuple[int, int]]] = None,
         on_cell_context_menu: Optional[EventType[tuple[int, int]]] = None,
         on_cell_context_menu: Optional[EventType[tuple[int, int]]] = None,
-        on_cell_edited: Optional[EventType] = None,
+        on_cell_edited: Optional[EventType[tuple[int, int], GridCell]] = None,
         on_click: Optional[EventType[[]]] = None,
         on_click: Optional[EventType[[]]] = None,
         on_column_resize: Optional[EventType[GridColumn, int]] = None,
         on_column_resize: Optional[EventType[GridColumn, int]] = None,
         on_context_menu: Optional[EventType[[]]] = None,
         on_context_menu: Optional[EventType[[]]] = None,
         on_delete: Optional[EventType[GridSelection]] = None,
         on_delete: Optional[EventType[GridSelection]] = None,
         on_double_click: Optional[EventType[[]]] = None,
         on_double_click: Optional[EventType[[]]] = None,
         on_finished_editing: Optional[
         on_finished_editing: Optional[
-            EventType[Union[GridCell, None], list[int]]
+            EventType[Union[GridCell, None], tuple[int, int]]
         ] = None,
         ] = None,
         on_focus: Optional[EventType[[]]] = None,
         on_focus: Optional[EventType[[]]] = None,
-        on_group_header_clicked: Optional[EventType] = None,
+        on_group_header_clicked: Optional[EventType[tuple[int, int], GridCell]] = None,
         on_group_header_context_menu: Optional[
         on_group_header_context_menu: Optional[
             EventType[int, GroupHeaderClickedEventArgs]
             EventType[int, GroupHeaderClickedEventArgs]
         ] = None,
         ] = None,

+ 3 - 1
reflex/components/radix/themes/components/tooltip.pyi

@@ -5,7 +5,9 @@
 # ------------------------------------------------------
 # ------------------------------------------------------
 from typing import Any, Dict, Literal, Optional, Union, overload
 from typing import Any, Dict, Literal, Optional, Union, overload
 
 
-from reflex.event import EventType
+from reflex.event import (
+    EventType,
+)
 from reflex.style import Style
 from reflex.style import Style
 from reflex.vars.base import Var
 from reflex.vars.base import Var
 
 

+ 1 - 0
reflex/components/react_player/__init__.py

@@ -1,5 +1,6 @@
 """React Player component for audio and video."""
 """React Player component for audio and video."""
 
 
+from . import react_player
 from .audio import Audio
 from .audio import Audio
 from .video import Video
 from .video import Video
 
 

+ 4 - 1
reflex/components/react_player/audio.pyi

@@ -5,6 +5,7 @@
 # ------------------------------------------------------
 # ------------------------------------------------------
 from typing import Any, Dict, Optional, Union, overload
 from typing import Any, Dict, Optional, Union, overload
 
 
+import reflex
 from reflex.components.react_player.react_player import ReactPlayer
 from reflex.components.react_player.react_player import ReactPlayer
 from reflex.event import EventType
 from reflex.event import EventType
 from reflex.style import Style
 from reflex.style import Style
@@ -58,7 +59,9 @@ class Audio(ReactPlayer):
         on_play: Optional[EventType[[]]] = None,
         on_play: Optional[EventType[[]]] = None,
         on_playback_quality_change: Optional[EventType[[]]] = None,
         on_playback_quality_change: Optional[EventType[[]]] = None,
         on_playback_rate_change: Optional[EventType[[]]] = None,
         on_playback_rate_change: Optional[EventType[[]]] = None,
-        on_progress: Optional[EventType] = None,
+        on_progress: Optional[
+            EventType[reflex.components.react_player.react_player.Progress]
+        ] = None,
         on_ready: Optional[EventType[[]]] = None,
         on_ready: Optional[EventType[[]]] = None,
         on_scroll: Optional[EventType[[]]] = None,
         on_scroll: Optional[EventType[[]]] = None,
         on_seek: Optional[EventType[float]] = None,
         on_seek: Optional[EventType[float]] = None,

+ 12 - 1
reflex/components/react_player/react_player.py

@@ -2,11 +2,22 @@
 
 
 from __future__ import annotations
 from __future__ import annotations
 
 
+from typing_extensions import TypedDict
+
 from reflex.components.component import NoSSRComponent
 from reflex.components.component import NoSSRComponent
 from reflex.event import EventHandler, empty_event, identity_event
 from reflex.event import EventHandler, empty_event, identity_event
 from reflex.vars.base import Var
 from reflex.vars.base import Var
 
 
 
 
+class Progress(TypedDict):
+    """Callback containing played and loaded progress as a fraction, and playedSeconds and loadedSeconds in seconds."""
+
+    played: float
+    playedSeconds: float
+    loaded: float
+    loadedSeconds: float
+
+
 class ReactPlayer(NoSSRComponent):
 class ReactPlayer(NoSSRComponent):
     """Using react-player and not implement all props and callback yet.
     """Using react-player and not implement all props and callback yet.
     reference: https://github.com/cookpete/react-player.
     reference: https://github.com/cookpete/react-player.
@@ -55,7 +66,7 @@ class ReactPlayer(NoSSRComponent):
     on_play: EventHandler[empty_event]
     on_play: EventHandler[empty_event]
 
 
     # Callback containing played and loaded progress as a fraction, and playedSeconds and loadedSeconds in seconds. eg { played: 0.12, playedSeconds: 11.3, loaded: 0.34, loadedSeconds: 16.7 }
     # Callback containing played and loaded progress as a fraction, and playedSeconds and loadedSeconds in seconds. eg { played: 0.12, playedSeconds: 11.3, loaded: 0.34, loadedSeconds: 16.7 }
-    on_progress: EventHandler[lambda progress: [progress]]
+    on_progress: EventHandler[identity_event(Progress)]
 
 
     # Callback containing duration of the media, in seconds.
     # Callback containing duration of the media, in seconds.
     on_duration: EventHandler[identity_event(float)]
     on_duration: EventHandler[identity_event(float)]

+ 9 - 1
reflex/components/react_player/react_player.pyi

@@ -5,11 +5,19 @@
 # ------------------------------------------------------
 # ------------------------------------------------------
 from typing import Any, Dict, Optional, Union, overload
 from typing import Any, Dict, Optional, Union, overload
 
 
+from typing_extensions import TypedDict
+
 from reflex.components.component import NoSSRComponent
 from reflex.components.component import NoSSRComponent
 from reflex.event import EventType
 from reflex.event import EventType
 from reflex.style import Style
 from reflex.style import Style
 from reflex.vars.base import Var
 from reflex.vars.base import Var
 
 
+class Progress(TypedDict):
+    played: float
+    playedSeconds: float
+    loaded: float
+    loadedSeconds: float
+
 class ReactPlayer(NoSSRComponent):
 class ReactPlayer(NoSSRComponent):
     @overload
     @overload
     @classmethod
     @classmethod
@@ -56,7 +64,7 @@ class ReactPlayer(NoSSRComponent):
         on_play: Optional[EventType[[]]] = None,
         on_play: Optional[EventType[[]]] = None,
         on_playback_quality_change: Optional[EventType[[]]] = None,
         on_playback_quality_change: Optional[EventType[[]]] = None,
         on_playback_rate_change: Optional[EventType[[]]] = None,
         on_playback_rate_change: Optional[EventType[[]]] = None,
-        on_progress: Optional[EventType] = None,
+        on_progress: Optional[EventType[Progress]] = None,
         on_ready: Optional[EventType[[]]] = None,
         on_ready: Optional[EventType[[]]] = None,
         on_scroll: Optional[EventType[[]]] = None,
         on_scroll: Optional[EventType[[]]] = None,
         on_seek: Optional[EventType[float]] = None,
         on_seek: Optional[EventType[float]] = None,

+ 4 - 1
reflex/components/react_player/video.pyi

@@ -5,6 +5,7 @@
 # ------------------------------------------------------
 # ------------------------------------------------------
 from typing import Any, Dict, Optional, Union, overload
 from typing import Any, Dict, Optional, Union, overload
 
 
+import reflex
 from reflex.components.react_player.react_player import ReactPlayer
 from reflex.components.react_player.react_player import ReactPlayer
 from reflex.event import EventType
 from reflex.event import EventType
 from reflex.style import Style
 from reflex.style import Style
@@ -58,7 +59,9 @@ class Video(ReactPlayer):
         on_play: Optional[EventType[[]]] = None,
         on_play: Optional[EventType[[]]] = None,
         on_playback_quality_change: Optional[EventType[[]]] = None,
         on_playback_quality_change: Optional[EventType[[]]] = None,
         on_playback_rate_change: Optional[EventType[[]]] = None,
         on_playback_rate_change: Optional[EventType[[]]] = None,
-        on_progress: Optional[EventType] = None,
+        on_progress: Optional[
+            EventType[reflex.components.react_player.react_player.Progress]
+        ] = None,
         on_ready: Optional[EventType[[]]] = None,
         on_ready: Optional[EventType[[]]] = None,
         on_scroll: Optional[EventType[[]]] = None,
         on_scroll: Optional[EventType[[]]] = None,
         on_seek: Optional[EventType[float]] = None,
         on_seek: Optional[EventType[float]] = None,

+ 18 - 4
reflex/utils/pyi_generator.py

@@ -214,7 +214,9 @@ def _get_type_hint(value, type_hint_globals, is_optional=True) -> str:
     return res
     return res
 
 
 
 
-def _generate_imports(typing_imports: Iterable[str]) -> list[ast.ImportFrom]:
+def _generate_imports(
+    typing_imports: Iterable[str],
+) -> list[ast.ImportFrom | ast.Import]:
     """Generate the import statements for the stub file.
     """Generate the import statements for the stub file.
 
 
     Args:
     Args:
@@ -228,6 +230,7 @@ def _generate_imports(typing_imports: Iterable[str]) -> list[ast.ImportFrom]:
             ast.ImportFrom(module=name, names=[ast.alias(name=val) for val in values])
             ast.ImportFrom(module=name, names=[ast.alias(name=val) for val in values])
             for name, values in DEFAULT_IMPORTS.items()
             for name, values in DEFAULT_IMPORTS.items()
         ],
         ],
+        ast.Import([ast.alias("reflex")]),
     ]
     ]
 
 
 
 
@@ -372,12 +375,13 @@ def _extract_class_props_as_ast_nodes(
     return kwargs
     return kwargs
 
 
 
 
-def type_to_ast(typ) -> ast.AST:
+def type_to_ast(typ, cls: type) -> ast.AST:
     """Converts any type annotation into its AST representation.
     """Converts any type annotation into its AST representation.
     Handles nested generic types, unions, etc.
     Handles nested generic types, unions, etc.
 
 
     Args:
     Args:
         typ: The type annotation to convert.
         typ: The type annotation to convert.
+        cls: The class where the type annotation is used.
 
 
     Returns:
     Returns:
         The AST representation of the type annotation.
         The AST representation of the type annotation.
@@ -390,6 +394,16 @@ def type_to_ast(typ) -> ast.AST:
     # Handle plain types (int, str, custom classes, etc.)
     # Handle plain types (int, str, custom classes, etc.)
     if origin is None:
     if origin is None:
         if hasattr(typ, "__name__"):
         if hasattr(typ, "__name__"):
+            if typ.__module__.startswith("reflex."):
+                typ_parts = typ.__module__.split(".")
+                cls_parts = cls.__module__.split(".")
+
+                zipped = list(zip(typ_parts, cls_parts, strict=False))
+
+                if all(a == b for a, b in zipped) and len(typ_parts) == len(cls_parts):
+                    return ast.Name(id=typ.__name__)
+
+                return ast.Name(id=typ.__module__ + "." + typ.__name__)
             return ast.Name(id=typ.__name__)
             return ast.Name(id=typ.__name__)
         elif hasattr(typ, "_name"):
         elif hasattr(typ, "_name"):
             return ast.Name(id=typ._name)
             return ast.Name(id=typ._name)
@@ -406,7 +420,7 @@ def type_to_ast(typ) -> ast.AST:
         return ast.Name(id=base_name)
         return ast.Name(id=base_name)
 
 
     # Convert all type arguments recursively
     # Convert all type arguments recursively
-    arg_nodes = [type_to_ast(arg) for arg in args]
+    arg_nodes = [type_to_ast(arg, cls) for arg in args]
 
 
     # Special case for single-argument types (like List[T] or Optional[T])
     # Special case for single-argument types (like List[T] or Optional[T])
     if len(arg_nodes) == 1:
     if len(arg_nodes) == 1:
@@ -487,7 +501,7 @@ def _generate_component_create_functiondef(
             ]
             ]
 
 
             # Convert each argument type to its AST representation
             # Convert each argument type to its AST representation
-            type_args = [type_to_ast(arg) for arg in arguments_without_var]
+            type_args = [type_to_ast(arg, cls=clz) for arg in arguments_without_var]
 
 
             # Join the type arguments with commas for EventType
             # Join the type arguments with commas for EventType
             args_str = ", ".join(ast.unparse(arg) for arg in type_args)
             args_str = ", ".join(ast.unparse(arg) for arg in type_args)