diff --git a/pyproject.toml b/pyproject.toml index 8c7b1c84c23..7ab5355c99b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -164,6 +164,7 @@ reportIncompatibleMethodOverride = false [tool.ruff] target-version = "py310" output-format = "concise" +extend-exclude = ["tests/units/reflex_base/utils/pyi_generator/golden"] lint.isort.split-on-trailing-comma = false preview = true lint.select = ["ALL"] diff --git a/tests/units/reflex_base/utils/__init__.py b/tests/units/reflex_base/utils/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/units/reflex_base/utils/pyi_generator/__init__.py b/tests/units/reflex_base/utils/pyi_generator/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/units/reflex_base/utils/pyi_generator/__main__.py b/tests/units/reflex_base/utils/pyi_generator/__main__.py new file mode 100644 index 00000000000..3f0828cefda --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/__main__.py @@ -0,0 +1,10 @@ +"""CLI entry point for pyi_generator regression tests. + +Usage: + python -m tests.units.reflex_base.utils.pyi_generator --update + python -m tests.units.reflex_base.utils.pyi_generator --check +""" + +from tests.units.reflex_base.utils.pyi_generator.test_regression import main + +main() diff --git a/tests/units/reflex_base/utils/pyi_generator/dataset/__init__.py b/tests/units/reflex_base/utils/pyi_generator/dataset/__init__.py new file mode 100644 index 00000000000..9ba86475d37 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/__init__.py @@ -0,0 +1,6 @@ +"""Test dataset for pyi_generator regression tests. + +This package contains Python modules designed to exercise all translation +features of the pyi_generator. Each module targets specific code paths +in the generator. +""" diff --git a/tests/units/reflex_base/utils/pyi_generator/dataset/classvar_and_private.py b/tests/units/reflex_base/utils/pyi_generator/dataset/classvar_and_private.py new file mode 100644 index 00000000000..e9aa27b9243 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/classvar_and_private.py @@ -0,0 +1,52 @@ +"""Component with ClassVar, private attrs, and excluded props. + +This module tests: +- ClassVar annotations are preserved (not turned into create() kwargs) +- Private annotated attributes (_foo) are removed from stubs +- EXCLUDED_PROPS (like tag, library, etc.) are not in create() +- Private methods are removed +- Non-annotated assignments inside Component classes are removed +- AnnAssign with value is blanked (value set to None) +""" + +from typing import Any, ClassVar + +from reflex_base.components.component import Component, field +from reflex_base.vars.base import Var + + +class StrictComponent(Component): + """A component with ClassVar, private, and excluded props.""" + + _internal_counter: int = 0 + + _hidden_state: ClassVar[dict[str, Any]] = {} + + tag: str | None = "div" + + library: str | None = "some-lib" + + # The valid children for this component. + _valid_children: ClassVar[list[str]] = ["ChildA", "ChildB"] + + # The memoization mode. + _memoization_mode: ClassVar[Any] = None + + # A public prop that should appear in create(). + visible_prop: Var[str] = field(doc="A prop visible in the stub.") + + # Another public prop. + size: Var[int] = field(doc="The size of the component.") + + some_constant = "not_a_prop" + + def _internal_helper(self) -> None: + """Private method, should be removed.""" + + def render_item(self) -> str: + """Public method, body should be blanked. + + Returns: + The rendered item. + """ + return f"
{self.visible_prop}
" diff --git a/tests/units/reflex_base/utils/pyi_generator/dataset/custom_create.py b/tests/units/reflex_base/utils/pyi_generator/dataset/custom_create.py new file mode 100644 index 00000000000..65b1472c03b --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/custom_create.py @@ -0,0 +1,38 @@ +"""Component with an explicitly defined create() method. + +This module tests: +- Existing create() is regenerated (replaced with generated version) +- Decorator list from original create() is preserved +- Custom kwargs on create() are included +""" + +from typing import Any + +from reflex_base.components.component import Component, field +from reflex_base.vars.base import Var + + +class CustomCreateComponent(Component): + """A component that defines its own create method.""" + + # A label prop. + label: Var[str] = field(doc="Display label.") + + # A numeric value. + amount: Var[int] = field(doc="The amount.") + + @classmethod + def create( + cls, *children: Any, custom_kwarg: str = "default", **props: Any + ) -> "CustomCreateComponent": + """Create a custom component with extra kwargs. + + Args: + *children: The child components. + custom_kwarg: A custom keyword argument. + **props: Additional properties. + + Returns: + The component instance. + """ + return super().create(*children, **props) diff --git a/tests/units/reflex_base/utils/pyi_generator/dataset/inheritance.py b/tests/units/reflex_base/utils/pyi_generator/dataset/inheritance.py new file mode 100644 index 00000000000..da31cde1ade --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/inheritance.py @@ -0,0 +1,39 @@ +"""Components with inheritance hierarchy. + +This module tests: +- Props from parent classes appear in create() via MRO traversal +- Overridden props are not duplicated +- Multiple levels of inheritance +""" + +from reflex_base.components.component import Component, field +from reflex_base.event import EventHandler, passthrough_event_spec +from reflex_base.vars.base import Var + + +class BaseWidget(Component): + """A base widget with common props.""" + + # Widget color. + color: Var[str] = field(doc="The widget color.") + + # Whether the widget is disabled. + disabled: Var[bool] = field(doc="Whether the widget is disabled.") + + +class InteractiveWidget(BaseWidget): + """An interactive widget extending BaseWidget.""" + + # The placeholder text. + placeholder: Var[str] = field(doc="Placeholder text.") + + on_value_change: EventHandler[passthrough_event_spec(str)] = field( + doc="Fired when value changes.", + ) + + +class FancyWidget(InteractiveWidget): + """A fancy widget with extra styling, three levels deep.""" + + # Border radius. + border_radius: Var[str] = field(doc="The border radius.") diff --git a/tests/units/reflex_base/utils/pyi_generator/dataset/literal_types.py b/tests/units/reflex_base/utils/pyi_generator/dataset/literal_types.py new file mode 100644 index 00000000000..7ff76d70669 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/literal_types.py @@ -0,0 +1,31 @@ +"""Component with Literal type props. + +This module tests: +- Literal type annotations on props +- Module-level Literal type aliases +- Var[Literal[...]] expansion (Var union with inner literal) +""" + +from typing import Literal + +from reflex_base.components.component import Component, field +from reflex_base.vars.base import Var + +LiteralSize = Literal["sm", "md", "lg", "xl"] +LiteralVariant = Literal["solid", "outline", "ghost"] +LiteralOrientation = Literal["horizontal", "vertical"] + + +class ThemedComponent(Component): + """A component with literal-typed props.""" + + # The size of the component. + size: Var[LiteralSize] = field(doc="Component size.") + + # The variant style. + variant: Var[LiteralVariant] = field(doc="Visual variant.") + + orientation: Var[LiteralOrientation] + + # A direct Literal annotation without alias. + color_scheme: Var[Literal["red", "green", "blue"]] diff --git a/tests/units/reflex_base/utils/pyi_generator/dataset/module_level.py b/tests/units/reflex_base/utils/pyi_generator/dataset/module_level.py new file mode 100644 index 00000000000..376fd167dd6 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/module_level.py @@ -0,0 +1,48 @@ +"""Module with module-level functions, constants, and type aliases. + +This module tests: +- Module-level function body blanking +- Module-level annotated assignments (value blanked) +- Module-level non-annotated assignments (removed) +- Type alias preservation +- Combined: component + module-level items +""" + +from typing import Literal + +from reflex_base.components.component import Component, field +from reflex_base.vars.base import Var + +LiteralMode = Literal["light", "dark", "system"] + +# A module-level constant (non-annotated, should be removed). +DEFAULT_TIMEOUT = 30 + +# Annotated module-level var (value should be blanked). +current_mode: LiteralMode = "system" + + +def helper_function(x: int, y: int) -> int: + """Add two numbers. + + Args: + x: First number. + y: Second number. + + Returns: + The sum. + """ + return x + y + + +def another_helper() -> None: + """Do nothing important.""" + print("side effect") + + +class ModuleComponent(Component): + """A component defined alongside module-level items.""" + + mode: Var[LiteralMode] = field(doc="The display mode.") + + timeout: Var[int] = field(doc="Timeout in seconds.") diff --git a/tests/units/reflex_base/utils/pyi_generator/dataset/namespace_component.py b/tests/units/reflex_base/utils/pyi_generator/dataset/namespace_component.py new file mode 100644 index 00000000000..f7ce43da7ed --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/namespace_component.py @@ -0,0 +1,43 @@ +"""Components using ComponentNamespace pattern. + +This module tests: +- ComponentNamespace with __call__ = staticmethod(SomeComponent.create) +- Multiple components in the same module +- Namespace with staticmethod assignments +- Module-level namespace instance assignment +""" + +from reflex_base.components.component import Component, ComponentNamespace, field +from reflex_base.vars.base import Var + + +class DialogRoot(Component): + """The root dialog component.""" + + # Whether the dialog is open. + is_open: Var[bool] = field(doc="Whether the dialog is open.") + + +class DialogContent(Component): + """The dialog content component.""" + + # Whether to force mount the content. + force_mount: Var[bool] = field(doc="Whether to force mount.") + + +class DialogTitle(Component): + """The dialog title component.""" + + # The title text. + title_text: Var[str] + + +class Dialog(ComponentNamespace): + """Dialog components namespace.""" + + root = __call__ = staticmethod(DialogRoot.create) + content = staticmethod(DialogContent.create) + title = staticmethod(DialogTitle.create) + + +dialog = Dialog() diff --git a/tests/units/reflex_base/utils/pyi_generator/dataset/simple_component.py b/tests/units/reflex_base/utils/pyi_generator/dataset/simple_component.py new file mode 100644 index 00000000000..7687d780a07 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/simple_component.py @@ -0,0 +1,58 @@ +"""A simple component with basic props. + +This module tests: +- Basic component stub generation +- Props with various simple types (str, int, bool, float) +- Default event handlers inherited from Component +- Props with doc strings (via field(doc=...)) +- Props with comment-based docs (# comment above prop) +- Module docstring removal in stubs +- visit_Assign: assignment to `Any` is preserved +- visit_Assign: non-annotated assignments are removed +""" + +from typing import Any + +from reflex_base.components.component import Component, field +from reflex_base.vars.base import Var + +# Assignment to Any should be preserved in the stub. +SomeType = Any + +# A regular non-annotated assignment should be removed. +SOME_CONSTANT = 42 + + +class SimpleComponent(Component): + """A simple test component with basic prop types.""" + + # The title displayed on the component. + title: Var[str] + + # The count to display. + count: Var[int] + + is_active: Var[bool] = field(doc="Whether the component is active.") + + opacity: Var[float] = field(doc="The opacity of the component.") + + label: Var[str] = field( + default=Var.create("default"), + doc="An optional label with a default value.", + ) + + def _private_method(self): + """This should not appear in the stub. + + Returns: + A string indicating this is a private method. + """ + return "private" + + def public_helper(self) -> str: + """A public method that should have its body blanked out. + + Returns: + A string indicating this is a public helper method. + """ + return "hello" diff --git a/tests/units/reflex_base/utils/pyi_generator/dataset/staticmethod_namespace.py b/tests/units/reflex_base/utils/pyi_generator/dataset/staticmethod_namespace.py new file mode 100644 index 00000000000..3f5fc00f725 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/staticmethod_namespace.py @@ -0,0 +1,42 @@ +"""Namespace with staticmethod __call__ that is not Component.create. + +This module tests: +- _generate_staticmethod_call_functiondef path +- Namespace __call__ with custom function (not wrapping .create) +- The fallback where __call__.__func__.__name__ != "create" +""" + +from reflex_base.components.component import Component, ComponentNamespace, field +from reflex_base.event import EventSpec +from reflex_base.vars.base import Var + + +class NotifyComponent(Component): + """A notification component.""" + + message: Var[str] = field(doc="The notification message.") + + level: Var[str] = field(doc="The notification level.") + + +def send_notification(message: str, level: str = "info") -> EventSpec: + """Send a notification event. + + Args: + message: The message to send. + level: The notification level. + + Returns: + The event spec. + """ + return EventSpec() # type: ignore[call-arg] + + +class Notify(ComponentNamespace): + """Notification namespace.""" + + component = staticmethod(NotifyComponent.create) + __call__ = staticmethod(send_notification) + + +notify = Notify() diff --git a/tests/units/reflex_base/utils/pyi_generator/dataset/string_event_annotations.py b/tests/units/reflex_base/utils/pyi_generator/dataset/string_event_annotations.py new file mode 100644 index 00000000000..f1e3bd61189 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/string_event_annotations.py @@ -0,0 +1,63 @@ +"""Component with string-based tuple return annotations on event handlers. + +This module tests: +- figure_out_return_type with string-based "tuple[...]" annotation +- The string parsing path for event handler signatures +- Empty tuple[()], single arg tuple[str], multi-arg tuple[str, int] +""" + +from reflex_base.components.component import Component, field +from reflex_base.event import EventHandler +from reflex_base.vars.base import Var + + +def on_empty_handler() -> "tuple[()]": + """Handler returning empty tuple. + + Returns: + An empty tuple. + """ + return () + + +def on_string_handler(value: Var[str]) -> "tuple[Var[str]]": + """Handler returning a single string arg. + + Args: + value: The string value from the event. + + Returns: + A tuple containing the string value. + """ + return (value,) + + +def on_multi_handler(name: Var[str], age: Var[int]) -> "tuple[Var[str], Var[int]]": + """Handler returning multiple args. + + Args: + name: The name from the event. + age: The age from the event. + + Returns: + A tuple containing the name and age. + """ + return (name, age) + + +class StringAnnotationComponent(Component): + """A component with string-annotated event handlers.""" + + value: Var[str] + + on_empty: EventHandler[on_empty_handler] = field( + doc="Fires with no args.", + ) + + on_string: EventHandler[on_string_handler] = field( + doc="Fires with a string.", + ) + + on_multi: EventHandler[on_multi_handler] = field( + doc="Fires with name and age.", + ) diff --git a/tests/units/reflex_base/utils/pyi_generator/dataset/sub_package/__init__.py b/tests/units/reflex_base/utils/pyi_generator/dataset/sub_package/__init__.py new file mode 100644 index 00000000000..a3f4ee8e02b --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/sub_package/__init__.py @@ -0,0 +1,15 @@ +"""A sub-package with lazy loading. + +This tests __init__.py stub generation with _SUBMOD_ATTRS. +""" + +from reflex_base.utils import lazy_loader + +_SUBMOD_ATTRS: dict[str, list[str]] = { + "widget": ["SubWidget"], +} + +__getattr__, __dir__, __all__ = lazy_loader.attach( + __name__, + submod_attrs=_SUBMOD_ATTRS, +) diff --git a/tests/units/reflex_base/utils/pyi_generator/dataset/sub_package/widget.py b/tests/units/reflex_base/utils/pyi_generator/dataset/sub_package/widget.py new file mode 100644 index 00000000000..d5147be5034 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/sub_package/widget.py @@ -0,0 +1,13 @@ +"""A widget component for the sub_package. + +This is a simple module providing a component for the __init__.py lazy loader test. +""" + +from reflex_base.components.component import Component, field +from reflex_base.vars.base import Var + + +class SubWidget(Component): + """A widget in the sub package.""" + + name: Var[str] = field(doc="Widget name.") diff --git a/tests/units/reflex_base/utils/pyi_generator/dataset/typed_event_handlers.py b/tests/units/reflex_base/utils/pyi_generator/dataset/typed_event_handlers.py new file mode 100644 index 00000000000..ed436acbc73 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/typed_event_handlers.py @@ -0,0 +1,77 @@ +"""Components with various event handler signatures. + +This module tests: +- Custom event handlers with typed tuple returns +- passthrough_event_spec usage +- Multiple event specs (Sequence of specs) +- Event handlers with no args (no_args_event_spec) +- Event handler with multi-arg tuples +- String-based tuple return annotations +""" + +from reflex_base.components.component import Component, field +from reflex_base.event import EventHandler, passthrough_event_spec +from reflex_base.vars.base import Var + + +def on_value_change_handler(value: Var[str]) -> tuple[Var[str]]: + """Event spec for a string value change. + + Args: + value: The new value. + + Returns: + The value tuple. + """ + return (value,) + + +def on_pair_change_handler(key: Var[str], value: Var[int]) -> tuple[Var[str], Var[int]]: + """Event spec for a key-value pair change. + + Args: + key: The key. + value: The value. + + Returns: + The key-value tuple. + """ + return (key, value) + + +class EventComponent(Component): + """A component with various event handler types.""" + + # A simple string value prop. + value: Var[str] + + # Custom event handler with typed return. + on_value_change: EventHandler[on_value_change_handler] = field( + doc="Fired when the value changes.", + ) + + # Event handler with multiple return args. + on_pair_change: EventHandler[on_pair_change_handler] = field( + doc="Fired when a key-value pair changes.", + ) + + # Passthrough event spec with single type. + on_item_select: EventHandler[passthrough_event_spec(str)] = field( + doc="Fired when an item is selected.", + ) + + # Passthrough event spec with multiple types. + on_range_change: EventHandler[passthrough_event_spec(int, int)] = field( + doc="Fired when the range changes.", + ) + + +class MultiSpecComponent(Component): + """A component where event triggers have multiple possible specs.""" + + value: Var[str] + + # Multiple event specs: can fire with no args or with a string value. + on_open_change: EventHandler[passthrough_event_spec(bool)] = field( + doc="Fired when the open state changes.", + ) diff --git a/tests/units/reflex_base/utils/pyi_generator/dataset/var_types.py b/tests/units/reflex_base/utils/pyi_generator/dataset/var_types.py new file mode 100644 index 00000000000..dc858c690d1 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/var_types.py @@ -0,0 +1,45 @@ +"""Component with various Var[T] prop types and edge cases. + +This module tests: +- Var[T] expansion: Var[str] -> Var[str] | str +- Var with Union args: Var[str | int] -> Var[str | int] | str | int +- Complex nested types: Var[list[str]], Var[dict[str, Any]], Var[list[dict[str, Any]]] +- Component with no custom props (just inherited defaults) +- Component with only event handlers (no data props) +""" + +from typing import Any + +from reflex_base.components.component import Component, field +from reflex_base.event import EventHandler, passthrough_event_spec +from reflex_base.vars.base import Var + + +class EmptyComponent(Component): + """A component with no custom props at all.""" + + +class EventOnlyComponent(Component): + """A component with only custom event handlers, no data props.""" + + on_toggle: EventHandler[passthrough_event_spec(bool)] = field( + doc="Fired when toggled.", + ) + + +class VarTypesComponent(Component): + """A component exercising various Var type expansions.""" + + # Basic Var types. + name: Var[str] + count: Var[int] + ratio: Var[float] + flag: Var[bool] + + # Union inside Var. + value: Var[str | int] = field(doc="A string or int value.") + + # Nested generic Var types. + items: Var[list[str]] = field(doc="A list of string items.") + metadata: Var[dict[str, Any]] = field(doc="Metadata dictionary.") + nested: Var[list[dict[str, Any]]] = field(doc="Nested structures.") diff --git a/tests/units/reflex_base/utils/pyi_generator/golden/.gitignore b/tests/units/reflex_base/utils/pyi_generator/golden/.gitignore new file mode 100644 index 00000000000..c9c1cd6e861 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/golden/.gitignore @@ -0,0 +1 @@ +!*.pyi \ No newline at end of file diff --git a/tests/units/reflex_base/utils/pyi_generator/golden/classvar_and_private.pyi b/tests/units/reflex_base/utils/pyi_generator/golden/classvar_and_private.pyi new file mode 100644 index 00000000000..d459b1b82bf --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/golden/classvar_and_private.pyi @@ -0,0 +1,84 @@ +"""Stub file for """ + +# ------------------- DO NOT EDIT ---------------------- +# This file was generated by `reflex/utils/pyi_generator.py`! +# ------------------------------------------------------ +from collections.abc import Mapping, Sequence +from typing import Any + +from reflex_base.components.component import Component +from reflex_base.event import EventType, PointerEventInfo +from reflex_base.vars.base import Var +from reflex_components_core.core.breakpoints import Breakpoints + +class StrictComponent(Component): + def render_item(self) -> str: ... + @classmethod + def create( + cls, + *children, + _internal_counter: int | None = None, + visible_prop: Var[str] | str | None = None, + size: Var[int] | int | None = None, + style: Sequence[Mapping[str, Any]] + | Mapping[str, Any] + | Var[Mapping[str, Any]] + | Breakpoints + | None = None, + key: Any | None = None, + id: Any | None = None, + ref: Var | None = None, + class_name: Any | None = None, + custom_attrs: dict[str, Any | Var] | None = None, + on_blur: EventType[()] | None = None, + on_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_context_menu: EventType[()] | EventType[PointerEventInfo] | None = None, + on_double_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_focus: EventType[()] | None = None, + on_mount: EventType[()] | None = None, + on_mouse_down: EventType[()] | None = None, + on_mouse_enter: EventType[()] | None = None, + on_mouse_leave: EventType[()] | None = None, + on_mouse_move: EventType[()] | None = None, + on_mouse_out: EventType[()] | None = None, + on_mouse_over: EventType[()] | None = None, + on_mouse_up: EventType[()] | None = None, + on_scroll: EventType[()] | None = None, + on_scroll_end: EventType[()] | None = None, + on_unmount: EventType[()] | None = None, + **props, + ) -> StrictComponent: + """Create the component. + + Args: + *children: The children of the component. + _internal_counter: no description + visible_prop: A prop visible in the stub. + size: The size of the component. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + ref: The Var to pass as the ref to the component. + class_name: The class name for the component. + custom_attrs: Attributes passed directly to the component. + on_focus: Fired when the element (or some element inside of it) receives focus. For example, it is called when the user clicks on a text input. + on_blur: Fired when focus has left the element (or left some element inside of it). For example, it is called when the user clicks outside of a focused text input. + on_click: Fired when the user clicks on an element. For example, it's called when the user clicks on a button. + on_context_menu: Fired when the user right-clicks on an element. + on_double_click: Fired when the user double-clicks on an element. + on_mouse_down: Fired when the user presses a mouse button on an element. + on_mouse_enter: Fired when the mouse pointer enters the element. + on_mouse_leave: Fired when the mouse pointer leaves the element. + on_mouse_move: Fired when the mouse pointer moves over the element. + on_mouse_out: Fired when the mouse pointer moves out of the element. + on_mouse_over: Fired when the mouse pointer moves onto the element. + on_mouse_up: Fired when the user releases a mouse button on an element. + on_scroll: Fired when the user scrolls the element. + on_scroll_end: Fired when scrolling ends on the element. + on_mount: Fired when the component is mounted to the page. + on_unmount: Fired when the component is removed from the page. Only called during navigation, not on page refresh. + **props: The props of the component. + + Returns: + The component. + """ diff --git a/tests/units/reflex_base/utils/pyi_generator/golden/custom_create.pyi b/tests/units/reflex_base/utils/pyi_generator/golden/custom_create.pyi new file mode 100644 index 00000000000..3f60cc9c1f2 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/golden/custom_create.pyi @@ -0,0 +1,83 @@ +"""Stub file for """ + +# ------------------- DO NOT EDIT ---------------------- +# This file was generated by `reflex/utils/pyi_generator.py`! +# ------------------------------------------------------ +from collections.abc import Mapping, Sequence +from typing import Any + +from reflex_base.components.component import Component +from reflex_base.event import EventType, PointerEventInfo +from reflex_base.vars.base import Var +from reflex_components_core.core.breakpoints import Breakpoints + +class CustomCreateComponent(Component): + @classmethod + def create( + cls, + *children, + custom_kwarg: str | None = "default", + label: Var[str] | str | None = None, + amount: Var[int] | int | None = None, + style: Sequence[Mapping[str, Any]] + | Mapping[str, Any] + | Var[Mapping[str, Any]] + | Breakpoints + | None = None, + key: Any | None = None, + id: Any | None = None, + ref: Var | None = None, + class_name: Any | None = None, + custom_attrs: dict[str, Any | Var] | None = None, + on_blur: EventType[()] | None = None, + on_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_context_menu: EventType[()] | EventType[PointerEventInfo] | None = None, + on_double_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_focus: EventType[()] | None = None, + on_mount: EventType[()] | None = None, + on_mouse_down: EventType[()] | None = None, + on_mouse_enter: EventType[()] | None = None, + on_mouse_leave: EventType[()] | None = None, + on_mouse_move: EventType[()] | None = None, + on_mouse_out: EventType[()] | None = None, + on_mouse_over: EventType[()] | None = None, + on_mouse_up: EventType[()] | None = None, + on_scroll: EventType[()] | None = None, + on_scroll_end: EventType[()] | None = None, + on_unmount: EventType[()] | None = None, + **props, + ) -> CustomCreateComponent: + """Create a custom component with extra kwargs. + + Args: + *children: The child components. + custom_kwarg: A custom keyword argument. + label: Display label. + amount: The amount. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + ref: The Var to pass as the ref to the component. + class_name: The class name for the component. + custom_attrs: Attributes passed directly to the component. + on_focus: Fired when the element (or some element inside of it) receives focus. For example, it is called when the user clicks on a text input. + on_blur: Fired when focus has left the element (or left some element inside of it). For example, it is called when the user clicks outside of a focused text input. + on_click: Fired when the user clicks on an element. For example, it's called when the user clicks on a button. + on_context_menu: Fired when the user right-clicks on an element. + on_double_click: Fired when the user double-clicks on an element. + on_mouse_down: Fired when the user presses a mouse button on an element. + on_mouse_enter: Fired when the mouse pointer enters the element. + on_mouse_leave: Fired when the mouse pointer leaves the element. + on_mouse_move: Fired when the mouse pointer moves over the element. + on_mouse_out: Fired when the mouse pointer moves out of the element. + on_mouse_over: Fired when the mouse pointer moves onto the element. + on_mouse_up: Fired when the user releases a mouse button on an element. + on_scroll: Fired when the user scrolls the element. + on_scroll_end: Fired when scrolling ends on the element. + on_mount: Fired when the component is mounted to the page. + on_unmount: Fired when the component is removed from the page. Only called during navigation, not on page refresh. + **props: Additional properties. + + Returns: + The component instance. + """ diff --git a/tests/units/reflex_base/utils/pyi_generator/golden/inheritance.pyi b/tests/units/reflex_base/utils/pyi_generator/golden/inheritance.pyi new file mode 100644 index 00000000000..ea1ad91d040 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/golden/inheritance.pyi @@ -0,0 +1,229 @@ +"""Stub file for """ + +# ------------------- DO NOT EDIT ---------------------- +# This file was generated by `reflex/utils/pyi_generator.py`! +# ------------------------------------------------------ +from collections.abc import Mapping, Sequence +from typing import Any + +from reflex_base.components.component import Component +from reflex_base.event import EventType, PointerEventInfo +from reflex_base.vars.base import Var +from reflex_components_core.core.breakpoints import Breakpoints + +class BaseWidget(Component): + @classmethod + def create( + cls, + *children, + color: Var[str] | str | None = None, + disabled: Var[bool] | bool | None = None, + style: Sequence[Mapping[str, Any]] + | Mapping[str, Any] + | Var[Mapping[str, Any]] + | Breakpoints + | None = None, + key: Any | None = None, + id: Any | None = None, + ref: Var | None = None, + class_name: Any | None = None, + custom_attrs: dict[str, Any | Var] | None = None, + on_blur: EventType[()] | None = None, + on_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_context_menu: EventType[()] | EventType[PointerEventInfo] | None = None, + on_double_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_focus: EventType[()] | None = None, + on_mount: EventType[()] | None = None, + on_mouse_down: EventType[()] | None = None, + on_mouse_enter: EventType[()] | None = None, + on_mouse_leave: EventType[()] | None = None, + on_mouse_move: EventType[()] | None = None, + on_mouse_out: EventType[()] | None = None, + on_mouse_over: EventType[()] | None = None, + on_mouse_up: EventType[()] | None = None, + on_scroll: EventType[()] | None = None, + on_scroll_end: EventType[()] | None = None, + on_unmount: EventType[()] | None = None, + **props, + ) -> BaseWidget: + """Create the component. + + Args: + *children: The children of the component. + color: The widget color. + disabled: Whether the widget is disabled. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + ref: The Var to pass as the ref to the component. + class_name: The class name for the component. + custom_attrs: Attributes passed directly to the component. + on_focus: Fired when the element (or some element inside of it) receives focus. For example, it is called when the user clicks on a text input. + on_blur: Fired when focus has left the element (or left some element inside of it). For example, it is called when the user clicks outside of a focused text input. + on_click: Fired when the user clicks on an element. For example, it's called when the user clicks on a button. + on_context_menu: Fired when the user right-clicks on an element. + on_double_click: Fired when the user double-clicks on an element. + on_mouse_down: Fired when the user presses a mouse button on an element. + on_mouse_enter: Fired when the mouse pointer enters the element. + on_mouse_leave: Fired when the mouse pointer leaves the element. + on_mouse_move: Fired when the mouse pointer moves over the element. + on_mouse_out: Fired when the mouse pointer moves out of the element. + on_mouse_over: Fired when the mouse pointer moves onto the element. + on_mouse_up: Fired when the user releases a mouse button on an element. + on_scroll: Fired when the user scrolls the element. + on_scroll_end: Fired when scrolling ends on the element. + on_mount: Fired when the component is mounted to the page. + on_unmount: Fired when the component is removed from the page. Only called during navigation, not on page refresh. + **props: The props of the component. + + Returns: + The component. + """ + +class InteractiveWidget(BaseWidget): + @classmethod + def create( + cls, + *children, + placeholder: Var[str] | str | None = None, + color: Var[str] | str | None = None, + disabled: Var[bool] | bool | None = None, + style: Sequence[Mapping[str, Any]] + | Mapping[str, Any] + | Var[Mapping[str, Any]] + | Breakpoints + | None = None, + key: Any | None = None, + id: Any | None = None, + ref: Var | None = None, + class_name: Any | None = None, + custom_attrs: dict[str, Any | Var] | None = None, + on_blur: EventType[()] | None = None, + on_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_context_menu: EventType[()] | EventType[PointerEventInfo] | None = None, + on_double_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_focus: EventType[()] | None = None, + on_mount: EventType[()] | None = None, + on_mouse_down: EventType[()] | None = None, + on_mouse_enter: EventType[()] | None = None, + on_mouse_leave: EventType[()] | None = None, + on_mouse_move: EventType[()] | None = None, + on_mouse_out: EventType[()] | None = None, + on_mouse_over: EventType[()] | None = None, + on_mouse_up: EventType[()] | None = None, + on_scroll: EventType[()] | None = None, + on_scroll_end: EventType[()] | None = None, + on_unmount: EventType[()] | None = None, + on_value_change: EventType[()] | EventType[str] | None = None, + **props, + ) -> InteractiveWidget: + """Create the component. + + Args: + *children: The children of the component. + placeholder: Placeholder text. + color: The widget color. + disabled: Whether the widget is disabled. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + ref: The Var to pass as the ref to the component. + class_name: The class name for the component. + custom_attrs: Attributes passed directly to the component. + on_focus: Fired when the element (or some element inside of it) receives focus. For example, it is called when the user clicks on a text input. + on_blur: Fired when focus has left the element (or left some element inside of it). For example, it is called when the user clicks outside of a focused text input. + on_click: Fired when the user clicks on an element. For example, it's called when the user clicks on a button. + on_context_menu: Fired when the user right-clicks on an element. + on_double_click: Fired when the user double-clicks on an element. + on_mouse_down: Fired when the user presses a mouse button on an element. + on_mouse_enter: Fired when the mouse pointer enters the element. + on_mouse_leave: Fired when the mouse pointer leaves the element. + on_mouse_move: Fired when the mouse pointer moves over the element. + on_mouse_out: Fired when the mouse pointer moves out of the element. + on_mouse_over: Fired when the mouse pointer moves onto the element. + on_mouse_up: Fired when the user releases a mouse button on an element. + on_scroll: Fired when the user scrolls the element. + on_scroll_end: Fired when scrolling ends on the element. + on_mount: Fired when the component is mounted to the page. + on_unmount: Fired when the component is removed from the page. Only called during navigation, not on page refresh. + on_value_change: Fired when value changes. + **props: The props of the component. + + Returns: + The component. + """ + +class FancyWidget(InteractiveWidget): + @classmethod + def create( + cls, + *children, + border_radius: Var[str] | str | None = None, + placeholder: Var[str] | str | None = None, + color: Var[str] | str | None = None, + disabled: Var[bool] | bool | None = None, + style: Sequence[Mapping[str, Any]] + | Mapping[str, Any] + | Var[Mapping[str, Any]] + | Breakpoints + | None = None, + key: Any | None = None, + id: Any | None = None, + ref: Var | None = None, + class_name: Any | None = None, + custom_attrs: dict[str, Any | Var] | None = None, + on_blur: EventType[()] | None = None, + on_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_context_menu: EventType[()] | EventType[PointerEventInfo] | None = None, + on_double_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_focus: EventType[()] | None = None, + on_mount: EventType[()] | None = None, + on_mouse_down: EventType[()] | None = None, + on_mouse_enter: EventType[()] | None = None, + on_mouse_leave: EventType[()] | None = None, + on_mouse_move: EventType[()] | None = None, + on_mouse_out: EventType[()] | None = None, + on_mouse_over: EventType[()] | None = None, + on_mouse_up: EventType[()] | None = None, + on_scroll: EventType[()] | None = None, + on_scroll_end: EventType[()] | None = None, + on_unmount: EventType[()] | None = None, + on_value_change: EventType[()] | EventType[str] | None = None, + **props, + ) -> FancyWidget: + """Create the component. + + Args: + *children: The children of the component. + border_radius: The border radius. + placeholder: Placeholder text. + color: The widget color. + disabled: Whether the widget is disabled. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + ref: The Var to pass as the ref to the component. + class_name: The class name for the component. + custom_attrs: Attributes passed directly to the component. + on_focus: Fired when the element (or some element inside of it) receives focus. For example, it is called when the user clicks on a text input. + on_blur: Fired when focus has left the element (or left some element inside of it). For example, it is called when the user clicks outside of a focused text input. + on_click: Fired when the user clicks on an element. For example, it's called when the user clicks on a button. + on_context_menu: Fired when the user right-clicks on an element. + on_double_click: Fired when the user double-clicks on an element. + on_mouse_down: Fired when the user presses a mouse button on an element. + on_mouse_enter: Fired when the mouse pointer enters the element. + on_mouse_leave: Fired when the mouse pointer leaves the element. + on_mouse_move: Fired when the mouse pointer moves over the element. + on_mouse_out: Fired when the mouse pointer moves out of the element. + on_mouse_over: Fired when the mouse pointer moves onto the element. + on_mouse_up: Fired when the user releases a mouse button on an element. + on_scroll: Fired when the user scrolls the element. + on_scroll_end: Fired when scrolling ends on the element. + on_mount: Fired when the component is mounted to the page. + on_unmount: Fired when the component is removed from the page. Only called during navigation, not on page refresh. + on_value_change: Fired when value changes. + **props: The props of the component. + + Returns: + The component. + """ diff --git a/tests/units/reflex_base/utils/pyi_generator/golden/literal_types.pyi b/tests/units/reflex_base/utils/pyi_generator/golden/literal_types.pyi new file mode 100644 index 00000000000..e051e7fbb5e --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/golden/literal_types.pyi @@ -0,0 +1,97 @@ +"""Stub file for """ + +# ------------------- DO NOT EDIT ---------------------- +# This file was generated by `reflex/utils/pyi_generator.py`! +# ------------------------------------------------------ +from collections.abc import Mapping, Sequence +from typing import Any, Literal + +from reflex_base.components.component import Component +from reflex_base.event import EventType, PointerEventInfo +from reflex_base.vars.base import Var +from reflex_components_core.core.breakpoints import Breakpoints + +LiteralSize = Literal["sm", "md", "lg", "xl"] +LiteralVariant = Literal["solid", "outline", "ghost"] +LiteralOrientation = Literal["horizontal", "vertical"] + +class ThemedComponent(Component): + @classmethod + def create( + cls, + *children, + size: Literal["lg", "md", "sm", "xl"] + | Var[Literal["lg", "md", "sm", "xl"]] + | None = None, + variant: Literal["ghost", "outline", "solid"] + | Var[Literal["ghost", "outline", "solid"]] + | None = None, + orientation: Literal["horizontal", "vertical"] + | Var[Literal["horizontal", "vertical"]] + | None = None, + color_scheme: Literal["blue", "green", "red"] + | Var[Literal["blue", "green", "red"]] + | None = None, + style: Sequence[Mapping[str, Any]] + | Mapping[str, Any] + | Var[Mapping[str, Any]] + | Breakpoints + | None = None, + key: Any | None = None, + id: Any | None = None, + ref: Var | None = None, + class_name: Any | None = None, + custom_attrs: dict[str, Any | Var] | None = None, + on_blur: EventType[()] | None = None, + on_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_context_menu: EventType[()] | EventType[PointerEventInfo] | None = None, + on_double_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_focus: EventType[()] | None = None, + on_mount: EventType[()] | None = None, + on_mouse_down: EventType[()] | None = None, + on_mouse_enter: EventType[()] | None = None, + on_mouse_leave: EventType[()] | None = None, + on_mouse_move: EventType[()] | None = None, + on_mouse_out: EventType[()] | None = None, + on_mouse_over: EventType[()] | None = None, + on_mouse_up: EventType[()] | None = None, + on_scroll: EventType[()] | None = None, + on_scroll_end: EventType[()] | None = None, + on_unmount: EventType[()] | None = None, + **props, + ) -> ThemedComponent: + """Create the component. + + Args: + *children: The children of the component. + size: Component size. + variant: Visual variant. + orientation: no description + color_scheme: A direct Literal annotation without alias. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + ref: The Var to pass as the ref to the component. + class_name: The class name for the component. + custom_attrs: Attributes passed directly to the component. + on_focus: Fired when the element (or some element inside of it) receives focus. For example, it is called when the user clicks on a text input. + on_blur: Fired when focus has left the element (or left some element inside of it). For example, it is called when the user clicks outside of a focused text input. + on_click: Fired when the user clicks on an element. For example, it's called when the user clicks on a button. + on_context_menu: Fired when the user right-clicks on an element. + on_double_click: Fired when the user double-clicks on an element. + on_mouse_down: Fired when the user presses a mouse button on an element. + on_mouse_enter: Fired when the mouse pointer enters the element. + on_mouse_leave: Fired when the mouse pointer leaves the element. + on_mouse_move: Fired when the mouse pointer moves over the element. + on_mouse_out: Fired when the mouse pointer moves out of the element. + on_mouse_over: Fired when the mouse pointer moves onto the element. + on_mouse_up: Fired when the user releases a mouse button on an element. + on_scroll: Fired when the user scrolls the element. + on_scroll_end: Fired when scrolling ends on the element. + on_mount: Fired when the component is mounted to the page. + on_unmount: Fired when the component is removed from the page. Only called during navigation, not on page refresh. + **props: The props of the component. + + Returns: + The component. + """ diff --git a/tests/units/reflex_base/utils/pyi_generator/golden/module_level.pyi b/tests/units/reflex_base/utils/pyi_generator/golden/module_level.pyi new file mode 100644 index 00000000000..7f7eb1e048e --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/golden/module_level.pyi @@ -0,0 +1,90 @@ +"""Stub file for """ + +# ------------------- DO NOT EDIT ---------------------- +# This file was generated by `reflex/utils/pyi_generator.py`! +# ------------------------------------------------------ +from collections.abc import Mapping, Sequence +from typing import Any, Literal + +from reflex_base.components.component import Component +from reflex_base.event import EventType, PointerEventInfo +from reflex_base.vars.base import Var +from reflex_components_core.core.breakpoints import Breakpoints + +LiteralMode = Literal["light", "dark", "system"] +DEFAULT_TIMEOUT = 30 +current_mode: LiteralMode + +def helper_function(x: int, y: int) -> int: ... +def another_helper() -> None: ... + +class ModuleComponent(Component): + @classmethod + def create( + cls, + *children, + mode: Literal["dark", "light", "system"] + | Var[Literal["dark", "light", "system"]] + | None = None, + timeout: Var[int] | int | None = None, + style: Sequence[Mapping[str, Any]] + | Mapping[str, Any] + | Var[Mapping[str, Any]] + | Breakpoints + | None = None, + key: Any | None = None, + id: Any | None = None, + ref: Var | None = None, + class_name: Any | None = None, + custom_attrs: dict[str, Any | Var] | None = None, + on_blur: EventType[()] | None = None, + on_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_context_menu: EventType[()] | EventType[PointerEventInfo] | None = None, + on_double_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_focus: EventType[()] | None = None, + on_mount: EventType[()] | None = None, + on_mouse_down: EventType[()] | None = None, + on_mouse_enter: EventType[()] | None = None, + on_mouse_leave: EventType[()] | None = None, + on_mouse_move: EventType[()] | None = None, + on_mouse_out: EventType[()] | None = None, + on_mouse_over: EventType[()] | None = None, + on_mouse_up: EventType[()] | None = None, + on_scroll: EventType[()] | None = None, + on_scroll_end: EventType[()] | None = None, + on_unmount: EventType[()] | None = None, + **props, + ) -> ModuleComponent: + """Create the component. + + Args: + *children: The children of the component. + mode: The display mode. + timeout: Timeout in seconds. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + ref: The Var to pass as the ref to the component. + class_name: The class name for the component. + custom_attrs: Attributes passed directly to the component. + on_focus: Fired when the element (or some element inside of it) receives focus. For example, it is called when the user clicks on a text input. + on_blur: Fired when focus has left the element (or left some element inside of it). For example, it is called when the user clicks outside of a focused text input. + on_click: Fired when the user clicks on an element. For example, it's called when the user clicks on a button. + on_context_menu: Fired when the user right-clicks on an element. + on_double_click: Fired when the user double-clicks on an element. + on_mouse_down: Fired when the user presses a mouse button on an element. + on_mouse_enter: Fired when the mouse pointer enters the element. + on_mouse_leave: Fired when the mouse pointer leaves the element. + on_mouse_move: Fired when the mouse pointer moves over the element. + on_mouse_out: Fired when the mouse pointer moves out of the element. + on_mouse_over: Fired when the mouse pointer moves onto the element. + on_mouse_up: Fired when the user releases a mouse button on an element. + on_scroll: Fired when the user scrolls the element. + on_scroll_end: Fired when scrolling ends on the element. + on_mount: Fired when the component is mounted to the page. + on_unmount: Fired when the component is removed from the page. Only called during navigation, not on page refresh. + **props: The props of the component. + + Returns: + The component. + """ diff --git a/tests/units/reflex_base/utils/pyi_generator/golden/namespace_component.pyi b/tests/units/reflex_base/utils/pyi_generator/golden/namespace_component.pyi new file mode 100644 index 00000000000..7b34e87efe5 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/golden/namespace_component.pyi @@ -0,0 +1,285 @@ +"""Stub file for """ + +# ------------------- DO NOT EDIT ---------------------- +# This file was generated by `reflex/utils/pyi_generator.py`! +# ------------------------------------------------------ +from collections.abc import Mapping, Sequence +from typing import Any + +from reflex_base.components.component import Component, ComponentNamespace +from reflex_base.event import EventType, PointerEventInfo +from reflex_base.vars.base import Var +from reflex_components_core.core.breakpoints import Breakpoints + +class DialogRoot(Component): + @classmethod + def create( + cls, + *children, + is_open: Var[bool] | bool | None = None, + style: Sequence[Mapping[str, Any]] + | Mapping[str, Any] + | Var[Mapping[str, Any]] + | Breakpoints + | None = None, + key: Any | None = None, + id: Any | None = None, + ref: Var | None = None, + class_name: Any | None = None, + custom_attrs: dict[str, Any | Var] | None = None, + on_blur: EventType[()] | None = None, + on_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_context_menu: EventType[()] | EventType[PointerEventInfo] | None = None, + on_double_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_focus: EventType[()] | None = None, + on_mount: EventType[()] | None = None, + on_mouse_down: EventType[()] | None = None, + on_mouse_enter: EventType[()] | None = None, + on_mouse_leave: EventType[()] | None = None, + on_mouse_move: EventType[()] | None = None, + on_mouse_out: EventType[()] | None = None, + on_mouse_over: EventType[()] | None = None, + on_mouse_up: EventType[()] | None = None, + on_scroll: EventType[()] | None = None, + on_scroll_end: EventType[()] | None = None, + on_unmount: EventType[()] | None = None, + **props, + ) -> DialogRoot: + """Create the component. + + Args: + *children: The children of the component. + is_open: Whether the dialog is open. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + ref: The Var to pass as the ref to the component. + class_name: The class name for the component. + custom_attrs: Attributes passed directly to the component. + on_focus: Fired when the element (or some element inside of it) receives focus. For example, it is called when the user clicks on a text input. + on_blur: Fired when focus has left the element (or left some element inside of it). For example, it is called when the user clicks outside of a focused text input. + on_click: Fired when the user clicks on an element. For example, it's called when the user clicks on a button. + on_context_menu: Fired when the user right-clicks on an element. + on_double_click: Fired when the user double-clicks on an element. + on_mouse_down: Fired when the user presses a mouse button on an element. + on_mouse_enter: Fired when the mouse pointer enters the element. + on_mouse_leave: Fired when the mouse pointer leaves the element. + on_mouse_move: Fired when the mouse pointer moves over the element. + on_mouse_out: Fired when the mouse pointer moves out of the element. + on_mouse_over: Fired when the mouse pointer moves onto the element. + on_mouse_up: Fired when the user releases a mouse button on an element. + on_scroll: Fired when the user scrolls the element. + on_scroll_end: Fired when scrolling ends on the element. + on_mount: Fired when the component is mounted to the page. + on_unmount: Fired when the component is removed from the page. Only called during navigation, not on page refresh. + **props: The props of the component. + + Returns: + The component. + """ + +class DialogContent(Component): + @classmethod + def create( + cls, + *children, + force_mount: Var[bool] | bool | None = None, + style: Sequence[Mapping[str, Any]] + | Mapping[str, Any] + | Var[Mapping[str, Any]] + | Breakpoints + | None = None, + key: Any | None = None, + id: Any | None = None, + ref: Var | None = None, + class_name: Any | None = None, + custom_attrs: dict[str, Any | Var] | None = None, + on_blur: EventType[()] | None = None, + on_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_context_menu: EventType[()] | EventType[PointerEventInfo] | None = None, + on_double_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_focus: EventType[()] | None = None, + on_mount: EventType[()] | None = None, + on_mouse_down: EventType[()] | None = None, + on_mouse_enter: EventType[()] | None = None, + on_mouse_leave: EventType[()] | None = None, + on_mouse_move: EventType[()] | None = None, + on_mouse_out: EventType[()] | None = None, + on_mouse_over: EventType[()] | None = None, + on_mouse_up: EventType[()] | None = None, + on_scroll: EventType[()] | None = None, + on_scroll_end: EventType[()] | None = None, + on_unmount: EventType[()] | None = None, + **props, + ) -> DialogContent: + """Create the component. + + Args: + *children: The children of the component. + force_mount: Whether to force mount. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + ref: The Var to pass as the ref to the component. + class_name: The class name for the component. + custom_attrs: Attributes passed directly to the component. + on_focus: Fired when the element (or some element inside of it) receives focus. For example, it is called when the user clicks on a text input. + on_blur: Fired when focus has left the element (or left some element inside of it). For example, it is called when the user clicks outside of a focused text input. + on_click: Fired when the user clicks on an element. For example, it's called when the user clicks on a button. + on_context_menu: Fired when the user right-clicks on an element. + on_double_click: Fired when the user double-clicks on an element. + on_mouse_down: Fired when the user presses a mouse button on an element. + on_mouse_enter: Fired when the mouse pointer enters the element. + on_mouse_leave: Fired when the mouse pointer leaves the element. + on_mouse_move: Fired when the mouse pointer moves over the element. + on_mouse_out: Fired when the mouse pointer moves out of the element. + on_mouse_over: Fired when the mouse pointer moves onto the element. + on_mouse_up: Fired when the user releases a mouse button on an element. + on_scroll: Fired when the user scrolls the element. + on_scroll_end: Fired when scrolling ends on the element. + on_mount: Fired when the component is mounted to the page. + on_unmount: Fired when the component is removed from the page. Only called during navigation, not on page refresh. + **props: The props of the component. + + Returns: + The component. + """ + +class DialogTitle(Component): + @classmethod + def create( + cls, + *children, + title_text: Var[str] | str | None = None, + style: Sequence[Mapping[str, Any]] + | Mapping[str, Any] + | Var[Mapping[str, Any]] + | Breakpoints + | None = None, + key: Any | None = None, + id: Any | None = None, + ref: Var | None = None, + class_name: Any | None = None, + custom_attrs: dict[str, Any | Var] | None = None, + on_blur: EventType[()] | None = None, + on_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_context_menu: EventType[()] | EventType[PointerEventInfo] | None = None, + on_double_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_focus: EventType[()] | None = None, + on_mount: EventType[()] | None = None, + on_mouse_down: EventType[()] | None = None, + on_mouse_enter: EventType[()] | None = None, + on_mouse_leave: EventType[()] | None = None, + on_mouse_move: EventType[()] | None = None, + on_mouse_out: EventType[()] | None = None, + on_mouse_over: EventType[()] | None = None, + on_mouse_up: EventType[()] | None = None, + on_scroll: EventType[()] | None = None, + on_scroll_end: EventType[()] | None = None, + on_unmount: EventType[()] | None = None, + **props, + ) -> DialogTitle: + """Create the component. + + Args: + *children: The children of the component. + title_text: The title text. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + ref: The Var to pass as the ref to the component. + class_name: The class name for the component. + custom_attrs: Attributes passed directly to the component. + on_focus: Fired when the element (or some element inside of it) receives focus. For example, it is called when the user clicks on a text input. + on_blur: Fired when focus has left the element (or left some element inside of it). For example, it is called when the user clicks outside of a focused text input. + on_click: Fired when the user clicks on an element. For example, it's called when the user clicks on a button. + on_context_menu: Fired when the user right-clicks on an element. + on_double_click: Fired when the user double-clicks on an element. + on_mouse_down: Fired when the user presses a mouse button on an element. + on_mouse_enter: Fired when the mouse pointer enters the element. + on_mouse_leave: Fired when the mouse pointer leaves the element. + on_mouse_move: Fired when the mouse pointer moves over the element. + on_mouse_out: Fired when the mouse pointer moves out of the element. + on_mouse_over: Fired when the mouse pointer moves onto the element. + on_mouse_up: Fired when the user releases a mouse button on an element. + on_scroll: Fired when the user scrolls the element. + on_scroll_end: Fired when scrolling ends on the element. + on_mount: Fired when the component is mounted to the page. + on_unmount: Fired when the component is removed from the page. Only called during navigation, not on page refresh. + **props: The props of the component. + + Returns: + The component. + """ + +class Dialog(ComponentNamespace): + root = staticmethod(DialogRoot.create) + content = staticmethod(DialogContent.create) + title = staticmethod(DialogTitle.create) + + @staticmethod + def __call__( + *children, + is_open: Var[bool] | bool | None = None, + style: Sequence[Mapping[str, Any]] + | Mapping[str, Any] + | Var[Mapping[str, Any]] + | Breakpoints + | None = None, + key: Any | None = None, + id: Any | None = None, + ref: Var | None = None, + class_name: Any | None = None, + custom_attrs: dict[str, Any | Var] | None = None, + on_blur: EventType[()] | None = None, + on_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_context_menu: EventType[()] | EventType[PointerEventInfo] | None = None, + on_double_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_focus: EventType[()] | None = None, + on_mount: EventType[()] | None = None, + on_mouse_down: EventType[()] | None = None, + on_mouse_enter: EventType[()] | None = None, + on_mouse_leave: EventType[()] | None = None, + on_mouse_move: EventType[()] | None = None, + on_mouse_out: EventType[()] | None = None, + on_mouse_over: EventType[()] | None = None, + on_mouse_up: EventType[()] | None = None, + on_scroll: EventType[()] | None = None, + on_scroll_end: EventType[()] | None = None, + on_unmount: EventType[()] | None = None, + **props, + ) -> DialogRoot: + """Create the component. + + Args: + *children: The children of the component. + is_open: Whether the dialog is open. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + ref: The Var to pass as the ref to the component. + class_name: The class name for the component. + custom_attrs: Attributes passed directly to the component. + on_focus: Fired when the element (or some element inside of it) receives focus. For example, it is called when the user clicks on a text input. + on_blur: Fired when focus has left the element (or left some element inside of it). For example, it is called when the user clicks outside of a focused text input. + on_click: Fired when the user clicks on an element. For example, it's called when the user clicks on a button. + on_context_menu: Fired when the user right-clicks on an element. + on_double_click: Fired when the user double-clicks on an element. + on_mouse_down: Fired when the user presses a mouse button on an element. + on_mouse_enter: Fired when the mouse pointer enters the element. + on_mouse_leave: Fired when the mouse pointer leaves the element. + on_mouse_move: Fired when the mouse pointer moves over the element. + on_mouse_out: Fired when the mouse pointer moves out of the element. + on_mouse_over: Fired when the mouse pointer moves onto the element. + on_mouse_up: Fired when the user releases a mouse button on an element. + on_scroll: Fired when the user scrolls the element. + on_scroll_end: Fired when scrolling ends on the element. + on_mount: Fired when the component is mounted to the page. + on_unmount: Fired when the component is removed from the page. Only called during navigation, not on page refresh. + **props: The props of the component. + + Returns: + The component. + """ + +dialog = Dialog() diff --git a/tests/units/reflex_base/utils/pyi_generator/golden/simple_component.pyi b/tests/units/reflex_base/utils/pyi_generator/golden/simple_component.pyi new file mode 100644 index 00000000000..cc88208502c --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/golden/simple_component.pyi @@ -0,0 +1,91 @@ +"""Stub file for """ + +# ------------------- DO NOT EDIT ---------------------- +# This file was generated by `reflex/utils/pyi_generator.py`! +# ------------------------------------------------------ +from collections.abc import Mapping, Sequence +from typing import Any + +from reflex_base.components.component import Component +from reflex_base.event import EventType, PointerEventInfo +from reflex_base.vars.base import Var +from reflex_components_core.core.breakpoints import Breakpoints + +SomeType = Any +SOME_CONSTANT = 42 + +class SimpleComponent(Component): + def public_helper(self) -> str: ... + @classmethod + def create( + cls, + *children, + title: Var[str] | str | None = None, + count: Var[int] | int | None = None, + is_active: Var[bool] | bool | None = None, + opacity: Var[float] | float | None = None, + label: Var[str] | str | None = None, + style: Sequence[Mapping[str, Any]] + | Mapping[str, Any] + | Var[Mapping[str, Any]] + | Breakpoints + | None = None, + key: Any | None = None, + id: Any | None = None, + ref: Var | None = None, + class_name: Any | None = None, + custom_attrs: dict[str, Any | Var] | None = None, + on_blur: EventType[()] | None = None, + on_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_context_menu: EventType[()] | EventType[PointerEventInfo] | None = None, + on_double_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_focus: EventType[()] | None = None, + on_mount: EventType[()] | None = None, + on_mouse_down: EventType[()] | None = None, + on_mouse_enter: EventType[()] | None = None, + on_mouse_leave: EventType[()] | None = None, + on_mouse_move: EventType[()] | None = None, + on_mouse_out: EventType[()] | None = None, + on_mouse_over: EventType[()] | None = None, + on_mouse_up: EventType[()] | None = None, + on_scroll: EventType[()] | None = None, + on_scroll_end: EventType[()] | None = None, + on_unmount: EventType[()] | None = None, + **props, + ) -> SimpleComponent: + """Create the component. + + Args: + *children: The children of the component. + title: The title displayed on the component. + count: The count to display. + is_active: Whether the component is active. + opacity: The opacity of the component. + label: An optional label with a default value. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + ref: The Var to pass as the ref to the component. + class_name: The class name for the component. + custom_attrs: Attributes passed directly to the component. + on_focus: Fired when the element (or some element inside of it) receives focus. For example, it is called when the user clicks on a text input. + on_blur: Fired when focus has left the element (or left some element inside of it). For example, it is called when the user clicks outside of a focused text input. + on_click: Fired when the user clicks on an element. For example, it's called when the user clicks on a button. + on_context_menu: Fired when the user right-clicks on an element. + on_double_click: Fired when the user double-clicks on an element. + on_mouse_down: Fired when the user presses a mouse button on an element. + on_mouse_enter: Fired when the mouse pointer enters the element. + on_mouse_leave: Fired when the mouse pointer leaves the element. + on_mouse_move: Fired when the mouse pointer moves over the element. + on_mouse_out: Fired when the mouse pointer moves out of the element. + on_mouse_over: Fired when the mouse pointer moves onto the element. + on_mouse_up: Fired when the user releases a mouse button on an element. + on_scroll: Fired when the user scrolls the element. + on_scroll_end: Fired when scrolling ends on the element. + on_mount: Fired when the component is mounted to the page. + on_unmount: Fired when the component is removed from the page. Only called during navigation, not on page refresh. + **props: The props of the component. + + Returns: + The component. + """ diff --git a/tests/units/reflex_base/utils/pyi_generator/golden/staticmethod_namespace.pyi b/tests/units/reflex_base/utils/pyi_generator/golden/staticmethod_namespace.pyi new file mode 100644 index 00000000000..22d04a776f6 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/golden/staticmethod_namespace.pyi @@ -0,0 +1,100 @@ +"""Stub file for """ + +# ------------------- DO NOT EDIT ---------------------- +# This file was generated by `reflex/utils/pyi_generator.py`! +# ------------------------------------------------------ +from collections.abc import Mapping, Sequence +from typing import Any + +from reflex_base.components.component import Component, ComponentNamespace +from reflex_base.event import EventSpec, EventType, PointerEventInfo +from reflex_base.vars.base import Var +from reflex_components_core.core.breakpoints import Breakpoints + +class NotifyComponent(Component): + @classmethod + def create( + cls, + *children, + message: Var[str] | str | None = None, + level: Var[str] | str | None = None, + style: Sequence[Mapping[str, Any]] + | Mapping[str, Any] + | Var[Mapping[str, Any]] + | Breakpoints + | None = None, + key: Any | None = None, + id: Any | None = None, + ref: Var | None = None, + class_name: Any | None = None, + custom_attrs: dict[str, Any | Var] | None = None, + on_blur: EventType[()] | None = None, + on_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_context_menu: EventType[()] | EventType[PointerEventInfo] | None = None, + on_double_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_focus: EventType[()] | None = None, + on_mount: EventType[()] | None = None, + on_mouse_down: EventType[()] | None = None, + on_mouse_enter: EventType[()] | None = None, + on_mouse_leave: EventType[()] | None = None, + on_mouse_move: EventType[()] | None = None, + on_mouse_out: EventType[()] | None = None, + on_mouse_over: EventType[()] | None = None, + on_mouse_up: EventType[()] | None = None, + on_scroll: EventType[()] | None = None, + on_scroll_end: EventType[()] | None = None, + on_unmount: EventType[()] | None = None, + **props, + ) -> NotifyComponent: + """Create the component. + + Args: + *children: The children of the component. + message: The notification message. + level: The notification level. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + ref: The Var to pass as the ref to the component. + class_name: The class name for the component. + custom_attrs: Attributes passed directly to the component. + on_focus: Fired when the element (or some element inside of it) receives focus. For example, it is called when the user clicks on a text input. + on_blur: Fired when focus has left the element (or left some element inside of it). For example, it is called when the user clicks outside of a focused text input. + on_click: Fired when the user clicks on an element. For example, it's called when the user clicks on a button. + on_context_menu: Fired when the user right-clicks on an element. + on_double_click: Fired when the user double-clicks on an element. + on_mouse_down: Fired when the user presses a mouse button on an element. + on_mouse_enter: Fired when the mouse pointer enters the element. + on_mouse_leave: Fired when the mouse pointer leaves the element. + on_mouse_move: Fired when the mouse pointer moves over the element. + on_mouse_out: Fired when the mouse pointer moves out of the element. + on_mouse_over: Fired when the mouse pointer moves onto the element. + on_mouse_up: Fired when the user releases a mouse button on an element. + on_scroll: Fired when the user scrolls the element. + on_scroll_end: Fired when scrolling ends on the element. + on_mount: Fired when the component is mounted to the page. + on_unmount: Fired when the component is removed from the page. Only called during navigation, not on page refresh. + **props: The props of the component. + + Returns: + The component. + """ + +def send_notification(message: str, level: str = "info") -> EventSpec: ... + +class Notify(ComponentNamespace): + component = staticmethod(NotifyComponent.create) + + @staticmethod + def __call__(message: str, level: str = "info", **props) -> EventSpec: + """Send a notification event. + + Args: + message: The message to send. + level: The notification level. + + Returns: + The event spec. + """ + +notify = Notify() diff --git a/tests/units/reflex_base/utils/pyi_generator/golden/string_event_annotations.pyi b/tests/units/reflex_base/utils/pyi_generator/golden/string_event_annotations.pyi new file mode 100644 index 00000000000..af628c6d27f --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/golden/string_event_annotations.pyi @@ -0,0 +1,89 @@ +"""Stub file for """ + +# ------------------- DO NOT EDIT ---------------------- +# This file was generated by `reflex/utils/pyi_generator.py`! +# ------------------------------------------------------ +from collections.abc import Mapping, Sequence +from typing import Any + +from reflex_base.components.component import Component +from reflex_base.event import EventType, PointerEventInfo +from reflex_base.vars.base import Var +from reflex_components_core.core.breakpoints import Breakpoints + +def on_empty_handler() -> tuple[()]: ... +def on_string_handler(value: Var[str]) -> tuple[Var[str]]: ... +def on_multi_handler(name: Var[str], age: Var[int]) -> tuple[Var[str], Var[int]]: ... + +class StringAnnotationComponent(Component): + @classmethod + def create( + cls, + *children, + value: Var[str] | str | None = None, + style: Sequence[Mapping[str, Any]] + | Mapping[str, Any] + | Var[Mapping[str, Any]] + | Breakpoints + | None = None, + key: Any | None = None, + id: Any | None = None, + ref: Var | None = None, + class_name: Any | None = None, + custom_attrs: dict[str, Any | Var] | None = None, + on_blur: EventType[()] | None = None, + on_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_context_menu: EventType[()] | EventType[PointerEventInfo] | None = None, + on_double_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_empty: EventType[()] | None = None, + on_focus: EventType[()] | None = None, + on_mount: EventType[()] | None = None, + on_mouse_down: EventType[()] | None = None, + on_mouse_enter: EventType[()] | None = None, + on_mouse_leave: EventType[()] | None = None, + on_mouse_move: EventType[()] | None = None, + on_mouse_out: EventType[()] | None = None, + on_mouse_over: EventType[()] | None = None, + on_mouse_up: EventType[()] | None = None, + on_multi: EventType[()] | EventType[str] | EventType[str, int] | None = None, + on_scroll: EventType[()] | None = None, + on_scroll_end: EventType[()] | None = None, + on_string: EventType[()] | EventType[str] | None = None, + on_unmount: EventType[()] | None = None, + **props, + ) -> StringAnnotationComponent: + """Create the component. + + Args: + *children: The children of the component. + value: no description + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + ref: The Var to pass as the ref to the component. + class_name: The class name for the component. + custom_attrs: Attributes passed directly to the component. + on_focus: Fired when the element (or some element inside of it) receives focus. For example, it is called when the user clicks on a text input. + on_blur: Fired when focus has left the element (or left some element inside of it). For example, it is called when the user clicks outside of a focused text input. + on_click: Fired when the user clicks on an element. For example, it's called when the user clicks on a button. + on_context_menu: Fired when the user right-clicks on an element. + on_double_click: Fired when the user double-clicks on an element. + on_mouse_down: Fired when the user presses a mouse button on an element. + on_mouse_enter: Fired when the mouse pointer enters the element. + on_mouse_leave: Fired when the mouse pointer leaves the element. + on_mouse_move: Fired when the mouse pointer moves over the element. + on_mouse_out: Fired when the mouse pointer moves out of the element. + on_mouse_over: Fired when the mouse pointer moves onto the element. + on_mouse_up: Fired when the user releases a mouse button on an element. + on_scroll: Fired when the user scrolls the element. + on_scroll_end: Fired when scrolling ends on the element. + on_mount: Fired when the component is mounted to the page. + on_unmount: Fired when the component is removed from the page. Only called during navigation, not on page refresh. + on_empty: Fires with no args. + on_string: Fires with a string. + on_multi: Fires with name and age. + **props: The props of the component. + + Returns: + The component. + """ diff --git a/tests/units/reflex_base/utils/pyi_generator/golden/sub_package/__init__.pyi b/tests/units/reflex_base/utils/pyi_generator/golden/sub_package/__init__.pyi new file mode 100644 index 00000000000..9fb215635e5 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/golden/sub_package/__init__.pyi @@ -0,0 +1,8 @@ +"""Stub file for """ +# ------------------- DO NOT EDIT ---------------------- +# This file was generated by `reflex/utils/pyi_generator.py`! +# ------------------------------------------------------ + +from .widget import SubWidget + +__all__ = ["SubWidget"] diff --git a/tests/units/reflex_base/utils/pyi_generator/golden/sub_package/widget.pyi b/tests/units/reflex_base/utils/pyi_generator/golden/sub_package/widget.pyi new file mode 100644 index 00000000000..d81d2740e06 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/golden/sub_package/widget.pyi @@ -0,0 +1,79 @@ +"""Stub file for """ + +# ------------------- DO NOT EDIT ---------------------- +# This file was generated by `reflex/utils/pyi_generator.py`! +# ------------------------------------------------------ +from collections.abc import Mapping, Sequence +from typing import Any + +from reflex_base.components.component import Component +from reflex_base.event import EventType, PointerEventInfo +from reflex_base.vars.base import Var +from reflex_components_core.core.breakpoints import Breakpoints + +class SubWidget(Component): + @classmethod + def create( + cls, + *children, + name: Var[str] | str | None = None, + style: Sequence[Mapping[str, Any]] + | Mapping[str, Any] + | Var[Mapping[str, Any]] + | Breakpoints + | None = None, + key: Any | None = None, + id: Any | None = None, + ref: Var | None = None, + class_name: Any | None = None, + custom_attrs: dict[str, Any | Var] | None = None, + on_blur: EventType[()] | None = None, + on_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_context_menu: EventType[()] | EventType[PointerEventInfo] | None = None, + on_double_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_focus: EventType[()] | None = None, + on_mount: EventType[()] | None = None, + on_mouse_down: EventType[()] | None = None, + on_mouse_enter: EventType[()] | None = None, + on_mouse_leave: EventType[()] | None = None, + on_mouse_move: EventType[()] | None = None, + on_mouse_out: EventType[()] | None = None, + on_mouse_over: EventType[()] | None = None, + on_mouse_up: EventType[()] | None = None, + on_scroll: EventType[()] | None = None, + on_scroll_end: EventType[()] | None = None, + on_unmount: EventType[()] | None = None, + **props, + ) -> SubWidget: + """Create the component. + + Args: + *children: The children of the component. + name: Widget name. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + ref: The Var to pass as the ref to the component. + class_name: The class name for the component. + custom_attrs: Attributes passed directly to the component. + on_focus: Fired when the element (or some element inside of it) receives focus. For example, it is called when the user clicks on a text input. + on_blur: Fired when focus has left the element (or left some element inside of it). For example, it is called when the user clicks outside of a focused text input. + on_click: Fired when the user clicks on an element. For example, it's called when the user clicks on a button. + on_context_menu: Fired when the user right-clicks on an element. + on_double_click: Fired when the user double-clicks on an element. + on_mouse_down: Fired when the user presses a mouse button on an element. + on_mouse_enter: Fired when the mouse pointer enters the element. + on_mouse_leave: Fired when the mouse pointer leaves the element. + on_mouse_move: Fired when the mouse pointer moves over the element. + on_mouse_out: Fired when the mouse pointer moves out of the element. + on_mouse_over: Fired when the mouse pointer moves onto the element. + on_mouse_up: Fired when the user releases a mouse button on an element. + on_scroll: Fired when the user scrolls the element. + on_scroll_end: Fired when scrolling ends on the element. + on_mount: Fired when the component is mounted to the page. + on_unmount: Fired when the component is removed from the page. Only called during navigation, not on page refresh. + **props: The props of the component. + + Returns: + The component. + """ diff --git a/tests/units/reflex_base/utils/pyi_generator/golden/typed_event_handlers.pyi b/tests/units/reflex_base/utils/pyi_generator/golden/typed_event_handlers.pyi new file mode 100644 index 00000000000..2ef4b8a677a --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/golden/typed_event_handlers.pyi @@ -0,0 +1,161 @@ +"""Stub file for """ + +# ------------------- DO NOT EDIT ---------------------- +# This file was generated by `reflex/utils/pyi_generator.py`! +# ------------------------------------------------------ +from collections.abc import Mapping, Sequence +from typing import Any + +from reflex_base.components.component import Component +from reflex_base.event import EventType, PointerEventInfo +from reflex_base.vars.base import Var +from reflex_components_core.core.breakpoints import Breakpoints + +def on_value_change_handler(value: Var[str]) -> tuple[Var[str]]: ... +def on_pair_change_handler( + key: Var[str], value: Var[int] +) -> tuple[Var[str], Var[int]]: ... + +class EventComponent(Component): + @classmethod + def create( + cls, + *children, + value: Var[str] | str | None = None, + style: Sequence[Mapping[str, Any]] + | Mapping[str, Any] + | Var[Mapping[str, Any]] + | Breakpoints + | None = None, + key: Any | None = None, + id: Any | None = None, + ref: Var | None = None, + class_name: Any | None = None, + custom_attrs: dict[str, Any | Var] | None = None, + on_blur: EventType[()] | None = None, + on_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_context_menu: EventType[()] | EventType[PointerEventInfo] | None = None, + on_double_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_focus: EventType[()] | None = None, + on_item_select: EventType[()] | EventType[str] | None = None, + on_mount: EventType[()] | None = None, + on_mouse_down: EventType[()] | None = None, + on_mouse_enter: EventType[()] | None = None, + on_mouse_leave: EventType[()] | None = None, + on_mouse_move: EventType[()] | None = None, + on_mouse_out: EventType[()] | None = None, + on_mouse_over: EventType[()] | None = None, + on_mouse_up: EventType[()] | None = None, + on_pair_change: EventType[()] | EventType[str] | EventType[str, int] | None = None, + on_range_change: EventType[()] | EventType[int] | EventType[int, int] | None = None, + on_scroll: EventType[()] | None = None, + on_scroll_end: EventType[()] | None = None, + on_unmount: EventType[()] | None = None, + on_value_change: EventType[()] | EventType[str] | None = None, + **props, + ) -> EventComponent: + """Create the component. + + Args: + *children: The children of the component. + value: A simple string value prop. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + ref: The Var to pass as the ref to the component. + class_name: The class name for the component. + custom_attrs: Attributes passed directly to the component. + on_focus: Fired when the element (or some element inside of it) receives focus. For example, it is called when the user clicks on a text input. + on_blur: Fired when focus has left the element (or left some element inside of it). For example, it is called when the user clicks outside of a focused text input. + on_click: Fired when the user clicks on an element. For example, it's called when the user clicks on a button. + on_context_menu: Fired when the user right-clicks on an element. + on_double_click: Fired when the user double-clicks on an element. + on_mouse_down: Fired when the user presses a mouse button on an element. + on_mouse_enter: Fired when the mouse pointer enters the element. + on_mouse_leave: Fired when the mouse pointer leaves the element. + on_mouse_move: Fired when the mouse pointer moves over the element. + on_mouse_out: Fired when the mouse pointer moves out of the element. + on_mouse_over: Fired when the mouse pointer moves onto the element. + on_mouse_up: Fired when the user releases a mouse button on an element. + on_scroll: Fired when the user scrolls the element. + on_scroll_end: Fired when scrolling ends on the element. + on_mount: Fired when the component is mounted to the page. + on_unmount: Fired when the component is removed from the page. Only called during navigation, not on page refresh. + on_value_change: Fired when the value changes. + on_pair_change: Fired when a key-value pair changes. + on_item_select: Fired when an item is selected. + on_range_change: Fired when the range changes. + **props: The props of the component. + + Returns: + The component. + """ + +class MultiSpecComponent(Component): + @classmethod + def create( + cls, + *children, + value: Var[str] | str | None = None, + style: Sequence[Mapping[str, Any]] + | Mapping[str, Any] + | Var[Mapping[str, Any]] + | Breakpoints + | None = None, + key: Any | None = None, + id: Any | None = None, + ref: Var | None = None, + class_name: Any | None = None, + custom_attrs: dict[str, Any | Var] | None = None, + on_blur: EventType[()] | None = None, + on_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_context_menu: EventType[()] | EventType[PointerEventInfo] | None = None, + on_double_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_focus: EventType[()] | None = None, + on_mount: EventType[()] | None = None, + on_mouse_down: EventType[()] | None = None, + on_mouse_enter: EventType[()] | None = None, + on_mouse_leave: EventType[()] | None = None, + on_mouse_move: EventType[()] | None = None, + on_mouse_out: EventType[()] | None = None, + on_mouse_over: EventType[()] | None = None, + on_mouse_up: EventType[()] | None = None, + on_open_change: EventType[()] | EventType[bool] | None = None, + on_scroll: EventType[()] | None = None, + on_scroll_end: EventType[()] | None = None, + on_unmount: EventType[()] | None = None, + **props, + ) -> MultiSpecComponent: + """Create the component. + + Args: + *children: The children of the component. + value: no description + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + ref: The Var to pass as the ref to the component. + class_name: The class name for the component. + custom_attrs: Attributes passed directly to the component. + on_focus: Fired when the element (or some element inside of it) receives focus. For example, it is called when the user clicks on a text input. + on_blur: Fired when focus has left the element (or left some element inside of it). For example, it is called when the user clicks outside of a focused text input. + on_click: Fired when the user clicks on an element. For example, it's called when the user clicks on a button. + on_context_menu: Fired when the user right-clicks on an element. + on_double_click: Fired when the user double-clicks on an element. + on_mouse_down: Fired when the user presses a mouse button on an element. + on_mouse_enter: Fired when the mouse pointer enters the element. + on_mouse_leave: Fired when the mouse pointer leaves the element. + on_mouse_move: Fired when the mouse pointer moves over the element. + on_mouse_out: Fired when the mouse pointer moves out of the element. + on_mouse_over: Fired when the mouse pointer moves onto the element. + on_mouse_up: Fired when the user releases a mouse button on an element. + on_scroll: Fired when the user scrolls the element. + on_scroll_end: Fired when scrolling ends on the element. + on_mount: Fired when the component is mounted to the page. + on_unmount: Fired when the component is removed from the page. Only called during navigation, not on page refresh. + on_open_change: Fired when the open state changes. + **props: The props of the component. + + Returns: + The component. + """ diff --git a/tests/units/reflex_base/utils/pyi_generator/golden/var_types.pyi b/tests/units/reflex_base/utils/pyi_generator/golden/var_types.pyi new file mode 100644 index 00000000000..f8a98a19ce3 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/golden/var_types.pyi @@ -0,0 +1,225 @@ +"""Stub file for """ + +# ------------------- DO NOT EDIT ---------------------- +# This file was generated by `reflex/utils/pyi_generator.py`! +# ------------------------------------------------------ +from collections.abc import Mapping, Sequence +from typing import Any + +from reflex_base.components.component import Component +from reflex_base.event import EventType, PointerEventInfo +from reflex_base.vars.base import Var +from reflex_components_core.core.breakpoints import Breakpoints + +class EmptyComponent(Component): + @classmethod + def create( + cls, + *children, + style: Sequence[Mapping[str, Any]] + | Mapping[str, Any] + | Var[Mapping[str, Any]] + | Breakpoints + | None = None, + key: Any | None = None, + id: Any | None = None, + ref: Var | None = None, + class_name: Any | None = None, + custom_attrs: dict[str, Any | Var] | None = None, + on_blur: EventType[()] | None = None, + on_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_context_menu: EventType[()] | EventType[PointerEventInfo] | None = None, + on_double_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_focus: EventType[()] | None = None, + on_mount: EventType[()] | None = None, + on_mouse_down: EventType[()] | None = None, + on_mouse_enter: EventType[()] | None = None, + on_mouse_leave: EventType[()] | None = None, + on_mouse_move: EventType[()] | None = None, + on_mouse_out: EventType[()] | None = None, + on_mouse_over: EventType[()] | None = None, + on_mouse_up: EventType[()] | None = None, + on_scroll: EventType[()] | None = None, + on_scroll_end: EventType[()] | None = None, + on_unmount: EventType[()] | None = None, + **props, + ) -> EmptyComponent: + """Create the component. + + Args: + *children: The children of the component. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + ref: The Var to pass as the ref to the component. + class_name: The class name for the component. + custom_attrs: Attributes passed directly to the component. + on_focus: Fired when the element (or some element inside of it) receives focus. For example, it is called when the user clicks on a text input. + on_blur: Fired when focus has left the element (or left some element inside of it). For example, it is called when the user clicks outside of a focused text input. + on_click: Fired when the user clicks on an element. For example, it's called when the user clicks on a button. + on_context_menu: Fired when the user right-clicks on an element. + on_double_click: Fired when the user double-clicks on an element. + on_mouse_down: Fired when the user presses a mouse button on an element. + on_mouse_enter: Fired when the mouse pointer enters the element. + on_mouse_leave: Fired when the mouse pointer leaves the element. + on_mouse_move: Fired when the mouse pointer moves over the element. + on_mouse_out: Fired when the mouse pointer moves out of the element. + on_mouse_over: Fired when the mouse pointer moves onto the element. + on_mouse_up: Fired when the user releases a mouse button on an element. + on_scroll: Fired when the user scrolls the element. + on_scroll_end: Fired when scrolling ends on the element. + on_mount: Fired when the component is mounted to the page. + on_unmount: Fired when the component is removed from the page. Only called during navigation, not on page refresh. + **props: The props of the component. + + Returns: + The component. + """ + +class EventOnlyComponent(Component): + @classmethod + def create( + cls, + *children, + style: Sequence[Mapping[str, Any]] + | Mapping[str, Any] + | Var[Mapping[str, Any]] + | Breakpoints + | None = None, + key: Any | None = None, + id: Any | None = None, + ref: Var | None = None, + class_name: Any | None = None, + custom_attrs: dict[str, Any | Var] | None = None, + on_blur: EventType[()] | None = None, + on_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_context_menu: EventType[()] | EventType[PointerEventInfo] | None = None, + on_double_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_focus: EventType[()] | None = None, + on_mount: EventType[()] | None = None, + on_mouse_down: EventType[()] | None = None, + on_mouse_enter: EventType[()] | None = None, + on_mouse_leave: EventType[()] | None = None, + on_mouse_move: EventType[()] | None = None, + on_mouse_out: EventType[()] | None = None, + on_mouse_over: EventType[()] | None = None, + on_mouse_up: EventType[()] | None = None, + on_scroll: EventType[()] | None = None, + on_scroll_end: EventType[()] | None = None, + on_toggle: EventType[()] | EventType[bool] | None = None, + on_unmount: EventType[()] | None = None, + **props, + ) -> EventOnlyComponent: + """Create the component. + + Args: + *children: The children of the component. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + ref: The Var to pass as the ref to the component. + class_name: The class name for the component. + custom_attrs: Attributes passed directly to the component. + on_focus: Fired when the element (or some element inside of it) receives focus. For example, it is called when the user clicks on a text input. + on_blur: Fired when focus has left the element (or left some element inside of it). For example, it is called when the user clicks outside of a focused text input. + on_click: Fired when the user clicks on an element. For example, it's called when the user clicks on a button. + on_context_menu: Fired when the user right-clicks on an element. + on_double_click: Fired when the user double-clicks on an element. + on_mouse_down: Fired when the user presses a mouse button on an element. + on_mouse_enter: Fired when the mouse pointer enters the element. + on_mouse_leave: Fired when the mouse pointer leaves the element. + on_mouse_move: Fired when the mouse pointer moves over the element. + on_mouse_out: Fired when the mouse pointer moves out of the element. + on_mouse_over: Fired when the mouse pointer moves onto the element. + on_mouse_up: Fired when the user releases a mouse button on an element. + on_scroll: Fired when the user scrolls the element. + on_scroll_end: Fired when scrolling ends on the element. + on_mount: Fired when the component is mounted to the page. + on_unmount: Fired when the component is removed from the page. Only called during navigation, not on page refresh. + on_toggle: Fired when toggled. + **props: The props of the component. + + Returns: + The component. + """ + +class VarTypesComponent(Component): + @classmethod + def create( + cls, + *children, + name: Var[str] | str | None = None, + count: Var[int] | int | None = None, + ratio: Var[float] | float | None = None, + flag: Var[bool] | bool | None = None, + value: Var[int | str] | int | str | None = None, + items: Var[list[str]] | list[str] | None = None, + metadata: Var[dict[str, Any]] | dict[str, Any] | None = None, + nested: Var[list[dict[str, Any]]] | list[dict[str, Any]] | None = None, + style: Sequence[Mapping[str, Any]] + | Mapping[str, Any] + | Var[Mapping[str, Any]] + | Breakpoints + | None = None, + key: Any | None = None, + id: Any | None = None, + ref: Var | None = None, + class_name: Any | None = None, + custom_attrs: dict[str, Any | Var] | None = None, + on_blur: EventType[()] | None = None, + on_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_context_menu: EventType[()] | EventType[PointerEventInfo] | None = None, + on_double_click: EventType[()] | EventType[PointerEventInfo] | None = None, + on_focus: EventType[()] | None = None, + on_mount: EventType[()] | None = None, + on_mouse_down: EventType[()] | None = None, + on_mouse_enter: EventType[()] | None = None, + on_mouse_leave: EventType[()] | None = None, + on_mouse_move: EventType[()] | None = None, + on_mouse_out: EventType[()] | None = None, + on_mouse_over: EventType[()] | None = None, + on_mouse_up: EventType[()] | None = None, + on_scroll: EventType[()] | None = None, + on_scroll_end: EventType[()] | None = None, + on_unmount: EventType[()] | None = None, + **props, + ) -> VarTypesComponent: + """Create the component. + + Args: + *children: The children of the component. + name: Basic Var types. + count: no description + ratio: no description + flag: no description + value: A string or int value. + items: A list of string items. + metadata: Metadata dictionary. + nested: Nested structures. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + ref: The Var to pass as the ref to the component. + class_name: The class name for the component. + custom_attrs: Attributes passed directly to the component. + on_focus: Fired when the element (or some element inside of it) receives focus. For example, it is called when the user clicks on a text input. + on_blur: Fired when focus has left the element (or left some element inside of it). For example, it is called when the user clicks outside of a focused text input. + on_click: Fired when the user clicks on an element. For example, it's called when the user clicks on a button. + on_context_menu: Fired when the user right-clicks on an element. + on_double_click: Fired when the user double-clicks on an element. + on_mouse_down: Fired when the user presses a mouse button on an element. + on_mouse_enter: Fired when the mouse pointer enters the element. + on_mouse_leave: Fired when the mouse pointer leaves the element. + on_mouse_move: Fired when the mouse pointer moves over the element. + on_mouse_out: Fired when the mouse pointer moves out of the element. + on_mouse_over: Fired when the mouse pointer moves onto the element. + on_mouse_up: Fired when the user releases a mouse button on an element. + on_scroll: Fired when the user scrolls the element. + on_scroll_end: Fired when scrolling ends on the element. + on_mount: Fired when the component is mounted to the page. + on_unmount: Fired when the component is removed from the page. Only called during navigation, not on page refresh. + **props: The props of the component. + + Returns: + The component. + """ diff --git a/tests/units/reflex_base/utils/pyi_generator/test_regression.py b/tests/units/reflex_base/utils/pyi_generator/test_regression.py new file mode 100644 index 00000000000..f52ca4ec07d --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/test_regression.py @@ -0,0 +1,265 @@ +"""Regression tests for pyi_generator. + +Runs PyiGenerator.scan_all against a directory of curated Python files and +compares the generated .pyi stubs against a set of golden reference files. + +Usage as CLI to regenerate golden files: + python -m tests.units.reflex_base.utils.pyi_generator --update + +Usage as pytest: + pytest tests/units/reflex_base/utils/pyi_generator/test_regression.py +""" + +from __future__ import annotations + +import argparse +import difflib +import sys +from pathlib import Path + +import pytest +from reflex_base.utils.pyi_generator import PyiGenerator + +_HERE = Path(__file__).resolve().parent +DATASET_DIR = _HERE / "dataset" +GOLDEN_DIR = _HERE / "golden" + +_UPDATE_CMD = "python -m tests.units.reflex_base.utils.pyi_generator --update" + + +def _golden_path_for(source_path: Path) -> Path: + """Map a dataset .py file to its golden .pyi counterpart. + + Args: + source_path: The path to the dataset .py file. + + Returns: + The corresponding path to the golden .pyi file. + """ + relative = source_path.relative_to(DATASET_DIR) + return GOLDEN_DIR / relative.with_suffix(".pyi") + + +def _normalize_stub(content: str) -> str: + """Replace the absolute-path docstring header so golden files are portable. + + Args: + content: The raw content of the generated .pyi file. + + Returns: + The normalized content with the dataset path replaced by a placeholder. + """ + lines = content.splitlines(keepends=True) + normalized: list[str] = [] + for line in lines: + if line.startswith('"""Stub file for '): + normalized.append('"""Stub file for """\n') + else: + normalized.append(line) + return "".join(normalized) + + +def _run_generator() -> dict[Path, str]: + """Run PyiGenerator.scan_all on the dataset dir and collect results. + + Generated .pyi files are read back and then removed from the dataset + tree so the working copy stays clean. A ``try/finally`` block ensures + generated files are always cleaned up, even if reading one of them fails. + + Returns: + A mapping from dataset source .py files to their generated .pyi content. + """ + gen = PyiGenerator() + gen.scan_all([str(DATASET_DIR)]) + + pyi_paths = [Path(pyi_str) for pyi_str, _hash in gen.written_files] + try: + results: dict[Path, str] = {} + for pyi_path in pyi_paths: + source_path = pyi_path.with_suffix(".py") + results[source_path] = pyi_path.read_text() + finally: + for pyi_path in pyi_paths: + pyi_path.unlink(missing_ok=True) + return results + + +def update_golden_files() -> list[str]: + """Regenerate all golden .pyi files from the dataset. + + Returns: + A list of relative paths to the golden files that were updated. + """ + GOLDEN_DIR.mkdir(parents=True, exist_ok=True) + for subdir in DATASET_DIR.rglob("*"): + if subdir.is_dir() and subdir != DATASET_DIR: + (GOLDEN_DIR / subdir.relative_to(DATASET_DIR)).mkdir( + parents=True, exist_ok=True + ) + + generated = _run_generator() + updated: list[str] = [] + for source_path, content in sorted(generated.items()): + golden = _golden_path_for(source_path) + golden.write_text(_normalize_stub(content)) + rel = golden.relative_to(_HERE) + updated.append(str(rel)) + print(f" updated: {rel}") + + expected_goldens = {_golden_path_for(s) for s in generated} + for existing in GOLDEN_DIR.rglob("*.pyi"): + if existing not in expected_goldens: + existing.unlink() + print(f" removed stale: {existing.relative_to(_HERE)}") + + return updated + + +@pytest.fixture(scope="module") +def generated_stubs(): + """Run the generator once for the whole test module. + + Returns: + A mapping from dataset source .py files to their generated .pyi content. + """ + return _run_generator() + + +def _existing_golden_cases() -> list[tuple[str, Path]]: + """Build test IDs from existing golden files (no generator run needed). + + Returns: + A list of tuples containing test IDs and corresponding dataset source paths. + """ + cases = [] + for golden in sorted(GOLDEN_DIR.rglob("*.pyi")): + relative = golden.relative_to(GOLDEN_DIR) + source = DATASET_DIR / relative.with_suffix(".py") + tid = str(relative.with_suffix(".py")).replace("/", ".") + cases.append((tid, source)) + return cases + + +@pytest.mark.parametrize( + "source_path", + [p for _, p in _existing_golden_cases()], + ids=[tid for tid, _ in _existing_golden_cases()], +) +def test_pyi_golden(generated_stubs: dict[Path, str], source_path: Path): + """Compare generated .pyi output against golden reference. + + Args: + generated_stubs: The mapping of dataset source paths to generated .pyi content. + source_path: The path to the dataset .py file for this test case. + """ + golden_path = _golden_path_for(source_path) + + content = generated_stubs.get(source_path) + if content is None: + pytest.fail( + f"pyi_generator produced no output for {source_path.name}, " + f"but a golden file exists at {golden_path}" + ) + + normalized = _normalize_stub(content) + expected = golden_path.read_text() + + if normalized != expected: + diff = difflib.unified_diff( + expected.splitlines(keepends=True), + normalized.splitlines(keepends=True), + fromfile=f"golden/{golden_path.name}", + tofile=f"generated/{golden_path.name}", + ) + pytest.fail( + f"Generated stub differs from golden reference for {source_path.name}.\n" + f"Run `{_UPDATE_CMD}` to regenerate.\n\n{''.join(diff)}" + ) + + +def test_no_extra_golden_files(generated_stubs: dict[Path, str]): + """Ensure no golden files exist without corresponding dataset sources. + + Args: + generated_stubs: The mapping of dataset source paths to generated .pyi content. + """ + expected_goldens = {_golden_path_for(s) for s in generated_stubs} + for existing in GOLDEN_DIR.rglob("*.pyi"): + assert existing in expected_goldens, ( + f"Stale golden file {existing.relative_to(_HERE)} has no dataset source. " + f"Run `{_UPDATE_CMD}` to clean up." + ) + + +def test_no_missing_golden_files(generated_stubs: dict[Path, str]): + """Ensure every generated stub has a corresponding golden file. + + Catches the case where a new dataset file is added but ``--update`` is + not run, so no golden reference exists and the new scenario silently + goes untested. + + Args: + generated_stubs: The mapping of dataset source paths to generated .pyi content. + """ + existing_goldens = set(GOLDEN_DIR.rglob("*.pyi")) + missing = [] + for source_path in sorted(generated_stubs): + golden = _golden_path_for(source_path) + if golden not in existing_goldens: + missing.append(str(golden.relative_to(_HERE))) + assert not missing, ( + f"Generated stubs have no golden files: {', '.join(missing)}. " + f"Run `{_UPDATE_CMD}` to create them." + ) + + +def main(): + parser = argparse.ArgumentParser(description="pyi_generator regression test suite") + parser.add_argument( + "--update", + action="store_true", + help="Regenerate golden .pyi files from the dataset.", + ) + parser.add_argument( + "--check", + action="store_true", + help="Check that generated stubs match golden files (CI mode).", + ) + args = parser.parse_args() + + if args.update: + print(f"Regenerating golden files from {DATASET_DIR} ...") + updated = update_golden_files() + print( + f"\nDone. {len(updated)} file(s) updated in {GOLDEN_DIR.relative_to(_HERE)}/" + ) + print("Review the changes and commit them with your PR.") + elif args.check: + print("Checking generated stubs against golden files...") + generated = _run_generator() + failures = [] + for source_path, content in sorted(generated.items()): + golden_path = _golden_path_for(source_path) + if not golden_path.exists(): + failures.append(f" {source_path.name}: missing golden file") + continue + if _normalize_stub(content) != golden_path.read_text(): + failures.append(f" {source_path.name}: differs from golden") + expected_goldens = {_golden_path_for(s) for s in generated} + for existing in sorted(GOLDEN_DIR.rglob("*.pyi")): + if existing not in expected_goldens: + failures.append( + f" {existing.relative_to(_HERE)}: stale golden (no dataset source)" + ) + if failures: + print("FAILED:") + print("\n".join(failures)) + print(f"\nRun `{_UPDATE_CMD}` to regenerate.") + sys.exit(1) + print("All stubs match golden files.") + else: + parser.print_help() + + +if __name__ == "__main__": + main() diff --git a/tests/units/reflex_base/utils/pyi_generator/test_unit.py b/tests/units/reflex_base/utils/pyi_generator/test_unit.py new file mode 100644 index 00000000000..4bbf5107b8a --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/test_unit.py @@ -0,0 +1,460 @@ +"""Unit tests for individual pyi_generator translation functions. + +Tests smaller functions in isolation using "code in -> expected code out" +patterns. These complement the golden file regression tests by testing +edge cases in type resolution and AST generation directly. +""" + +from __future__ import annotations + +import ast +import linecache +import sys +import types as builtin_types +import typing +from types import SimpleNamespace +from typing import Any, Literal, Optional, Union + +import pytest +from reflex_base.components.component import Component +from reflex_base.utils.pyi_generator import ( + StubGenerator, + _get_type_hint, + _is_literal, + _is_optional, + _is_union, + _safe_issubclass, + type_to_ast, +) +from reflex_base.vars.base import Var + + +def test_is_union_typing_union(): + assert _is_union(Union[str, int]) is True # noqa: UP007 + + +def test_is_union_pipe_union(): + assert _is_union(str | int) is True + + +def test_is_union_optional_is_union(): + assert _is_union(Optional[str]) is True # noqa: UP045 + + +def test_is_union_plain_type(): + assert _is_union(str) is False + + +def test_is_union_none(): + assert _is_union(None) is False + + +def test_is_union_var(): + assert _is_union(Var[str]) is False + + +def test_is_optional_none_value(): + assert _is_optional(None) is True + + +def test_is_optional_none_type(): + assert _is_optional(type(None)) is True + + +def test_is_optional_optional_type(): + assert _is_optional(Optional[str]) is True # noqa: UP045 + + +def test_is_optional_union_with_none(): + assert _is_optional(str | None) is True + + +def test_is_optional_plain_type(): + assert _is_optional(str) is False + + +def test_is_optional_union_without_none(): + assert _is_optional(str | int) is False + + +def test_is_literal_literal_type(): + assert _is_literal(Literal["a", "b"]) is True + + +def test_is_literal_plain_type(): + assert _is_literal(str) is False + + +def test_is_literal_none(): + assert _is_literal(None) is False + + +def test_safe_issubclass_true(): + assert _safe_issubclass(bool, int) is True + + +def test_safe_issubclass_false(): + assert _safe_issubclass(str, int) is False + + +def test_safe_issubclass_non_type(): + assert _safe_issubclass("not a type", int) is False + + +def test_safe_issubclass_none(): + assert _safe_issubclass(None, int) is False + + +@pytest.fixture +def type_hint_globals(): + """Provide a type_hint_globals dict with common types. + + Returns: + A dict mapping type names to their corresponding types, including built-ins, typing constructs, and + custom types. + """ + return { + "str": str, + "int": int, + "float": float, + "bool": bool, + "list": list, + "dict": dict, + "Var": Var, + "Any": Any, + "Literal": Literal, + "Optional": Optional, + "Union": Union, + **{name: getattr(typing, name) for name in ["Sequence", "Mapping"]}, + } + + +def test_get_type_hint_none_value(type_hint_globals): + assert _get_type_hint(None, type_hint_globals) == "None" + + +def test_get_type_hint_none_type(type_hint_globals): + assert _get_type_hint(type(None), type_hint_globals) == "None" + + +def test_get_type_hint_simple_optional(type_hint_globals): + assert _get_type_hint(str, type_hint_globals, is_optional=True) == "str | None" + + +def test_get_type_hint_simple_not_optional(type_hint_globals): + assert _get_type_hint(str, type_hint_globals, is_optional=False) == "str" + + +def test_get_type_hint_optional_union(type_hint_globals): + assert _get_type_hint(Optional[str], type_hint_globals) == "str | None" # noqa: UP045 + + +def test_get_type_hint_union_without_none(type_hint_globals): + result = _get_type_hint(Union[str, int], type_hint_globals, is_optional=False) # noqa: UP007 + assert result == "int | str" + + +def test_get_type_hint_union_with_none(type_hint_globals): + result = _get_type_hint(Union[str, int, None], type_hint_globals) # noqa: UP007 + assert result == "int | str | None" + + +def test_get_type_hint_var_expansion(type_hint_globals): + """Var[str] should expand to Var[str] | str.""" + result = _get_type_hint(Var[str], type_hint_globals, is_optional=False) + assert result == "Var[str] | str" + + +def test_get_type_hint_var_union_expansion(type_hint_globals): + """Var[str | int] should expand to include Var, str, and int.""" + result = _get_type_hint(Var[str | int], type_hint_globals, is_optional=False) + parts = {p.strip() for p in result.split("|")} + assert "str" in parts + assert "int" in parts + assert any("Var[" in p for p in parts) + + +def test_get_type_hint_literal(type_hint_globals): + result = _get_type_hint( + Literal["a", "b", "c"], type_hint_globals, is_optional=False + ) + assert result == "Literal['a', 'b', 'c']" + + +def test_type_to_ast_none_type(): + node = type_to_ast(type(None), Component) + assert isinstance(node, ast.Name) + assert node.id == "None" + + +def test_type_to_ast_none_value(): + node = type_to_ast(None, Component) + assert isinstance(node, ast.Name) + assert node.id == "None" + + +def test_type_to_ast_simple_type(): + node = type_to_ast(str, Component) + assert isinstance(node, ast.Name) + assert node.id == "str" + + +def test_type_to_ast_literal(): + node = type_to_ast(Literal["x", "y"], Component) + assert isinstance(node, ast.Subscript) + unparsed = ast.unparse(node) + assert "Literal" in unparsed + assert "'x'" in unparsed + assert "'y'" in unparsed + + +def test_type_to_ast_union(): + node = type_to_ast(Union[str, int], Component) # noqa: UP007 + assert isinstance(node, ast.Subscript) + unparsed = ast.unparse(node) + assert "str" in unparsed + assert "int" in unparsed + + +def test_type_to_ast_generic(): + node = type_to_ast(list[str], Component) + unparsed = ast.unparse(node) + assert "list" in unparsed + assert "str" in unparsed + + +def test_type_to_ast_nested_generic(): + node = type_to_ast(dict[str, list[int]], Component) + unparsed = ast.unparse(node) + assert "dict" in unparsed + assert "str" in unparsed + assert "list" in unparsed + assert "int" in unparsed + + +_stub_gen_counter = 0 + + +def _generate_stub_from_source(source: str) -> str: + """Parse source, run StubGenerator, return unparsed result. + + Args: + source: The Python source code to generate a stub from. + + Returns: + The generated stub code as a string. + """ + global _stub_gen_counter + _stub_gen_counter += 1 + module_name = f"_pyi_test_mod_{_stub_gen_counter}" + filename = f"{module_name}.py" + + linecache.cache[filename] = ( + len(source), + None, + source.splitlines(keepends=True), + filename, + ) + + mod = builtin_types.ModuleType(module_name) + mod.__file__ = filename + sys.modules[module_name] = mod + try: + exec(compile(source, filename, "exec"), mod.__dict__) + + for obj in vars(mod).values(): + if ( + isinstance(obj, type) + and issubclass(obj, Component) + and obj is not Component + ): + obj.__module__ = module_name + + classes: dict[str, type[Component] | type[SimpleNamespace]] = { + name: obj + for name, obj in vars(mod).items() + if isinstance(obj, type) + and issubclass(obj, Component) + and obj is not Component + } + + tree = ast.parse(source) + generator = StubGenerator(mod, classes) + new_tree = generator.visit(tree) + return ast.unparse(new_tree) + finally: + sys.modules.pop(module_name, None) + linecache.cache.pop(filename, None) + + +def test_stub_private_method_removed(): + source = """ +from reflex_base.components.component import Component +from reflex_base.vars.base import Var + +class Foo(Component): + x: Var[str] + def _hidden(self): pass + def visible(self): return 1 +""" + result = _generate_stub_from_source(source) + assert "def _hidden" not in result + assert "def visible" in result + # Public method body should be blanked to ellipsis. + assert "return 1" not in result + + +def test_stub_module_docstring_removed(): + source = '''"""This is a module docstring.""" +from reflex_base.components.component import Component +from reflex_base.vars.base import Var + +class Bar(Component): + y: Var[int] +''' + result = _generate_stub_from_source(source) + assert "This is a module docstring" not in result + # Should still have the class and create method. + assert "class Bar" in result + assert "def create" in result + + +def test_stub_future_import_removed(): + source = """from __future__ import annotations +from reflex_base.components.component import Component +from reflex_base.vars.base import Var + +class Baz(Component): + z: Var[bool] +""" + result = _generate_stub_from_source(source) + assert "__future__" not in result + # Default imports should be injected instead. + assert "from reflex_base.event import" in result + + +def test_stub_class_docstring_removed(): + source = ''' +from reflex_base.components.component import Component +from reflex_base.vars.base import Var + +class DocComponent(Component): + """This class docstring should be removed.""" + val: Var[str] +''' + result = _generate_stub_from_source(source) + assert "This class docstring should be removed" not in result + assert "class DocComponent" in result + + +def test_stub_non_annotated_assignment_removed(): + source = """ +from reflex_base.components.component import Component +from reflex_base.vars.base import Var + +class AssignComp(Component): + some_const = "hello" + val: Var[str] +""" + result = _generate_stub_from_source(source) + assert "some_const" not in result + assert "hello" not in result + + +def test_stub_any_assignment_preserved(): + source = """ +from typing import Any +from reflex_base.components.component import Component +from reflex_base.vars.base import Var + +SomeAlias = Any + +class AnyComp(Component): + val: Var[str] +""" + result = _generate_stub_from_source(source) + assert "SomeAlias = Any" in result + + +def test_stub_annotated_assignment_value_blanked(): + source = """ +from reflex_base.components.component import Component +from reflex_base.vars.base import Var + +mode: str = "default" + +class ModeComp(Component): + val: Var[str] +""" + result = _generate_stub_from_source(source) + assert "mode: str" in result + # Value should be stripped — only the annotation remains. + assert '"default"' not in result + assert "mode: str =" not in result + + +def test_stub_classvar_preserved(): + source = """ +from typing import ClassVar +from reflex_base.components.component import Component +from reflex_base.vars.base import Var + +class CVComp(Component): + allowed_types: ClassVar[list[str]] = ["A"] + val: Var[str] +""" + result = _generate_stub_from_source(source) + assert "ClassVar[list[str]]" in result + # ClassVar props should NOT appear as create() kwargs. + assert "allowed_types" not in result.split("def create")[1] + + +def test_stub_private_classvar_removed(): + source = """ +from typing import ClassVar +from reflex_base.components.component import Component +from reflex_base.vars.base import Var + +class PVComp(Component): + _valid_children: ClassVar[list[str]] = ["A"] + val: Var[str] +""" + result = _generate_stub_from_source(source) + # Private ClassVar annotations are stripped entirely. + assert "_valid_children" not in result + + +def test_stub_public_function_body_blanked(): + source = """ +from reflex_base.components.component import Component +from reflex_base.vars.base import Var + +class FuncComp(Component): + val: Var[str] + def helper(self) -> str: + x = 1 + y = 2 + return str(x + y) +""" + result = _generate_stub_from_source(source) + assert "def helper(self) -> str:" in result + assert "x = 1" not in result + assert "x + y" not in result + + +def test_stub_create_method_generated(): + source = """ +from reflex_base.components.component import Component, field +from reflex_base.vars.base import Var + +class CreateComp(Component): + name: Var[str] = field(doc="The name.") +""" + result = _generate_stub_from_source(source) + assert "@classmethod" in result + assert "def create(cls, *children" in result + # name prop should appear as a keyword arg with Var expansion. + assert "name:" in result + assert "**props" in result + # Return type should reference the class. + assert "CreateComp" in result