From f81cd8f0e9ddd58674274c124175e56a2b760ad2 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 9 Apr 2026 22:40:47 -0700 Subject: [PATCH 1/8] initial pyi_generator regression test --- tests/units/reflex_base/utils/__init__.py | 0 .../utils/pyi_generator/__init__.py | 0 .../utils/pyi_generator/__main__.py | 10 + .../utils/pyi_generator/dataset/__init__.py | 6 + .../utils/pyi_generator/dataset/any_assign.py | 24 + .../dataset/classvar_and_private.py | 49 ++ .../pyi_generator/dataset/custom_create.py | 36 ++ .../utils/pyi_generator/dataset/edge_cases.py | 39 ++ .../pyi_generator/dataset/inheritance.py | 41 ++ .../pyi_generator/dataset/literal_types.py | 31 ++ .../pyi_generator/dataset/module_level.py | 48 ++ .../dataset/namespace_component.py | 45 ++ .../pyi_generator/dataset/simple_component.py | 42 ++ .../dataset/staticmethod_namespace.py | 44 ++ .../dataset/string_event_annotations.py | 46 ++ .../dataset/sub_package/__init__.py | 15 + .../dataset/sub_package/widget.py | 13 + .../dataset/typed_event_handlers.py | 79 ++++ .../utils/pyi_generator/dataset/var_types.py | 31 ++ .../utils/pyi_generator/test_regression.py | 207 +++++++++ .../utils/pyi_generator/test_unit.py | 419 ++++++++++++++++++ 21 files changed, 1225 insertions(+) create mode 100644 tests/units/reflex_base/utils/__init__.py create mode 100644 tests/units/reflex_base/utils/pyi_generator/__init__.py create mode 100644 tests/units/reflex_base/utils/pyi_generator/__main__.py create mode 100644 tests/units/reflex_base/utils/pyi_generator/dataset/__init__.py create mode 100644 tests/units/reflex_base/utils/pyi_generator/dataset/any_assign.py create mode 100644 tests/units/reflex_base/utils/pyi_generator/dataset/classvar_and_private.py create mode 100644 tests/units/reflex_base/utils/pyi_generator/dataset/custom_create.py create mode 100644 tests/units/reflex_base/utils/pyi_generator/dataset/edge_cases.py create mode 100644 tests/units/reflex_base/utils/pyi_generator/dataset/inheritance.py create mode 100644 tests/units/reflex_base/utils/pyi_generator/dataset/literal_types.py create mode 100644 tests/units/reflex_base/utils/pyi_generator/dataset/module_level.py create mode 100644 tests/units/reflex_base/utils/pyi_generator/dataset/namespace_component.py create mode 100644 tests/units/reflex_base/utils/pyi_generator/dataset/simple_component.py create mode 100644 tests/units/reflex_base/utils/pyi_generator/dataset/staticmethod_namespace.py create mode 100644 tests/units/reflex_base/utils/pyi_generator/dataset/string_event_annotations.py create mode 100644 tests/units/reflex_base/utils/pyi_generator/dataset/sub_package/__init__.py create mode 100644 tests/units/reflex_base/utils/pyi_generator/dataset/sub_package/widget.py create mode 100644 tests/units/reflex_base/utils/pyi_generator/dataset/typed_event_handlers.py create mode 100644 tests/units/reflex_base/utils/pyi_generator/dataset/var_types.py create mode 100644 tests/units/reflex_base/utils/pyi_generator/test_regression.py create mode 100644 tests/units/reflex_base/utils/pyi_generator/test_unit.py 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/any_assign.py b/tests/units/reflex_base/utils/pyi_generator/dataset/any_assign.py new file mode 100644 index 00000000000..833f8407c47 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/any_assign.py @@ -0,0 +1,24 @@ +"""Module with assignments to Any and dunder assignments. + +This module tests: +- visit_Assign: assignment to `Any` is preserved +- visit_Assign: dunder tuple assignments are removed (lazy_loader.attach pattern) +- Non-Component classes are not processed (no create() added) +""" + +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 SmallComponent(Component): + """A small component.""" + + text: Var[str] = field(doc="The text content.") 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..c8a93df4443 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/classvar_and_private.py @@ -0,0 +1,49 @@ +"""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 = "div" + + library: str = "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.""" + pass + + def render_item(self) -> str: + """Public method, body should be blanked.""" + 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..22384395673 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/custom_create.py @@ -0,0 +1,36 @@ +"""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/edge_cases.py b/tests/units/reflex_base/utils/pyi_generator/dataset/edge_cases.py new file mode 100644 index 00000000000..d634c98ccee --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/edge_cases.py @@ -0,0 +1,39 @@ +"""Components with edge case type annotations. + +This module tests: +- Var[list[int]] type hint (nested generic inside Var) +- Optional[Var[str]] (explicit Optional wrapping) +- Component with no 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.""" + + pass + + +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 NestedGenericComponent(Component): + """A component with nested generic Var types.""" + + # Var with nested generic. + numbers: Var[list[int]] = field(doc="A list of numbers.") + + pairs: Var[dict[str, int]] = field(doc="Key-value pairs.") + + nested: Var[list[dict[str, Any]]] = field(doc="Nested structures.") 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..b49a1e30dec --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/inheritance.py @@ -0,0 +1,41 @@ +"""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 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 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..6f7f3270835 --- /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 Any, 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..b4f5cf86a29 --- /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 Any, 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..05223612c75 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/namespace_component.py @@ -0,0 +1,45 @@ +"""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 typing import Any + +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..6fcb9805aa0 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/simple_component.py @@ -0,0 +1,42 @@ +"""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 +""" + +from typing import Any + +from reflex_base.components.component import Component, field +from reflex_base.vars.base import Var + + +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.""" + return "private" + + def public_helper(self) -> str: + """A public method that should have its body blanked out.""" + 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..1076c80f484 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/staticmethod_namespace.py @@ -0,0 +1,44 @@ +"""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 typing import Any + +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. + """ + ... + + +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..f96dbf306e0 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/string_event_annotations.py @@ -0,0 +1,46 @@ +"""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 typing import Any + +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.""" + return ((),) + + +def on_string_handler(value: Var[str]) -> "tuple[Var[str]]": + """Handler returning a single string arg.""" + return (value,) + + +def on_multi_handler(name: Var[str], age: Var[int]) -> "tuple[Var[str], Var[int]]": + """Handler returning multiple args.""" + 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..a8214acd5e4 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/typed_event_handlers.py @@ -0,0 +1,79 @@ +"""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 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 + + +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..9ba5be23964 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/var_types.py @@ -0,0 +1,31 @@ +"""Component with various Var[T] prop types. + +This module tests: +- Var[T] expansion: Var[str] -> Var[str] | str +- Var with Union args: Var[str | int] -> Var[str | int] | str | int +- Optional props (None default) +- Complex nested types: Var[list[str]], Var[dict[str, Any]] +- Non-Var props (plain type annotations) +""" + +from typing import Any + +from reflex_base.components.component import Component, field +from reflex_base.vars.base import Var + + +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.") 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..c4bbfda6962 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/test_regression.py @@ -0,0 +1,207 @@ +"""Regression tests for pyi_generator. + +Runs pyi_generator 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 _scan_file + +_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" + +DATASET_MODULES = sorted( + p + for p in DATASET_DIR.rglob("*.py") + if p.name != "__init__.py" and not p.name.startswith("_") +) + +DATASET_INIT_MODULES = sorted( + p for p in DATASET_DIR.rglob("__init__.py") if p != DATASET_DIR / "__init__.py" +) + +ALL_DATASET_FILES = DATASET_MODULES + DATASET_INIT_MODULES + + +def _golden_path_for(module_path: Path) -> Path: + """Map a dataset .py file to its golden .pyi counterpart.""" + relative = module_path.relative_to(DATASET_DIR) + return GOLDEN_DIR / relative.with_suffix(".pyi") + + +def _generate_stub(module_path: Path) -> str | None: + """Generate a .pyi stub for a single dataset module and return its content. + + Returns None if the generator decides no stub is needed. + """ + result = _scan_file(module_path) + if result is None: + return None + pyi_path = Path(result[0]) + content = pyi_path.read_text() + pyi_path.unlink(missing_ok=True) + return content + + +def _normalize_stub(content: str) -> str: + """Strip the file-specific header (path line) so golden files are portable.""" + 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 update_golden_files() -> list[str]: + """Regenerate all golden .pyi files from the dataset. + + Returns a list of updated file names. + """ + 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 + ) + + updated: list[str] = [] + for module_path in ALL_DATASET_FILES: + content = _generate_stub(module_path) + if content is None: + continue + golden = _golden_path_for(module_path) + normalized = _normalize_stub(content) + golden.write_text(normalized) + updated.append(str(golden.relative_to(_HERE))) + print(f" updated: {golden.relative_to(_HERE)}") + + expected_goldens = {_golden_path_for(p) for p in ALL_DATASET_FILES} + 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 + + +def _get_test_cases() -> list[tuple[str, Path]]: + """Build parameterized test cases: (test_id, module_path).""" + cases = [] + for module_path in ALL_DATASET_FILES: + golden = _golden_path_for(module_path) + if golden.exists(): + test_id = str(module_path.relative_to(DATASET_DIR)).replace("/", ".") + cases.append((test_id, module_path)) + return cases + + +@pytest.fixture(autouse=True, scope="module") +def _ensure_dataset_importable(): + """Ensure the dataset directory is on sys.path so modules can be imported.""" + repo_root = _HERE.parent.parent.parent.parent.parent + if str(repo_root) not in sys.path: + sys.path.insert(0, str(repo_root)) + + +@pytest.mark.parametrize( + "module_path", + [p for _, p in _get_test_cases()], + ids=[tid for tid, _ in _get_test_cases()], +) +def test_pyi_golden(module_path: Path): + """Compare generated .pyi output against golden reference.""" + golden_path = _golden_path_for(module_path) + if not golden_path.exists(): + pytest.skip(f"No golden file for {module_path.name}. Run `{_UPDATE_CMD}` to generate.") + + generated = _generate_stub(module_path) + if generated is None: + pytest.fail( + f"pyi_generator produced no output for {module_path.name}, " + f"but a golden file exists at {golden_path}" + ) + + normalized = _normalize_stub(generated) + 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}", + ) + diff_text = "".join(diff) + pytest.fail( + f"Generated stub differs from golden reference for {module_path.name}.\n" + f"Run `{_UPDATE_CMD}` to regenerate.\n\n{diff_text}" + ) + + +def test_no_extra_golden_files(): + """Ensure no golden files exist without corresponding dataset sources.""" + expected_goldens = {_golden_path_for(p) for p in ALL_DATASET_FILES} + 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 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...") + failures = [] + for module_path in ALL_DATASET_FILES: + golden_path = _golden_path_for(module_path) + if not golden_path.exists(): + continue + generated = _generate_stub(module_path) + if generated is None: + failures.append(f" {module_path.name}: no output generated") + continue + normalized = _normalize_stub(generated) + if normalized != golden_path.read_text(): + failures.append(f" {module_path.name}: differs from golden") + 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..8dbe92fdb0e --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/test_unit.py @@ -0,0 +1,419 @@ +"""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 typing import Any, ClassVar, Literal, Optional, Union + +import pytest + +from reflex_base.components.component import Component, field +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 + + +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 + + +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 + + +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.""" + 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" + + +def test_get_type_hint_union_without_none(type_hint_globals): + result = _get_type_hint(Union[str, int], type_hint_globals, is_optional=False) + assert "int" in result + assert "str" in result + + +def test_get_type_hint_union_with_none(type_hint_globals): + result = _get_type_hint(Union[str, int, None], type_hint_globals) + assert result.endswith("| None") + assert "int" in result + assert "str" in result + + +def test_get_type_hint_var_expansion(type_hint_globals): + """Var[str] should expand to include both Var[str] and str.""" + result = _get_type_hint(Var[str], type_hint_globals, is_optional=False) + assert "Var[str]" in result + assert "str" in result + + +def test_get_type_hint_var_union_expansion(type_hint_globals): + """Var[str | int] should expand to Var[str | int] | str | int.""" + result = _get_type_hint(Var[str | int], type_hint_globals, is_optional=False) + assert "Var[" in result + assert "str" in result + assert "int" in result + + +def test_get_type_hint_literal(type_hint_globals): + result = _get_type_hint(Literal["a", "b", "c"], type_hint_globals, is_optional=False) + assert "Literal" in result + assert "'a'" in result + assert "'b'" in result + assert "'c'" in result + + +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) + 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.""" + 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 = { + 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 "_hidden" not in result + assert "visible" 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 + + +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 + + +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 + + +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 + + +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 + assert '"default"' 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): + _valid_children: ClassVar[list[str]] = ["A"] + val: Var[str] +''' + result = _generate_stub_from_source(source) + assert "ClassVar" 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 "helper" in result + assert "x = 1" 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 "def create" in result + assert "name" in result + assert "classmethod" in result From e47087b3fe35229350fb61d4f2adb64e45b68121 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 9 Apr 2026 22:50:17 -0700 Subject: [PATCH 2/8] fix golden pyi files Use the generator to generate the golden files...duh --- .../utils/pyi_generator/golden/.gitignore | 1 + .../utils/pyi_generator/golden/any_assign.pyi | 82 +++++ .../golden/classvar_and_private.pyi | 84 ++++++ .../pyi_generator/golden/custom_create.pyi | 83 +++++ .../utils/pyi_generator/golden/edge_cases.pyi | 216 +++++++++++++ .../pyi_generator/golden/inheritance.pyi | 229 ++++++++++++++ .../pyi_generator/golden/literal_types.pyi | 97 ++++++ .../pyi_generator/golden/module_level.pyi | 90 ++++++ .../golden/namespace_component.pyi | 285 ++++++++++++++++++ .../pyi_generator/golden/simple_component.pyi | 88 ++++++ .../golden/staticmethod_namespace.pyi | 100 ++++++ .../golden/string_event_annotations.pyi | 89 ++++++ .../golden/sub_package/__init__.pyi | 8 + .../golden/sub_package/widget.pyi | 79 +++++ .../golden/typed_event_handlers.pyi | 161 ++++++++++ .../utils/pyi_generator/golden/var_types.pyi | 91 ++++++ .../utils/pyi_generator/test_regression.py | 150 ++++----- 17 files changed, 1847 insertions(+), 86 deletions(-) create mode 100644 tests/units/reflex_base/utils/pyi_generator/golden/.gitignore create mode 100644 tests/units/reflex_base/utils/pyi_generator/golden/any_assign.pyi create mode 100644 tests/units/reflex_base/utils/pyi_generator/golden/classvar_and_private.pyi create mode 100644 tests/units/reflex_base/utils/pyi_generator/golden/custom_create.pyi create mode 100644 tests/units/reflex_base/utils/pyi_generator/golden/edge_cases.pyi create mode 100644 tests/units/reflex_base/utils/pyi_generator/golden/inheritance.pyi create mode 100644 tests/units/reflex_base/utils/pyi_generator/golden/literal_types.pyi create mode 100644 tests/units/reflex_base/utils/pyi_generator/golden/module_level.pyi create mode 100644 tests/units/reflex_base/utils/pyi_generator/golden/namespace_component.pyi create mode 100644 tests/units/reflex_base/utils/pyi_generator/golden/simple_component.pyi create mode 100644 tests/units/reflex_base/utils/pyi_generator/golden/staticmethod_namespace.pyi create mode 100644 tests/units/reflex_base/utils/pyi_generator/golden/string_event_annotations.pyi create mode 100644 tests/units/reflex_base/utils/pyi_generator/golden/sub_package/__init__.pyi create mode 100644 tests/units/reflex_base/utils/pyi_generator/golden/sub_package/widget.pyi create mode 100644 tests/units/reflex_base/utils/pyi_generator/golden/typed_event_handlers.pyi create mode 100644 tests/units/reflex_base/utils/pyi_generator/golden/var_types.pyi 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/any_assign.pyi b/tests/units/reflex_base/utils/pyi_generator/golden/any_assign.pyi new file mode 100644 index 00000000000..21e2bfc0ca4 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/golden/any_assign.pyi @@ -0,0 +1,82 @@ +"""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 SmallComponent(Component): + @classmethod + def create( + cls, + *children, + 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, + ) -> SmallComponent: + """Create the component. + + Args: + *children: The children of the component. + text: The text content. + 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/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/edge_cases.pyi b/tests/units/reflex_base/utils/pyi_generator/golden/edge_cases.pyi new file mode 100644 index 00000000000..901a0d3c04c --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/golden/edge_cases.pyi @@ -0,0 +1,216 @@ +"""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 NestedGenericComponent(Component): + @classmethod + def create( + cls, + *children, + numbers: Var[list[int]] | list[int] | None = None, + pairs: Var[dict[str, int]] | dict[str, int] | 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, + ) -> NestedGenericComponent: + """Create the component. + + Args: + *children: The children of the component. + numbers: A list of numbers. + pairs: Key-value pairs. + 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/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..bf63a734326 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/golden/simple_component.pyi @@ -0,0 +1,88 @@ +"""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 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..8af5776be83 --- /dev/null +++ b/tests/units/reflex_base/utils/pyi_generator/golden/var_types.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 + +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, + 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. + 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 index c4bbfda6962..27717479dc2 100644 --- a/tests/units/reflex_base/utils/pyi_generator/test_regression.py +++ b/tests/units/reflex_base/utils/pyi_generator/test_regression.py @@ -1,7 +1,7 @@ """Regression tests for pyi_generator. -Runs pyi_generator against a directory of curated Python files and compares -the generated .pyi stubs against a set of golden reference files. +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 @@ -19,7 +19,7 @@ import pytest -from reflex_base.utils.pyi_generator import _scan_file +from reflex_base.utils.pyi_generator import PyiGenerator _HERE = Path(__file__).resolve().parent DATASET_DIR = _HERE / "dataset" @@ -27,41 +27,15 @@ _UPDATE_CMD = "python -m tests.units.reflex_base.utils.pyi_generator --update" -DATASET_MODULES = sorted( - p - for p in DATASET_DIR.rglob("*.py") - if p.name != "__init__.py" and not p.name.startswith("_") -) - -DATASET_INIT_MODULES = sorted( - p for p in DATASET_DIR.rglob("__init__.py") if p != DATASET_DIR / "__init__.py" -) - -ALL_DATASET_FILES = DATASET_MODULES + DATASET_INIT_MODULES - -def _golden_path_for(module_path: Path) -> Path: +def _golden_path_for(source_path: Path) -> Path: """Map a dataset .py file to its golden .pyi counterpart.""" - relative = module_path.relative_to(DATASET_DIR) + relative = source_path.relative_to(DATASET_DIR) return GOLDEN_DIR / relative.with_suffix(".pyi") -def _generate_stub(module_path: Path) -> str | None: - """Generate a .pyi stub for a single dataset module and return its content. - - Returns None if the generator decides no stub is needed. - """ - result = _scan_file(module_path) - if result is None: - return None - pyi_path = Path(result[0]) - content = pyi_path.read_text() - pyi_path.unlink(missing_ok=True) - return content - - def _normalize_stub(content: str) -> str: - """Strip the file-specific header (path line) so golden files are portable.""" + """Replace the absolute-path docstring header so golden files are portable.""" lines = content.splitlines(keepends=True) normalized: list[str] = [] for line in lines: @@ -72,11 +46,26 @@ def _normalize_stub(content: str) -> str: return "".join(normalized) -def update_golden_files() -> list[str]: - """Regenerate all golden .pyi files from the dataset. +def _run_generator() -> dict[Path, str]: + """Run PyiGenerator.scan_all on the dataset dir and collect results. - Returns a list of updated file names. + Generated .pyi files are read back and then removed from the dataset + tree so the working copy stays clean. """ + gen = PyiGenerator() + gen.scan_all([str(DATASET_DIR)]) + + results: dict[Path, str] = {} + for pyi_str, _hash in gen.written_files: + pyi_path = Path(pyi_str) + source_path = pyi_path.with_suffix(".py") + results[source_path] = pyi_path.read_text() + pyi_path.unlink(missing_ok=True) + return results + + +def update_golden_files() -> list[str]: + """Regenerate all golden .pyi files from the dataset.""" GOLDEN_DIR.mkdir(parents=True, exist_ok=True) for subdir in DATASET_DIR.rglob("*"): if subdir.is_dir() and subdir != DATASET_DIR: @@ -84,18 +73,16 @@ def update_golden_files() -> list[str]: parents=True, exist_ok=True ) + generated = _run_generator() updated: list[str] = [] - for module_path in ALL_DATASET_FILES: - content = _generate_stub(module_path) - if content is None: - continue - golden = _golden_path_for(module_path) - normalized = _normalize_stub(content) - golden.write_text(normalized) - updated.append(str(golden.relative_to(_HERE))) - print(f" updated: {golden.relative_to(_HERE)}") - - expected_goldens = {_golden_path_for(p) for p in ALL_DATASET_FILES} + 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() @@ -104,44 +91,40 @@ def update_golden_files() -> list[str]: return updated -def _get_test_cases() -> list[tuple[str, Path]]: - """Build parameterized test cases: (test_id, module_path).""" - cases = [] - for module_path in ALL_DATASET_FILES: - golden = _golden_path_for(module_path) - if golden.exists(): - test_id = str(module_path.relative_to(DATASET_DIR)).replace("/", ".") - cases.append((test_id, module_path)) - return cases +@pytest.fixture(scope="module") +def generated_stubs(): + """Run the generator once for the whole test module.""" + return _run_generator() -@pytest.fixture(autouse=True, scope="module") -def _ensure_dataset_importable(): - """Ensure the dataset directory is on sys.path so modules can be imported.""" - repo_root = _HERE.parent.parent.parent.parent.parent - if str(repo_root) not in sys.path: - sys.path.insert(0, str(repo_root)) +def _existing_golden_cases() -> list[tuple[str, Path]]: + """Build test IDs from existing golden files (no generator run needed).""" + 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( - "module_path", - [p for _, p in _get_test_cases()], - ids=[tid for tid, _ in _get_test_cases()], + "source_path", + [p for _, p in _existing_golden_cases()], + ids=[tid for tid, _ in _existing_golden_cases()], ) -def test_pyi_golden(module_path: Path): +def test_pyi_golden(generated_stubs: dict[Path, str], source_path: Path): """Compare generated .pyi output against golden reference.""" - golden_path = _golden_path_for(module_path) - if not golden_path.exists(): - pytest.skip(f"No golden file for {module_path.name}. Run `{_UPDATE_CMD}` to generate.") + golden_path = _golden_path_for(source_path) - generated = _generate_stub(module_path) - if generated is None: + content = generated_stubs.get(source_path) + if content is None: pytest.fail( - f"pyi_generator produced no output for {module_path.name}, " + f"pyi_generator produced no output for {source_path.name}, " f"but a golden file exists at {golden_path}" ) - normalized = _normalize_stub(generated) + normalized = _normalize_stub(content) expected = golden_path.read_text() if normalized != expected: @@ -151,16 +134,15 @@ def test_pyi_golden(module_path: Path): fromfile=f"golden/{golden_path.name}", tofile=f"generated/{golden_path.name}", ) - diff_text = "".join(diff) pytest.fail( - f"Generated stub differs from golden reference for {module_path.name}.\n" - f"Run `{_UPDATE_CMD}` to regenerate.\n\n{diff_text}" + 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(): +def test_no_extra_golden_files(generated_stubs: dict[Path, str]): """Ensure no golden files exist without corresponding dataset sources.""" - expected_goldens = {_golden_path_for(p) for p in ALL_DATASET_FILES} + 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. " @@ -181,18 +163,14 @@ def main(): 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 module_path in ALL_DATASET_FILES: - golden_path = _golden_path_for(module_path) + for source_path, content in sorted(generated.items()): + golden_path = _golden_path_for(source_path) if not golden_path.exists(): continue - generated = _generate_stub(module_path) - if generated is None: - failures.append(f" {module_path.name}: no output generated") - continue - normalized = _normalize_stub(generated) - if normalized != golden_path.read_text(): - failures.append(f" {module_path.name}: differs from golden") + if _normalize_stub(content) != golden_path.read_text(): + failures.append(f" {source_path.name}: differs from golden") if failures: print("FAILED:") print("\n".join(failures)) From 05247c33a1c482f164686930affc1f4e0c10dd92 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 9 Apr 2026 23:08:30 -0700 Subject: [PATCH 3/8] pre-commit fixes --- pyproject.toml | 1 + .../dataset/classvar_and_private.py | 1 - .../pyi_generator/dataset/custom_create.py | 4 +- .../utils/pyi_generator/dataset/edge_cases.py | 2 - .../pyi_generator/dataset/inheritance.py | 2 - .../pyi_generator/dataset/literal_types.py | 2 +- .../pyi_generator/dataset/module_level.py | 2 +- .../dataset/namespace_component.py | 2 - .../pyi_generator/dataset/simple_component.py | 2 - .../dataset/staticmethod_namespace.py | 3 - .../dataset/string_event_annotations.py | 2 - .../dataset/typed_event_handlers.py | 2 - .../utils/pyi_generator/test_regression.py | 69 +++++++++++++++--- .../utils/pyi_generator/test_unit.py | 73 +++++++++++-------- 14 files changed, 107 insertions(+), 60 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8c7b1c84c23..719fe8357fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -225,6 +225,7 @@ lint.flake8-bugbear.extend-immutable-calls = [ "T", "N", ] +"tests/units/reflex_base/utils/pyi_generator/dataset/*.py" = ["DOC201"] "benchmarks/*.py" = ["ANN001", "D100", "D103", "D104", "B018", "PERF", "T", "N"] "*/.templates/*.py" = ["D100", "D103", "D104"] "*.pyi" = ["D301", "D415", "D417", "D418", "E742", "N", "PGH"] 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 index c8a93df4443..70221c86cec 100644 --- 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 @@ -42,7 +42,6 @@ class StrictComponent(Component): def _internal_helper(self) -> None: """Private method, should be removed.""" - pass def render_item(self) -> str: """Public method, body should be blanked.""" 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 index 22384395673..65b1472c03b 100644 --- a/tests/units/reflex_base/utils/pyi_generator/dataset/custom_create.py +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/custom_create.py @@ -22,7 +22,9 @@ class CustomCreateComponent(Component): amount: Var[int] = field(doc="The amount.") @classmethod - def create(cls, *children: Any, custom_kwarg: str = "default", **props: Any) -> "CustomCreateComponent": + def create( + cls, *children: Any, custom_kwarg: str = "default", **props: Any + ) -> "CustomCreateComponent": """Create a custom component with extra kwargs. Args: diff --git a/tests/units/reflex_base/utils/pyi_generator/dataset/edge_cases.py b/tests/units/reflex_base/utils/pyi_generator/dataset/edge_cases.py index d634c98ccee..2aa806f0bea 100644 --- a/tests/units/reflex_base/utils/pyi_generator/dataset/edge_cases.py +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/edge_cases.py @@ -17,8 +17,6 @@ class EmptyComponent(Component): """A component with no custom props at all.""" - pass - class EventOnlyComponent(Component): """A component with only custom event handlers, no data 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 index b49a1e30dec..da31cde1ade 100644 --- a/tests/units/reflex_base/utils/pyi_generator/dataset/inheritance.py +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/inheritance.py @@ -6,8 +6,6 @@ - Multiple levels of inheritance """ -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 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 index 6f7f3270835..7ff76d70669 100644 --- a/tests/units/reflex_base/utils/pyi_generator/dataset/literal_types.py +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/literal_types.py @@ -6,7 +6,7 @@ - Var[Literal[...]] expansion (Var union with inner literal) """ -from typing import Any, Literal +from typing import Literal from reflex_base.components.component import Component, field from reflex_base.vars.base import Var 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 index b4f5cf86a29..376fd167dd6 100644 --- a/tests/units/reflex_base/utils/pyi_generator/dataset/module_level.py +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/module_level.py @@ -8,7 +8,7 @@ - Combined: component + module-level items """ -from typing import Any, Literal +from typing import Literal from reflex_base.components.component import Component, field from reflex_base.vars.base import Var 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 index 05223612c75..f7ce43da7ed 100644 --- a/tests/units/reflex_base/utils/pyi_generator/dataset/namespace_component.py +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/namespace_component.py @@ -7,8 +7,6 @@ - Module-level namespace instance assignment """ -from typing import Any - from reflex_base.components.component import Component, ComponentNamespace, field from reflex_base.vars.base import Var 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 index 6fcb9805aa0..9418f2158c2 100644 --- a/tests/units/reflex_base/utils/pyi_generator/dataset/simple_component.py +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/simple_component.py @@ -9,8 +9,6 @@ - Module docstring removal in stubs """ -from typing import Any - from reflex_base.components.component import Component, field from reflex_base.vars.base import Var 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 index 1076c80f484..cce13ff61cd 100644 --- a/tests/units/reflex_base/utils/pyi_generator/dataset/staticmethod_namespace.py +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/staticmethod_namespace.py @@ -6,8 +6,6 @@ - The fallback where __call__.__func__.__name__ != "create" """ -from typing import Any - from reflex_base.components.component import Component, ComponentNamespace, field from reflex_base.event import EventSpec from reflex_base.vars.base import Var @@ -31,7 +29,6 @@ def send_notification(message: str, level: str = "info") -> EventSpec: Returns: The event spec. """ - ... class Notify(ComponentNamespace): 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 index f96dbf306e0..63622288da7 100644 --- 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 @@ -6,8 +6,6 @@ - Empty tuple[()], single arg tuple[str], multi-arg tuple[str, int] """ -from typing import Any - from reflex_base.components.component import Component, field from reflex_base.event import EventHandler from reflex_base.vars.base import Var 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 index a8214acd5e4..ed436acbc73 100644 --- 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 @@ -9,8 +9,6 @@ - String-based tuple return annotations """ -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 diff --git a/tests/units/reflex_base/utils/pyi_generator/test_regression.py b/tests/units/reflex_base/utils/pyi_generator/test_regression.py index 27717479dc2..53933f02904 100644 --- a/tests/units/reflex_base/utils/pyi_generator/test_regression.py +++ b/tests/units/reflex_base/utils/pyi_generator/test_regression.py @@ -18,7 +18,6 @@ from pathlib import Path import pytest - from reflex_base.utils.pyi_generator import PyiGenerator _HERE = Path(__file__).resolve().parent @@ -29,13 +28,27 @@ def _golden_path_for(source_path: Path) -> Path: - """Map a dataset .py file to its golden .pyi counterpart.""" + """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.""" + """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: @@ -51,6 +64,9 @@ def _run_generator() -> dict[Path, str]: Generated .pyi files are read back and then removed from the dataset tree so the working copy stays clean. + + Returns: + A mapping from dataset source .py files to their generated .pyi content. """ gen = PyiGenerator() gen.scan_all([str(DATASET_DIR)]) @@ -65,7 +81,11 @@ def _run_generator() -> dict[Path, str]: def update_golden_files() -> list[str]: - """Regenerate all golden .pyi files from the dataset.""" + """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: @@ -93,12 +113,20 @@ def update_golden_files() -> list[str]: @pytest.fixture(scope="module") def generated_stubs(): - """Run the generator once for the whole test module.""" + """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).""" + """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) @@ -114,7 +142,12 @@ def _existing_golden_cases() -> list[tuple[str, Path]]: 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.""" + """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) @@ -141,7 +174,11 @@ def test_pyi_golden(generated_stubs: dict[Path, str], source_path: Path): def test_no_extra_golden_files(generated_stubs: dict[Path, str]): - """Ensure no golden files exist without corresponding dataset sources.""" + """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, ( @@ -152,14 +189,24 @@ def test_no_extra_golden_files(generated_stubs: dict[Path, str]): 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).") + 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( + 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...") diff --git a/tests/units/reflex_base/utils/pyi_generator/test_unit.py b/tests/units/reflex_base/utils/pyi_generator/test_unit.py index 8dbe92fdb0e..6dc9d646c66 100644 --- a/tests/units/reflex_base/utils/pyi_generator/test_unit.py +++ b/tests/units/reflex_base/utils/pyi_generator/test_unit.py @@ -12,11 +12,10 @@ import sys import types as builtin_types import typing -from typing import Any, ClassVar, Literal, Optional, Union +from typing import Any, Literal, Optional, Union import pytest - -from reflex_base.components.component import Component, field +from reflex_base.components.component import Component from reflex_base.utils.pyi_generator import ( StubGenerator, _get_type_hint, @@ -30,7 +29,7 @@ def test_is_union_typing_union(): - assert _is_union(Union[str, int]) is True + assert _is_union(Union[str, int]) is True # noqa: UP007 def test_is_union_pipe_union(): @@ -38,7 +37,7 @@ def test_is_union_pipe_union(): def test_is_union_optional_is_union(): - assert _is_union(Optional[str]) is True + assert _is_union(Optional[str]) is True # noqa: UP045 def test_is_union_plain_type(): @@ -62,7 +61,7 @@ def test_is_optional_none_type(): def test_is_optional_optional_type(): - assert _is_optional(Optional[str]) is True + assert _is_optional(Optional[str]) is True # noqa: UP045 def test_is_optional_union_with_none(): @@ -105,9 +104,14 @@ def test_safe_issubclass_none(): assert _safe_issubclass(None, int) is False -@pytest.fixture() +@pytest.fixture def type_hint_globals(): - """Provide a type_hint_globals dict with common types.""" + """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, @@ -141,17 +145,17 @@ def test_get_type_hint_simple_not_optional(type_hint_globals): def test_get_type_hint_optional_union(type_hint_globals): - assert _get_type_hint(Optional[str], type_hint_globals) == "str | None" + 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) + result = _get_type_hint(Union[str, int], type_hint_globals, is_optional=False) # noqa: UP007 assert "int" in result assert "str" in result def test_get_type_hint_union_with_none(type_hint_globals): - result = _get_type_hint(Union[str, int, None], type_hint_globals) + result = _get_type_hint(Union[str, int, None], type_hint_globals) # noqa: UP007 assert result.endswith("| None") assert "int" in result assert "str" in result @@ -173,7 +177,9 @@ def test_get_type_hint_var_union_expansion(type_hint_globals): def test_get_type_hint_literal(type_hint_globals): - result = _get_type_hint(Literal["a", "b", "c"], type_hint_globals, is_optional=False) + result = _get_type_hint( + Literal["a", "b", "c"], type_hint_globals, is_optional=False + ) assert "Literal" in result assert "'a'" in result assert "'b'" in result @@ -208,7 +214,7 @@ def test_type_to_ast_literal(): def test_type_to_ast_union(): - node = type_to_ast(Union[str, int], Component) + node = type_to_ast(Union[str, int], Component) # noqa: UP007 assert isinstance(node, ast.Subscript) unparsed = ast.unparse(node) assert "str" in unparsed @@ -235,7 +241,14 @@ def test_type_to_ast_nested_generic(): def _generate_stub_from_source(source: str) -> str: - """Parse source, run StubGenerator, return unparsed result.""" + """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}" @@ -280,7 +293,7 @@ def _generate_stub_from_source(source: str) -> str: def test_stub_private_method_removed(): - source = ''' + source = """ from reflex_base.components.component import Component from reflex_base.vars.base import Var @@ -288,7 +301,7 @@ class Foo(Component): x: Var[str] def _hidden(self): pass def visible(self): return 1 -''' +""" result = _generate_stub_from_source(source) assert "_hidden" not in result assert "visible" in result @@ -307,13 +320,13 @@ class Bar(Component): def test_stub_future_import_removed(): - source = '''from __future__ import annotations + 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 @@ -332,20 +345,20 @@ class DocComponent(Component): def test_stub_non_annotated_assignment_removed(): - source = ''' + 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 def test_stub_any_assignment_preserved(): - source = ''' + source = """ from typing import Any from reflex_base.components.component import Component from reflex_base.vars.base import Var @@ -354,13 +367,13 @@ def test_stub_any_assignment_preserved(): 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 = ''' + source = """ from reflex_base.components.component import Component from reflex_base.vars.base import Var @@ -368,14 +381,14 @@ def test_stub_annotated_assignment_value_blanked(): class ModeComp(Component): val: Var[str] -''' +""" result = _generate_stub_from_source(source) assert "mode: str" in result assert '"default"' not in result def test_stub_classvar_preserved(): - source = ''' + source = """ from typing import ClassVar from reflex_base.components.component import Component from reflex_base.vars.base import Var @@ -383,13 +396,13 @@ def test_stub_classvar_preserved(): class CVComp(Component): _valid_children: ClassVar[list[str]] = ["A"] val: Var[str] -''' +""" result = _generate_stub_from_source(source) assert "ClassVar" in result def test_stub_public_function_body_blanked(): - source = ''' + source = """ from reflex_base.components.component import Component from reflex_base.vars.base import Var @@ -399,20 +412,20 @@ def helper(self) -> str: x = 1 y = 2 return str(x + y) -''' +""" result = _generate_stub_from_source(source) assert "helper" in result assert "x = 1" not in result def test_stub_create_method_generated(): - source = ''' + 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 "def create" in result assert "name" in result From 0969ef268a33bfed8155abcd128719b051f24cb3 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 9 Apr 2026 23:20:44 -0700 Subject: [PATCH 4/8] ruff: ignore golden pyi files Fix additional pre-commit issues --- pyproject.toml | 1 + .../utils/pyi_generator/dataset/classvar_and_private.py | 4 ++-- .../utils/pyi_generator/dataset/staticmethod_namespace.py | 1 + .../utils/pyi_generator/dataset/string_event_annotations.py | 2 +- .../reflex_base/utils/pyi_generator/golden/edge_cases.pyi | 1 - tests/units/reflex_base/utils/pyi_generator/test_unit.py | 3 ++- 6 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 719fe8357fe..900c4ad8296 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/pyi_generator/dataset/classvar_and_private.py b/tests/units/reflex_base/utils/pyi_generator/dataset/classvar_and_private.py index 70221c86cec..4b863ab281b 100644 --- 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 @@ -22,9 +22,9 @@ class StrictComponent(Component): _hidden_state: ClassVar[dict[str, Any]] = {} - tag: str = "div" + tag: str | None = "div" - library: str = "some-lib" + library: str | None = "some-lib" # The valid children for this component. _valid_children: ClassVar[list[str]] = ["ChildA", "ChildB"] 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 index cce13ff61cd..3f5fc00f725 100644 --- a/tests/units/reflex_base/utils/pyi_generator/dataset/staticmethod_namespace.py +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/staticmethod_namespace.py @@ -29,6 +29,7 @@ def send_notification(message: str, level: str = "info") -> EventSpec: Returns: The event spec. """ + return EventSpec() # type: ignore[call-arg] class Notify(ComponentNamespace): 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 index 63622288da7..f73b1f9dbf7 100644 --- 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 @@ -13,7 +13,7 @@ def on_empty_handler() -> "tuple[()]": """Handler returning empty tuple.""" - return ((),) + return () def on_string_handler(value: Var[str]) -> "tuple[Var[str]]": diff --git a/tests/units/reflex_base/utils/pyi_generator/golden/edge_cases.pyi b/tests/units/reflex_base/utils/pyi_generator/golden/edge_cases.pyi index 901a0d3c04c..126b2d01e29 100644 --- a/tests/units/reflex_base/utils/pyi_generator/golden/edge_cases.pyi +++ b/tests/units/reflex_base/utils/pyi_generator/golden/edge_cases.pyi @@ -12,7 +12,6 @@ from reflex_base.vars.base import Var from reflex_components_core.core.breakpoints import Breakpoints class EmptyComponent(Component): - @classmethod def create( cls, diff --git a/tests/units/reflex_base/utils/pyi_generator/test_unit.py b/tests/units/reflex_base/utils/pyi_generator/test_unit.py index 6dc9d646c66..5df4a1834ee 100644 --- a/tests/units/reflex_base/utils/pyi_generator/test_unit.py +++ b/tests/units/reflex_base/utils/pyi_generator/test_unit.py @@ -12,6 +12,7 @@ import sys import types as builtin_types import typing +from types import SimpleNamespace from typing import Any, Literal, Optional, Union import pytest @@ -275,7 +276,7 @@ def _generate_stub_from_source(source: str) -> str: ): obj.__module__ = module_name - classes = { + classes: dict[str, type[Component] | type[SimpleNamespace]] = { name: obj for name, obj in vars(mod).items() if isinstance(obj, type) From 482df1d603a9d068aae98313c743d1736d00b221 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 9 Apr 2026 23:31:27 -0700 Subject: [PATCH 5/8] remove redundant files from the test suite clean up assertions --- .../utils/pyi_generator/dataset/any_assign.py | 24 -- .../utils/pyi_generator/dataset/edge_cases.py | 37 --- .../pyi_generator/dataset/simple_component.py | 10 + .../utils/pyi_generator/dataset/var_types.py | 22 +- .../utils/pyi_generator/golden/any_assign.pyi | 82 ------- .../utils/pyi_generator/golden/edge_cases.pyi | 215 ------------------ .../pyi_generator/golden/simple_component.pyi | 3 + .../utils/pyi_generator/golden/var_types.pyi | 134 +++++++++++ .../utils/pyi_generator/test_unit.py | 73 ++++-- 9 files changed, 215 insertions(+), 385 deletions(-) delete mode 100644 tests/units/reflex_base/utils/pyi_generator/dataset/any_assign.py delete mode 100644 tests/units/reflex_base/utils/pyi_generator/dataset/edge_cases.py delete mode 100644 tests/units/reflex_base/utils/pyi_generator/golden/any_assign.pyi delete mode 100644 tests/units/reflex_base/utils/pyi_generator/golden/edge_cases.pyi diff --git a/tests/units/reflex_base/utils/pyi_generator/dataset/any_assign.py b/tests/units/reflex_base/utils/pyi_generator/dataset/any_assign.py deleted file mode 100644 index 833f8407c47..00000000000 --- a/tests/units/reflex_base/utils/pyi_generator/dataset/any_assign.py +++ /dev/null @@ -1,24 +0,0 @@ -"""Module with assignments to Any and dunder assignments. - -This module tests: -- visit_Assign: assignment to `Any` is preserved -- visit_Assign: dunder tuple assignments are removed (lazy_loader.attach pattern) -- Non-Component classes are not processed (no create() added) -""" - -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 SmallComponent(Component): - """A small component.""" - - text: Var[str] = field(doc="The text content.") diff --git a/tests/units/reflex_base/utils/pyi_generator/dataset/edge_cases.py b/tests/units/reflex_base/utils/pyi_generator/dataset/edge_cases.py deleted file mode 100644 index 2aa806f0bea..00000000000 --- a/tests/units/reflex_base/utils/pyi_generator/dataset/edge_cases.py +++ /dev/null @@ -1,37 +0,0 @@ -"""Components with edge case type annotations. - -This module tests: -- Var[list[int]] type hint (nested generic inside Var) -- Optional[Var[str]] (explicit Optional wrapping) -- Component with no 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 NestedGenericComponent(Component): - """A component with nested generic Var types.""" - - # Var with nested generic. - numbers: Var[list[int]] = field(doc="A list of numbers.") - - pairs: Var[dict[str, int]] = field(doc="Key-value pairs.") - - nested: Var[list[dict[str, Any]]] = field(doc="Nested structures.") 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 index 9418f2158c2..74efdfa0465 100644 --- a/tests/units/reflex_base/utils/pyi_generator/dataset/simple_component.py +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/simple_component.py @@ -7,11 +7,21 @@ - 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.""" 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 index 9ba5be23964..dc858c690d1 100644 --- a/tests/units/reflex_base/utils/pyi_generator/dataset/var_types.py +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/var_types.py @@ -1,19 +1,32 @@ -"""Component with various Var[T] prop types. +"""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 -- Optional props (None default) -- Complex nested types: Var[list[str]], Var[dict[str, Any]] -- Non-Var props (plain type annotations) +- 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.""" @@ -29,3 +42,4 @@ class VarTypesComponent(Component): # 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/any_assign.pyi b/tests/units/reflex_base/utils/pyi_generator/golden/any_assign.pyi deleted file mode 100644 index 21e2bfc0ca4..00000000000 --- a/tests/units/reflex_base/utils/pyi_generator/golden/any_assign.pyi +++ /dev/null @@ -1,82 +0,0 @@ -"""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 SmallComponent(Component): - @classmethod - def create( - cls, - *children, - 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, - ) -> SmallComponent: - """Create the component. - - Args: - *children: The children of the component. - text: The text content. - 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/edge_cases.pyi b/tests/units/reflex_base/utils/pyi_generator/golden/edge_cases.pyi deleted file mode 100644 index 126b2d01e29..00000000000 --- a/tests/units/reflex_base/utils/pyi_generator/golden/edge_cases.pyi +++ /dev/null @@ -1,215 +0,0 @@ -"""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 NestedGenericComponent(Component): - @classmethod - def create( - cls, - *children, - numbers: Var[list[int]] | list[int] | None = None, - pairs: Var[dict[str, int]] | dict[str, int] | 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, - ) -> NestedGenericComponent: - """Create the component. - - Args: - *children: The children of the component. - numbers: A list of numbers. - pairs: Key-value pairs. - 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/golden/simple_component.pyi b/tests/units/reflex_base/utils/pyi_generator/golden/simple_component.pyi index bf63a734326..cc88208502c 100644 --- a/tests/units/reflex_base/utils/pyi_generator/golden/simple_component.pyi +++ b/tests/units/reflex_base/utils/pyi_generator/golden/simple_component.pyi @@ -11,6 +11,9 @@ 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 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 index 8af5776be83..f8a98a19ce3 100644 --- a/tests/units/reflex_base/utils/pyi_generator/golden/var_types.pyi +++ b/tests/units/reflex_base/utils/pyi_generator/golden/var_types.pyi @@ -11,6 +11,138 @@ 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( @@ -23,6 +155,7 @@ class VarTypesComponent(Component): 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]] @@ -62,6 +195,7 @@ class VarTypesComponent(Component): 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. diff --git a/tests/units/reflex_base/utils/pyi_generator/test_unit.py b/tests/units/reflex_base/utils/pyi_generator/test_unit.py index 5df4a1834ee..4bbf5107b8a 100644 --- a/tests/units/reflex_base/utils/pyi_generator/test_unit.py +++ b/tests/units/reflex_base/utils/pyi_generator/test_unit.py @@ -151,40 +151,34 @@ def test_get_type_hint_optional_union(type_hint_globals): 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 "int" in result - assert "str" in result + 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.endswith("| None") - assert "int" in result - assert "str" in result + assert result == "int | str | None" def test_get_type_hint_var_expansion(type_hint_globals): - """Var[str] should expand to include both Var[str] and str.""" + """Var[str] should expand to Var[str] | str.""" result = _get_type_hint(Var[str], type_hint_globals, is_optional=False) - assert "Var[str]" in result - assert "str" in result + assert result == "Var[str] | str" def test_get_type_hint_var_union_expansion(type_hint_globals): - """Var[str | int] should expand to Var[str | int] | str | int.""" + """Var[str | int] should expand to include Var, str, and int.""" result = _get_type_hint(Var[str | int], type_hint_globals, is_optional=False) - assert "Var[" in result - assert "str" in result - assert "int" in result + 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 "Literal" in result - assert "'a'" in result - assert "'b'" in result - assert "'c'" in result + assert result == "Literal['a', 'b', 'c']" def test_type_to_ast_none_type(): @@ -304,8 +298,10 @@ def _hidden(self): pass def visible(self): return 1 """ result = _generate_stub_from_source(source) - assert "_hidden" not in result - assert "visible" in result + 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(): @@ -318,6 +314,9 @@ class Bar(Component): ''' 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(): @@ -330,6 +329,8 @@ class Baz(Component): """ 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(): @@ -343,6 +344,7 @@ class DocComponent(Component): ''' 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(): @@ -356,6 +358,7 @@ class AssignComp(Component): """ result = _generate_stub_from_source(source) assert "some_const" not in result + assert "hello" not in result def test_stub_any_assignment_preserved(): @@ -385,7 +388,9 @@ class ModeComp(Component): """ 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(): @@ -395,11 +400,28 @@ def test_stub_classvar_preserved(): 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) - assert "ClassVar" in result + # Private ClassVar annotations are stripped entirely. + assert "_valid_children" not in result def test_stub_public_function_body_blanked(): @@ -415,8 +437,9 @@ def helper(self) -> str: return str(x + y) """ result = _generate_stub_from_source(source) - assert "helper" in result + 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(): @@ -428,6 +451,10 @@ class CreateComp(Component): name: Var[str] = field(doc="The name.") """ result = _generate_stub_from_source(source) - assert "def create" in result - assert "name" in result - assert "classmethod" in result + 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 From 7895cf713d46f25ac396abb48ff3453398922de8 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 9 Apr 2026 23:36:08 -0700 Subject: [PATCH 6/8] fix ruff issues in dataset --- pyproject.toml | 1 - .../pyi_generator/dataset/simple_component.py | 12 +++++++-- .../dataset/string_event_annotations.py | 25 ++++++++++++++++--- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 900c4ad8296..7ab5355c99b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -226,7 +226,6 @@ lint.flake8-bugbear.extend-immutable-calls = [ "T", "N", ] -"tests/units/reflex_base/utils/pyi_generator/dataset/*.py" = ["DOC201"] "benchmarks/*.py" = ["ANN001", "D100", "D103", "D104", "B018", "PERF", "T", "N"] "*/.templates/*.py" = ["D100", "D103", "D104"] "*.pyi" = ["D301", "D415", "D417", "D418", "E742", "N", "PGH"] 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 index 74efdfa0465..7687d780a07 100644 --- a/tests/units/reflex_base/utils/pyi_generator/dataset/simple_component.py +++ b/tests/units/reflex_base/utils/pyi_generator/dataset/simple_component.py @@ -42,9 +42,17 @@ class SimpleComponent(Component): ) def _private_method(self): - """This should not appear in the stub.""" + """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.""" + """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/string_event_annotations.py b/tests/units/reflex_base/utils/pyi_generator/dataset/string_event_annotations.py index f73b1f9dbf7..f1e3bd61189 100644 --- 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 @@ -12,17 +12,36 @@ def on_empty_handler() -> "tuple[()]": - """Handler returning empty 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.""" + """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.""" + """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) From be2fd338dc02320f63ea841b48411a7ee26c72be Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Fri, 10 Apr 2026 08:50:02 -0700 Subject: [PATCH 7/8] greptile feedback addressed --- .../utils/pyi_generator/test_regression.py | 47 ++++++++++++++++--- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/tests/units/reflex_base/utils/pyi_generator/test_regression.py b/tests/units/reflex_base/utils/pyi_generator/test_regression.py index 53933f02904..f52ca4ec07d 100644 --- a/tests/units/reflex_base/utils/pyi_generator/test_regression.py +++ b/tests/units/reflex_base/utils/pyi_generator/test_regression.py @@ -63,7 +63,8 @@ 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. + 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. @@ -71,12 +72,15 @@ def _run_generator() -> dict[Path, str]: gen = PyiGenerator() gen.scan_all([str(DATASET_DIR)]) - results: dict[Path, str] = {} - for pyi_str, _hash in gen.written_files: - pyi_path = Path(pyi_str) - source_path = pyi_path.with_suffix(".py") - results[source_path] = pyi_path.read_text() - pyi_path.unlink(missing_ok=True) + 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 @@ -187,6 +191,28 @@ def test_no_extra_golden_files(generated_stubs: dict[Path, str]): ) +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( @@ -215,9 +241,16 @@ def main(): 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)) From b7441226ec63830da2d344a6817affcc9ce39b2a Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Fri, 10 Apr 2026 08:51:01 -0700 Subject: [PATCH 8/8] fix pre-commit --- .../utils/pyi_generator/dataset/classvar_and_private.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 index 4b863ab281b..e9aa27b9243 100644 --- 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 @@ -44,5 +44,9 @@ def _internal_helper(self) -> None: """Private method, should be removed.""" def render_item(self) -> str: - """Public method, body should be blanked.""" + """Public method, body should be blanked. + + Returns: + The rendered item. + """ return f"
{self.visible_prop}
"