Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
Empty file.
Empty file.
10 changes: 10 additions & 0 deletions tests/units/reflex_base/utils/pyi_generator/__main__.py
Original file line number Diff line number Diff line change
@@ -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()
Original file line number Diff line number Diff line change
@@ -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.
"""
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Component with ClassVar, private attrs, and excluded props.

This module tests:
- ClassVar annotations are preserved (not turned into create() kwargs)
- Private annotated attributes (_foo) are removed from stubs
- EXCLUDED_PROPS (like tag, library, etc.) are not in create()
- Private methods are removed
- Non-annotated assignments inside Component classes are removed
- AnnAssign with value is blanked (value set to None)
"""

from typing import Any, ClassVar

from reflex_base.components.component import Component, field
from reflex_base.vars.base import Var


class StrictComponent(Component):
"""A component with ClassVar, private, and excluded props."""

_internal_counter: int = 0

_hidden_state: ClassVar[dict[str, Any]] = {}

tag: str | None = "div"

library: str | None = "some-lib"

# The valid children for this component.
_valid_children: ClassVar[list[str]] = ["ChildA", "ChildB"]

# The memoization mode.
_memoization_mode: ClassVar[Any] = None

# A public prop that should appear in create().
visible_prop: Var[str] = field(doc="A prop visible in the stub.")

# Another public prop.
size: Var[int] = field(doc="The size of the component.")

some_constant = "not_a_prop"

def _internal_helper(self) -> None:
"""Private method, should be removed."""

def render_item(self) -> str:
"""Public method, body should be blanked.

Returns:
The rendered item.
"""
return f"<div>{self.visible_prop}</div>"
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""Component with an explicitly defined create() method.
This module tests:
- Existing create() is regenerated (replaced with generated version)
- Decorator list from original create() is preserved
- Custom kwargs on create() are included
"""

from typing import Any

from reflex_base.components.component import Component, field
from reflex_base.vars.base import Var


class CustomCreateComponent(Component):
"""A component that defines its own create method."""

# A label prop.
label: Var[str] = field(doc="Display label.")

# A numeric value.
amount: Var[int] = field(doc="The amount.")

@classmethod
def create(
cls, *children: Any, custom_kwarg: str = "default", **props: Any
) -> "CustomCreateComponent":
"""Create a custom component with extra kwargs.
Args:
*children: The child components.
custom_kwarg: A custom keyword argument.
**props: Additional properties.
Returns:
The component instance.
"""
return super().create(*children, **props)
39 changes: 39 additions & 0 deletions tests/units/reflex_base/utils/pyi_generator/dataset/inheritance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""Components with inheritance hierarchy.

This module tests:
- Props from parent classes appear in create() via MRO traversal
- Overridden props are not duplicated
- Multiple levels of inheritance
"""

from reflex_base.components.component import Component, field
from reflex_base.event import EventHandler, passthrough_event_spec
from reflex_base.vars.base import Var


class BaseWidget(Component):
"""A base widget with common props."""

# Widget color.
color: Var[str] = field(doc="The widget color.")

# Whether the widget is disabled.
disabled: Var[bool] = field(doc="Whether the widget is disabled.")


class InteractiveWidget(BaseWidget):
"""An interactive widget extending BaseWidget."""

# The placeholder text.
placeholder: Var[str] = field(doc="Placeholder text.")

on_value_change: EventHandler[passthrough_event_spec(str)] = field(
doc="Fired when value changes.",
)


class FancyWidget(InteractiveWidget):
"""A fancy widget with extra styling, three levels deep."""

# Border radius.
border_radius: Var[str] = field(doc="The border radius.")
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Component with Literal type props.

This module tests:
- Literal type annotations on props
- Module-level Literal type aliases
- Var[Literal[...]] expansion (Var union with inner literal)
"""

from typing import Literal

from reflex_base.components.component import Component, field
from reflex_base.vars.base import Var

LiteralSize = Literal["sm", "md", "lg", "xl"]
LiteralVariant = Literal["solid", "outline", "ghost"]
LiteralOrientation = Literal["horizontal", "vertical"]


class ThemedComponent(Component):
"""A component with literal-typed props."""

# The size of the component.
size: Var[LiteralSize] = field(doc="Component size.")

# The variant style.
variant: Var[LiteralVariant] = field(doc="Visual variant.")

orientation: Var[LiteralOrientation]

# A direct Literal annotation without alias.
color_scheme: Var[Literal["red", "green", "blue"]]
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""Module with module-level functions, constants, and type aliases.

This module tests:
- Module-level function body blanking
- Module-level annotated assignments (value blanked)
- Module-level non-annotated assignments (removed)
- Type alias preservation
- Combined: component + module-level items
"""

from typing import Literal

from reflex_base.components.component import Component, field
from reflex_base.vars.base import Var

LiteralMode = Literal["light", "dark", "system"]

# A module-level constant (non-annotated, should be removed).
DEFAULT_TIMEOUT = 30

# Annotated module-level var (value should be blanked).
current_mode: LiteralMode = "system"


def helper_function(x: int, y: int) -> int:
"""Add two numbers.

Args:
x: First number.
y: Second number.

Returns:
The sum.
"""
return x + y


def another_helper() -> None:
"""Do nothing important."""
print("side effect")


class ModuleComponent(Component):
"""A component defined alongside module-level items."""

mode: Var[LiteralMode] = field(doc="The display mode.")

timeout: Var[int] = field(doc="Timeout in seconds.")
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Components using ComponentNamespace pattern.

This module tests:
- ComponentNamespace with __call__ = staticmethod(SomeComponent.create)
- Multiple components in the same module
- Namespace with staticmethod assignments
- Module-level namespace instance assignment
"""

from reflex_base.components.component import Component, ComponentNamespace, field
from reflex_base.vars.base import Var


class DialogRoot(Component):
"""The root dialog component."""

# Whether the dialog is open.
is_open: Var[bool] = field(doc="Whether the dialog is open.")


class DialogContent(Component):
"""The dialog content component."""

# Whether to force mount the content.
force_mount: Var[bool] = field(doc="Whether to force mount.")


class DialogTitle(Component):
"""The dialog title component."""

# The title text.
title_text: Var[str]


class Dialog(ComponentNamespace):
"""Dialog components namespace."""

root = __call__ = staticmethod(DialogRoot.create)
content = staticmethod(DialogContent.create)
title = staticmethod(DialogTitle.create)


dialog = Dialog()
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""A simple component with basic props.

This module tests:
- Basic component stub generation
- Props with various simple types (str, int, bool, float)
- Default event handlers inherited from Component
- Props with doc strings (via field(doc=...))
- Props with comment-based docs (# comment above prop)
- Module docstring removal in stubs
- visit_Assign: assignment to `Any` is preserved
- visit_Assign: non-annotated assignments are removed
"""

from typing import Any

from reflex_base.components.component import Component, field
from reflex_base.vars.base import Var

# Assignment to Any should be preserved in the stub.
SomeType = Any

# A regular non-annotated assignment should be removed.
SOME_CONSTANT = 42


class SimpleComponent(Component):
"""A simple test component with basic prop types."""

# The title displayed on the component.
title: Var[str]

# The count to display.
count: Var[int]

is_active: Var[bool] = field(doc="Whether the component is active.")

opacity: Var[float] = field(doc="The opacity of the component.")

label: Var[str] = field(
default=Var.create("default"),
doc="An optional label with a default value.",
)

def _private_method(self):
"""This should not appear in the stub.

Returns:
A string indicating this is a private method.
"""
return "private"

def public_helper(self) -> str:
"""A public method that should have its body blanked out.

Returns:
A string indicating this is a public helper method.
"""
return "hello"
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Namespace with staticmethod __call__ that is not Component.create.
This module tests:
- _generate_staticmethod_call_functiondef path
- Namespace __call__ with custom function (not wrapping .create)
- The fallback where __call__.__func__.__name__ != "create"
"""

from reflex_base.components.component import Component, ComponentNamespace, field
from reflex_base.event import EventSpec
from reflex_base.vars.base import Var


class NotifyComponent(Component):
"""A notification component."""

message: Var[str] = field(doc="The notification message.")

level: Var[str] = field(doc="The notification level.")


def send_notification(message: str, level: str = "info") -> EventSpec:
"""Send a notification event.
Args:
message: The message to send.
level: The notification level.
Returns:
The event spec.
"""
return EventSpec() # type: ignore[call-arg]


class Notify(ComponentNamespace):
"""Notification namespace."""

component = staticmethod(NotifyComponent.create)
__call__ = staticmethod(send_notification)


notify = Notify()
Loading
Loading