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
2 changes: 1 addition & 1 deletion reflex/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ class BaseConfig:
env_file: str | None = None

# Whether to automatically create setters for state base vars
state_auto_setters: bool = True
state_auto_setters: bool | None = None

# Whether to display the sticky "Built with Reflex" badge on all pages.
show_built_with_reflex: bool | None = None
Expand Down
48 changes: 44 additions & 4 deletions reflex/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,20 @@ def __call__(self, *args: Any) -> EventSpec:
EventHandlerValueError: If the given Var name is not a str
NotImplementedError: If the setter for the given Var is async
"""
from reflex.config import get_config
from reflex.utils.exceptions import EventHandlerValueError

config = get_config()
if config.state_auto_setters is None:
console.deprecate(
feature_name="state_auto_setters defaulting to True",
reason="The default value will be changed to False in a future release. Set state_auto_setters explicitly or define setters explicitly. "
f"Used {self.state_cls.__name__}.setvar without defining it.",
deprecation_version="0.8.9",
removal_version="0.9.0",
dedupe=True,
)

if args:
if not isinstance(args[0], str):
msg = f"Var name must be passed as a string, got {args[0]!r}"
Expand Down Expand Up @@ -1036,7 +1048,7 @@ def _init_var(cls, name: str, prop: Var):
)
raise VarTypeError(msg)
cls._set_var(name, prop)
if cls.is_user_defined() and get_config().state_auto_setters:
if cls.is_user_defined() and get_config().state_auto_setters is not False:
cls._create_setter(name, prop)
cls._set_default_value(name, prop)

Expand Down Expand Up @@ -1096,19 +1108,22 @@ def _set_var(cls, name: str, prop: Var):
setattr(cls, name, prop)

@classmethod
def _create_event_handler(cls, fn: Any):
def _create_event_handler(
cls, fn: Any, event_handler_cls: type[EventHandler] = EventHandler
):
"""Create an event handler for the given function.

Args:
fn: The function to create an event handler for.
event_handler_cls: The event handler class to use.

Returns:
The event handler.
"""
# Check if function has stored event_actions from decorator
event_actions = getattr(fn, "_rx_event_actions", {})

return EventHandler(
return event_handler_cls(
fn=fn, state_full_name=cls.get_full_name(), event_actions=event_actions
)

Expand All @@ -1125,9 +1140,34 @@ def _create_setter(cls, name: str, prop: Var):
name: The name of the var.
prop: The var to create a setter for.
"""
from reflex.config import get_config

config = get_config()
_create_event_handler_kwargs = {}

if config.state_auto_setters is None:

class EventHandlerDeprecatedSetter(EventHandler):
def __call__(self, *args, **kwargs):
console.deprecate(
feature_name="state_auto_setters defaulting to True",
reason="The default value will be changed to False in a future release. Set state_auto_setters explicitly or define setters explicitly. "
f"Used {setter_name} in {cls.__name__} without defining it.",
deprecation_version="0.8.9",
removal_version="0.9.0",
dedupe=True,
)
return super().__call__(*args, **kwargs)

_create_event_handler_kwargs["event_handler_cls"] = (
EventHandlerDeprecatedSetter
)

setter_name = Var._get_setter_name_for_name(name)
if setter_name not in cls.__dict__:
event_handler = cls._create_event_handler(prop._get_setter(name))
event_handler = cls._create_event_handler(
prop._get_setter(name), **_create_event_handler_kwargs
)
cls.event_handlers[setter_name] = event_handler
setattr(cls, setter_name, event_handler)

Expand Down
28 changes: 22 additions & 6 deletions reflex/utils/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import inspect
import os
import shutil
import sys
import time
from pathlib import Path
from types import FrameType
Expand Down Expand Up @@ -244,23 +245,38 @@ def warn(msg: str, *, dedupe: bool = False, **kwargs):
print_to_log_file(f"[orange1]Warning: {msg}[/orange1]", **kwargs)


def _get_first_non_framework_frame() -> FrameType | None:
@once
def _exclude_paths_from_frame_info() -> list[Path]:
import importlib.util

import click
import granian
import socketio
import typing_extensions

import reflex as rx

# Exclude utility modules that should never be the source of deprecated reflex usage.
exclude_modules = [click, rx, typing_extensions]
exclude_modules = [click, rx, typing_extensions, socketio, granian]
modules_paths = [file for m in exclude_modules if (file := m.__file__)] + [
spec.origin
for m in [*sys.builtin_module_names, *sys.stdlib_module_names]
if (spec := importlib.util.find_spec(m)) and spec.origin
]
exclude_roots = [
p.parent.resolve() if (p := Path(file)).name == "__init__.py" else p.resolve()
for m in exclude_modules
if (file := m.__file__)
for file in modules_paths
]
# Specifically exclude the reflex cli module.
if reflex_bin := shutil.which(b"reflex"):
exclude_roots.append(Path(reflex_bin.decode()))
Comment thread
adhami3310 marked this conversation as resolved.

return exclude_roots


def _get_first_non_framework_frame() -> FrameType | None:
exclude_roots = _exclude_paths_from_frame_info()

frame = inspect.currentframe()
while frame := frame and frame.f_back:
frame_path = Path(inspect.getfile(frame)).resolve()
Expand Down Expand Up @@ -297,13 +313,13 @@ def deprecate(
filename = Path(origin_frame.f_code.co_filename)
if filename.is_relative_to(Path.cwd()):
filename = filename.relative_to(Path.cwd())
loc = f"{filename}:{origin_frame.f_lineno}"
loc = f" ({filename}:{origin_frame.f_lineno})"
dedupe_key = f"{dedupe_key} {loc}"

if dedupe_key not in _EMITTED_DEPRECATION_WARNINGS:
msg = (
f"{feature_name} has been deprecated in version {deprecation_version}. {reason.rstrip('.').lstrip('. ')}. It will be completely "
f"removed in {removal_version}. ({loc})"
f"removed in {removal_version}.{loc}"
)
if _LOG_LEVEL <= LogLevel.WARNING:
print(f"[yellow]DeprecationWarning: {msg}[/yellow]", **kwargs)
Expand Down
3 changes: 2 additions & 1 deletion reflex/vars/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,7 @@ def _get_setter(self, name: str) -> Callable[[BaseState, Any], None]:
Returns:
A function that that creates a setter for the var.
"""
setter_name = Var._get_setter_name_for_name(name)

def setter(state: Any, value: Any):
"""Get the setter for the var.
Expand All @@ -951,7 +952,7 @@ def setter(state: Any, value: Any):

setter.__annotations__["value"] = self._var_type

setter.__qualname__ = Var._get_setter_name_for_name(name)
setter.__qualname__ = setter_name

return setter

Expand Down
6 changes: 5 additions & 1 deletion tests/integration/test_connection_banner.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ def ConnectionBanner():
class State(rx.State):
foo: int = 0

@rx.event
def set_foo(self, foo: int):
self.foo = foo

@rx.event
async def delay(self):
await asyncio.sleep(5)
Expand All @@ -33,7 +37,7 @@ def index():
rx.button(
"Increment",
id="increment",
on_click=State.set_foo(State.foo + 1), # pyright: ignore [reportAttributeAccessIssue]
on_click=State.set_foo(State.foo + 1),
),
rx.button("Delay", id="delay", on_click=State.delay),
)
Expand Down
6 changes: 5 additions & 1 deletion tests/integration/test_exception_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ class TestAppState(rx.State):

react_error: bool = False

@rx.event
def set_react_error(self, value: bool):
self.react_error = value

def divide_by_number(self, number: int):
"""Divide by number and print the result.

Expand All @@ -54,7 +58,7 @@ def index():
),
rx.button(
"induce_react_error",
on_click=TestAppState.set_react_error(True), # pyright: ignore [reportAttributeAccessIssue]
on_click=TestAppState.set_react_error(True),
id="induce-react-error-btn",
),
rx.box(
Expand Down
6 changes: 5 additions & 1 deletion tests/integration/test_lifespan.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ async def lifespan_task(inc: int = 1):
class LifespanState(rx.State):
interval: int = 100

@rx.event
def set_interval(self, interval: int):
self.interval = interval

@rx.var(cache=False)
def task_global(self) -> int:
return lifespan_task_global
Expand All @@ -73,7 +77,7 @@ def index():
rx.moment(
interval=LifespanState.interval, on_change=LifespanState.tick
),
on_click=LifespanState.set_interval( # pyright: ignore [reportAttributeAccessIssue]
on_click=LifespanState.set_interval(
rx.cond(LifespanState.interval, 0, 100)
),
id="toggle-tick",
Expand Down
Loading