Forráskód Böngészése

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 6 hónapja
szülő
commit
a65fc2e90b

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

@@ -109,19 +109,6 @@ class DataEditorTheme(Base):
     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):
     """The bounds of the group header."""
 
@@ -149,7 +136,7 @@ class Rectangle(TypedDict):
 class GridSelectionCurrent(TypedDict):
     """The current selection."""
 
-    cell: list[int]
+    cell: tuple[int, int]
     range: Rectangle
     rangeStack: list[Rectangle]
 
@@ -167,7 +154,7 @@ class GroupHeaderClickedEventArgs(TypedDict):
 
     kind: str
     group: str
-    location: list[int]
+    location: tuple[int, int]
     bounds: Bounds
     isEdge: bool
     shiftKey: bool
@@ -178,7 +165,7 @@ class GroupHeaderClickedEventArgs(TypedDict):
     localEventY: int
     button: int
     buttons: int
-    scrollEdge: list[int]
+    scrollEdge: tuple[int, int]
 
 
 class GridCell(TypedDict):
@@ -306,10 +293,10 @@ class DataEditor(NoSSRComponent):
     on_cell_context_menu: EventHandler[identity_event(Tuple[int, int])]
 
     # 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.
-    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.
     on_group_header_context_menu: EventHandler[
@@ -335,7 +322,9 @@ class DataEditor(NoSSRComponent):
     on_delete: EventHandler[identity_event(GridSelection)]
 
     # 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.
     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_medium: Optional[str]
 
-def on_edit_spec(pos, data: dict[str, Any]): ...
-
 class Bounds(TypedDict):
     x: int
     y: int
@@ -96,7 +94,7 @@ class Rectangle(TypedDict):
     height: int
 
 class GridSelectionCurrent(TypedDict):
-    cell: list[int]
+    cell: tuple[int, int]
     range: Rectangle
     rangeStack: list[Rectangle]
 
@@ -108,7 +106,7 @@ class GridSelection(TypedDict):
 class GroupHeaderClickedEventArgs(TypedDict):
     kind: str
     group: str
-    location: list[int]
+    location: tuple[int, int]
     bounds: Bounds
     isEdge: bool
     shiftKey: bool
@@ -119,7 +117,7 @@ class GroupHeaderClickedEventArgs(TypedDict):
     localEventY: int
     button: int
     buttons: int
-    scrollEdge: list[int]
+    scrollEdge: tuple[int, int]
 
 class GridCell(TypedDict):
     span: Optional[List[int]]
@@ -189,17 +187,17 @@ class DataEditor(NoSSRComponent):
         on_cell_activated: 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_edited: Optional[EventType] = None,
+        on_cell_edited: Optional[EventType[tuple[int, int], GridCell]] = None,
         on_click: Optional[EventType[[]]] = None,
         on_column_resize: Optional[EventType[GridColumn, int]] = None,
         on_context_menu: Optional[EventType[[]]] = None,
         on_delete: Optional[EventType[GridSelection]] = None,
         on_double_click: Optional[EventType[[]]] = None,
         on_finished_editing: Optional[
-            EventType[Union[GridCell, None], list[int]]
+            EventType[Union[GridCell, None], tuple[int, int]]
         ] = 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[
             EventType[int, GroupHeaderClickedEventArgs]
         ] = None,

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

@@ -5,7 +5,9 @@
 # ------------------------------------------------------
 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.vars.base import Var
 

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

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

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

@@ -5,6 +5,7 @@
 # ------------------------------------------------------
 from typing import Any, Dict, Optional, Union, overload
 
+import reflex
 from reflex.components.react_player.react_player import ReactPlayer
 from reflex.event import EventType
 from reflex.style import Style
@@ -58,7 +59,9 @@ class Audio(ReactPlayer):
         on_play: Optional[EventType[[]]] = None,
         on_playback_quality_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_scroll: Optional[EventType[[]]] = 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 typing_extensions import TypedDict
+
 from reflex.components.component import NoSSRComponent
 from reflex.event import EventHandler, empty_event, identity_event
 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):
     """Using react-player and not implement all props and callback yet.
     reference: https://github.com/cookpete/react-player.
@@ -55,7 +66,7 @@ class ReactPlayer(NoSSRComponent):
     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 }
-    on_progress: EventHandler[lambda progress: [progress]]
+    on_progress: EventHandler[identity_event(Progress)]
 
     # Callback containing duration of the media, in seconds.
     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_extensions import TypedDict
+
 from reflex.components.component import NoSSRComponent
 from reflex.event import EventType
 from reflex.style import Style
 from reflex.vars.base import Var
 
+class Progress(TypedDict):
+    played: float
+    playedSeconds: float
+    loaded: float
+    loadedSeconds: float
+
 class ReactPlayer(NoSSRComponent):
     @overload
     @classmethod
@@ -56,7 +64,7 @@ class ReactPlayer(NoSSRComponent):
         on_play: Optional[EventType[[]]] = None,
         on_playback_quality_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_scroll: Optional[EventType[[]]] = 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
 
+import reflex
 from reflex.components.react_player.react_player import ReactPlayer
 from reflex.event import EventType
 from reflex.style import Style
@@ -58,7 +59,9 @@ class Video(ReactPlayer):
         on_play: Optional[EventType[[]]] = None,
         on_playback_quality_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_scroll: Optional[EventType[[]]] = 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
 
 
-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.
 
     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])
             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
 
 
-def type_to_ast(typ) -> ast.AST:
+def type_to_ast(typ, cls: type) -> ast.AST:
     """Converts any type annotation into its AST representation.
     Handles nested generic types, unions, etc.
 
     Args:
         typ: The type annotation to convert.
+        cls: The class where the type annotation is used.
 
     Returns:
         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.)
     if origin is None:
         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__)
         elif hasattr(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)
 
     # 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])
     if len(arg_nodes) == 1:
@@ -487,7 +501,7 @@ def _generate_component_create_functiondef(
             ]
 
             # 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
             args_str = ", ".join(ast.unparse(arg) for arg in type_args)