Merge branch 'main' into add-validation-to-function-vars

This commit is contained in:
Khaleel Al-Adhami 2025-02-13 12:52:00 -08:00
commit a2074b9081
6 changed files with 94 additions and 37 deletions

View File

@ -46,10 +46,26 @@ def render_multiple_pages(app, num: int):
class State(rx.State):
"""The app state."""
position: str
college: str
age: Tuple[int, int] = (18, 50)
salary: Tuple[int, int] = (0, 25000000)
position: rx.Field[str]
college: rx.Field[str]
age: rx.Field[Tuple[int, int]] = rx.field((18, 50))
salary: rx.Field[Tuple[int, int]] = rx.field((0, 25000000))
@rx.event
def set_position(self, value: str):
self.position = value
@rx.event
def set_college(self, value: str):
self.college = value
@rx.event
def set_age(self, value: list[int]):
self.age = (value[0], value[1])
@rx.event
def set_salary(self, value: list[int]):
self.salary = (value[0], value[1])
comp1 = rx.center(
rx.theme_panel(),
@ -74,13 +90,13 @@ def render_multiple_pages(app, num: int):
rx.select(
["C", "PF", "SF", "PG", "SG"],
placeholder="Select a position. (All)",
on_change=State.set_position, # pyright: ignore [reportAttributeAccessIssue]
on_change=State.set_position,
size="3",
),
rx.select(
college,
placeholder="Select a college. (All)",
on_change=State.set_college, # pyright: ignore [reportAttributeAccessIssue]
on_change=State.set_college,
size="3",
),
),
@ -95,7 +111,7 @@ def render_multiple_pages(app, num: int):
default_value=[18, 50],
min=18,
max=50,
on_value_commit=State.set_age, # pyright: ignore [reportAttributeAccessIssue]
on_value_commit=State.set_age,
),
align_items="left",
width="100%",
@ -110,7 +126,7 @@ def render_multiple_pages(app, num: int):
default_value=[0, 25000000],
min=0,
max=25000000,
on_value_commit=State.set_salary, # pyright: ignore [reportAttributeAccessIssue]
on_value_commit=State.set_salary,
),
align_items="left",
width="100%",

View File

@ -591,7 +591,9 @@ class App(MiddlewareMixin, LifespanMixin):
Returns:
The generated component.
"""
return component if isinstance(component, Component) else component()
from reflex.compiler.compiler import into_component
return into_component(component)
def add_page(
self,

View File

@ -4,7 +4,7 @@ from __future__ import annotations
from datetime import datetime
from pathlib import Path
from typing import TYPE_CHECKING, Callable, Dict, Iterable, Optional, Tuple, Type, Union
from typing import TYPE_CHECKING, Dict, Iterable, Optional, Sequence, Tuple, Type, Union
from reflex import constants
from reflex.compiler import templates, utils
@ -545,30 +545,47 @@ def purge_web_pages_dir():
if TYPE_CHECKING:
from reflex.app import UnevaluatedPage
COMPONENT_TYPE = Union[Component, Var, Tuple[Union[Component, Var], ...]]
COMPONENT_TYPE_OR_CALLABLE = Union[COMPONENT_TYPE, Callable[[], COMPONENT_TYPE]]
from reflex.app import ComponentCallable, UnevaluatedPage
def componentify_unevaluated(
possible_component: COMPONENT_TYPE_OR_CALLABLE,
) -> Component:
"""Convert a possible component to a component.
def _into_component_once(component: Component | ComponentCallable) -> Component | None:
"""Convert a component to a Component.
Args:
possible_component: The possible component to convert.
component: The component to convert.
Returns:
The component.
The converted component.
"""
if isinstance(possible_component, Var):
return Fragment.create(possible_component)
if isinstance(possible_component, tuple):
return Fragment.create(*possible_component)
if isinstance(possible_component, Component):
return possible_component
return componentify_unevaluated(possible_component())
if isinstance(component, Component):
return component
if isinstance(component, (Var, int, float, str)):
return Fragment.create(component)
if isinstance(component, Sequence):
return Fragment.create(*component)
return None
def into_component(component: Component | ComponentCallable) -> Component:
"""Convert a component to a Component.
Args:
component: The component to convert.
Returns:
The converted component.
Raises:
TypeError: If the component is not a Component.
"""
if (converted := _into_component_once(component)) is not None:
return converted
if (
callable(component)
and (converted := _into_component_once(component())) is not None
):
return converted
raise TypeError(f"Expected a Component, got {type(component)}")
def compile_unevaluated_page(
@ -591,7 +608,7 @@ def compile_unevaluated_page(
The compiled component and whether state should be enabled.
"""
# Generate the component if it is a callable.
component = componentify_unevaluated(page.component)
component = into_component(page.component)
component._add_style_recursive(style or {}, theme)
@ -696,7 +713,7 @@ class ExecutorSafeFunctions:
The route, compiled component, and compiled page.
"""
component, enable_state = compile_unevaluated_page(
route, cls.UNCOMPILED_PAGES[route]
route, cls.UNCOMPILED_PAGES[route], cls.STATE, style, theme
)
return route, component, compile_page(route, component, cls.STATE)

View File

@ -1742,6 +1742,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
Yields:
StateUpdate object
Raises:
ValueError: If a string value is received for an int or float type and cannot be converted.
"""
from reflex.utils import telemetry
@ -1779,12 +1782,25 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
hinted_args, (Base, BaseModelV1, BaseModelV2)
):
payload[arg] = hinted_args(**value)
if isinstance(value, list) and (hinted_args is set or hinted_args is Set):
elif isinstance(value, list) and (hinted_args is set or hinted_args is Set):
payload[arg] = set(value)
if isinstance(value, list) and (
elif isinstance(value, list) and (
hinted_args is tuple or hinted_args is Tuple
):
payload[arg] = tuple(value)
elif isinstance(value, str) and (
hinted_args is int or hinted_args is float
):
try:
payload[arg] = hinted_args(value)
except ValueError:
raise ValueError(
f"Received a string value ({value}) for {arg} but expected a {hinted_args}"
) from None
else:
console.warn(
f"Received a string value ({value}) for {arg} but expected a {hinted_args}. A simple conversion was successful."
)
# Wrap the function in a try/except block.
try:
@ -2459,7 +2475,7 @@ class ComponentState(State, mixin=True):
Returns:
A new instance of the Component with an independent copy of the State.
"""
from reflex.compiler.compiler import componentify_unevaluated
from reflex.compiler.compiler import into_component
cls._per_component_state_instance_count += 1
state_cls_name = f"{cls.__name__}_n{cls._per_component_state_instance_count}"
@ -2472,7 +2488,7 @@ class ComponentState(State, mixin=True):
# Save a reference to the dynamic state for pickle/unpickle.
setattr(reflex.istate.dynamic, state_cls_name, component_state)
component = component_state.get_component(*children, **props)
component = componentify_unevaluated(component)
component = into_component(component)
component.State = component_state
return component

View File

@ -1050,7 +1050,7 @@ class Var(Generic[VAR_TYPE]):
"""
actual_name = self._var_field_name
def setter(state: BaseState, value: Any):
def setter(state: Any, value: Any):
"""Get the setter for the var.
Args:
@ -1068,6 +1068,8 @@ class Var(Generic[VAR_TYPE]):
else:
setattr(state, actual_name, value)
setter.__annotations__["value"] = self._var_type
setter.__qualname__ = self._get_setter_name()
return setter

View File

@ -20,7 +20,11 @@ def BackgroundTask():
class State(rx.State):
counter: int = 0
_task_id: int = 0
iterations: int = 10
iterations: rx.Field[int] = rx.field(10)
@rx.event
def set_iterations(self, value: str):
self.iterations = int(value)
@rx.event(background=True)
async def handle_event(self):
@ -125,8 +129,8 @@ def BackgroundTask():
rx.input(
id="iterations",
placeholder="Iterations",
value=State.iterations.to_string(), # pyright: ignore [reportAttributeAccessIssue]
on_change=State.set_iterations, # pyright: ignore [reportAttributeAccessIssue]
value=State.iterations.to_string(),
on_change=State.set_iterations,
),
rx.button(
"Delayed Increment",