diff --git a/packages/reflex-base/src/reflex_base/config.py b/packages/reflex-base/src/reflex_base/config.py index 6c29603d982..ec32a5623b1 100644 --- a/packages/reflex-base/src/reflex_base/config.py +++ b/packages/reflex-base/src/reflex_base/config.py @@ -238,7 +238,7 @@ class BaseConfig: env_file: str | None = None - state_auto_setters: bool | None = None + state_auto_setters: bool = False show_built_with_reflex: bool | None = None @@ -352,6 +352,21 @@ def _post_init(self, **kwargs): if not self._skip_plugins_checks: self._add_builtin_plugins() + # Warn if state_auto_setters is explicitly set. + if "state_auto_setters" in kwargs: + if kwargs["state_auto_setters"]: + reason = ( + "auto setters will be removed; use explicit event handlers instead" + ) + else: + reason = "state_auto_setters=False is already the default and the option will be removed" + console.deprecate( + feature_name="state_auto_setters", + reason=reason, + deprecation_version="0.9.0", + removal_version="1.0", + ) + # Update default URLs if ports were set kwargs.update(env_kwargs) self._non_default_attributes = set(kwargs.keys()) @@ -381,7 +396,7 @@ def _normalize_disable_plugins(self): feature_name="Passing strings to disable_plugins", reason="pass Plugin classes directly instead, e.g. disable_plugins=[SitemapPlugin]", deprecation_version="0.8.28", - removal_version="0.9.0", + removal_version="1.0", ) try: from reflex_base.environment import interpret_plugin_class_env diff --git a/packages/reflex-base/src/reflex_base/utils/compat.py b/packages/reflex-base/src/reflex_base/utils/compat.py index cb9f35485a3..03211e4d4e7 100644 --- a/packages/reflex-base/src/reflex_base/utils/compat.py +++ b/packages/reflex-base/src/reflex_base/utils/compat.py @@ -2,10 +2,7 @@ import sys from collections.abc import Mapping -from typing import TYPE_CHECKING, Any - -if TYPE_CHECKING: - from pydantic.fields import FieldInfo +from typing import Any async def windows_hot_reload_lifespan_hack(): @@ -51,19 +48,3 @@ def annotations_from_namespace(namespace: Mapping[str, Any]) -> dict[str, Any]: if annotate := get_annotate_from_class_namespace(namespace): return call_annotate_function(annotate, format=Format.FORWARDREF) return namespace.get("__annotations__", {}) - - -def sqlmodel_field_has_primary_key(field_info: "FieldInfo") -> bool: - """Determines if a field is a primary. - - Args: - field_info: a rx.model field - - Returns: - If field_info is a primary key (Bool) - """ - if getattr(field_info, "primary_key", None) is True: - return True - if getattr(field_info, "sa_column", None) is None: - return False - return bool(getattr(field_info.sa_column, "primary_key", None)) # pyright: ignore[reportAttributeAccessIssue] diff --git a/packages/reflex-components-markdown/src/reflex_components_markdown/markdown.py b/packages/reflex-components-markdown/src/reflex_components_markdown/markdown.py index 6f7dbf7ef3b..f6033c83a3a 100644 --- a/packages/reflex-components-markdown/src/reflex_components_markdown/markdown.py +++ b/packages/reflex-components-markdown/src/reflex_components_markdown/markdown.py @@ -17,7 +17,6 @@ field, ) from reflex_base.components.tags.tag import Tag -from reflex_base.utils import console from reflex_base.utils.imports import ImportDict, ImportTypes, ImportVar from reflex_base.vars.base import LiteralVar, Var, VarData from reflex_base.vars.number import ternary_operation @@ -242,14 +241,6 @@ def create( # Update the base component map with the custom component map. component_map = {**get_base_component_map(), **props.pop("component_map", {})} - if "codeblock" in component_map: - console.deprecate( - feature_name="'codeblock' in component_map", - reason="Use 'pre' instead of 'codeblock' to customize code block rendering in markdown", - deprecation_version="0.8.25", - removal_version="0.9.0", - ) - component_map["pre"] = component_map.pop("codeblock") # Get the markdown source. src = children[0] diff --git a/pyi_hashes.json b/pyi_hashes.json index f6db8e53593..f7900836abd 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -40,7 +40,7 @@ "packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.pyi": "e10210239ce7dc18980e70eec19b9353", "packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.pyi": "2a93782c63e82a6939411273fe2486d9", "packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "f654cc9cb305712b485fcd676935c0c1", - "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "9c11bca2c4c5b722f55aba969f383e74", + "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "2d6efa2d5f2586a7036d606a24fb425d", "packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "ad4b084d94e50311f761d69b3173e357", "packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "241b80584f3e029145e6e003d1c476f2", "packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "b2f485bfde4978047b7b944cf15d92cb", diff --git a/reflex/app.py b/reflex/app.py index e3e66aec45d..482f8be3c15 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -332,10 +332,6 @@ class App(MiddlewareMixin, LifespanMixin): reset_style: bool = dataclasses.field(default=True) - overlay_component: Component | ComponentCallable | None = dataclasses.field( - default=None - ) - app_wraps: dict[tuple[int, str], Callable[[bool], Component | None]] = ( dataclasses.field( default_factory=lambda: { @@ -1092,22 +1088,6 @@ def _add_overlay_to_component( return Fragment.create(overlay_component, *children) - def _setup_overlay_component(self): - """If a State is not used and no overlay_component is specified, do not render the connection modal.""" - if self.overlay_component is None: - return - console.deprecate( - feature_name="overlay_component", - reason="Use `extra_app_wraps` to add the overlay component instead.", - deprecation_version="0.8.2", - removal_version="0.9.0", - ) - overlay_component = self._generate_component(self.overlay_component) - for k, component in self._pages.items(): - self._pages[k] = self._add_overlay_to_component( - component, overlay_component - ) - def _setup_sticky_badge(self): """Add the sticky badge to the app.""" from reflex_base.components.component import memo @@ -1285,7 +1265,6 @@ def get_compilation_time() -> str: self._add_optional_endpoints() self._validate_var_dependencies() - self._setup_overlay_component() if config.show_built_with_reflex is None: if ( diff --git a/reflex/istate/data.py b/reflex/istate/data.py index 624f0b64120..fb3e975bceb 100644 --- a/reflex/istate/data.py +++ b/reflex/istate/data.py @@ -239,7 +239,7 @@ def page(self) -> PageData: feature_name="RouterData.page", reason="Use RouterData.url instead", deprecation_version="0.8.1", - removal_version="0.9.0", + removal_version="1.0", ) return self._page diff --git a/reflex/model.py b/reflex/model.py index 63f09f63a8a..050a144a60a 100644 --- a/reflex/model.py +++ b/reflex/model.py @@ -13,8 +13,6 @@ from reflex_base.utils import console from reflex_base.utils.serializers import serializer -from reflex.utils.compat import sqlmodel_field_has_primary_key - if TYPE_CHECKING: from typing import TypeVar @@ -369,26 +367,6 @@ class Model(sqlmodel.SQLModel): "extra": "allow", } - @classmethod - def __pydantic_init_subclass__(cls): - """Drop the default primary key field if any primary key field is defined.""" - non_default_primary_key_fields = [ - field_name - for field_name, field_info in cls.model_fields.items() - if field_name != "id" and sqlmodel_field_has_primary_key(field_info) - ] - if non_default_primary_key_fields: - cls.model_fields.pop("id", None) - console.deprecate( - feature_name="Overriding default primary key", - reason=( - "Register sqlmodel.SQLModel classes with `@rx.ModelRegistry.register`" - ), - deprecation_version="0.8.15", - removal_version="0.9.0", - ) - super().__pydantic_init_subclass__() - @staticmethod def create_all(): """Create all the tables.""" diff --git a/reflex/state.py b/reflex/state.py index 463a1057e50..def7fcc382f 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -222,20 +222,8 @@ 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_base.config import get_config from reflex_base.utils.exceptions import EventHandlerValueError - config = get_config() - if config.state_auto_setters is None and self.state is not 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.__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}" @@ -1073,7 +1061,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 is not False: + if cls.is_user_defined() and get_config().state_auto_setters is True: cls._create_setter(name, prop) cls._set_default_value(name, prop) @@ -1170,29 +1158,8 @@ def _create_setter(cls, name: str, prop: Var): name: The name of the var. prop: The var to create a setter for. """ - from reflex_base.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( @@ -1874,19 +1841,6 @@ def get_value(self, key: str) -> Any: Raises: TypeError: If the key is not a string or MutableProxy. """ - if isinstance(key, MutableProxy): - # Legacy behavior from v0.7.14: handle non-string keys with deprecation warning - from reflex_base.utils import console - - console.deprecate( - feature_name="Non-string keys in get_value", - reason="Passing non-string keys to get_value is deprecated and will no longer be supported", - deprecation_version="0.8.0", - removal_version="0.9.0", - ) - - return key.__wrapped__ - if isinstance(key, str): if isinstance(val := getattr(self, key), MutableProxy): return val.__wrapped__ diff --git a/tests/units/test_app.py b/tests/units/test_app.py index f331dd6c8b9..a17b32f6b63 100644 --- a/tests/units/test_app.py +++ b/tests/units/test_app.py @@ -27,7 +27,6 @@ from reflex_base.vars.base import computed_var from reflex_components_core.base.bare import Bare from reflex_components_core.base.fragment import Fragment -from reflex_components_core.core.cond import Cond from reflex_components_radix.themes.typography.text import Text from sqlalchemy.engine.base import Engine from starlette.applications import Starlette @@ -37,7 +36,7 @@ import reflex as rx from reflex import AdminDash, constants -from reflex.app import App, ComponentCallable, default_overlay_component, upload +from reflex.app import App, ComponentCallable, upload from reflex.environment import environment from reflex.istate.manager.disk import StateManagerDisk from reflex.istate.manager.memory import StateManagerMemory @@ -523,6 +522,11 @@ async def test_dynamic_var_event( clean_registration_context.register_base_state(test_state) state = test_state() # pyright: ignore [reportCallIssue] state.add_var("int_val", int, 0) + + def set_int_val(self, value: int): + self.int_val = value + + state._add_event_handler("set_int_val", set_int_val) async with mock_base_state_event_processor as processor: await processor.enqueue( token, @@ -1933,63 +1937,6 @@ async def test_process_events( await mock_root_event_context.state_manager.close() -@pytest.mark.parametrize( - ("state", "overlay_component", "exp_page_child"), - [ - (None, default_overlay_component, Fragment), - (None, None, None), - (None, Text.create("foo"), Text), - (State, default_overlay_component, Fragment), - (State, None, None), - (State, Text.create("foo"), Text), - (State, lambda: Text.create("foo"), Text), - ], -) -def test_overlay_component( - state: type[State] | None, - overlay_component: Component | ComponentCallable | None, - exp_page_child: type[Component] | None, -): - """Test that the overlay component is set correctly. - - Args: - state: The state class to pass to App. - overlay_component: The overlay_component to pass to App. - exp_page_child: The type of the expected child in the page fragment. - """ - app = App(_state=state, overlay_component=overlay_component) - app._setup_overlay_component() - if exp_page_child is None: - assert app.overlay_component is None - elif isinstance(exp_page_child, Fragment): - assert app.overlay_component is not None - generated_component = app._generate_component(app.overlay_component) - assert isinstance(generated_component, Fragment) - assert isinstance( - generated_component.children[0], - Cond, # ConnectionModal is a Cond under the hood - ) - else: - assert app.overlay_component is not None - assert isinstance( - app._generate_component(app.overlay_component), - exp_page_child, - ) - - app.add_page(rx.box("Index"), route="/test") - # overlay components are wrapped during compile only - app._compile_page("test") - app._setup_overlay_component() - page = app._pages["test"] - - if exp_page_child is not None: - assert len(page.children) == 4 - children_types = (type(child) for child in page.children) - assert exp_page_child in children_types # pyright: ignore [reportOperatorIssue] - else: - assert len(page.children) == 3 - - @pytest.fixture def compilable_app(tmp_path: Path) -> Generator[tuple[App, Path], None, None]: """Fixture for an app that can be compiled. diff --git a/tests/units/test_model.py b/tests/units/test_model.py index 82b61a57c22..92565b9b8ba 100644 --- a/tests/units/test_model.py +++ b/tests/units/test_model.py @@ -50,7 +50,7 @@ class ChildModel(Model): def test_default_primary_key(model_default_primary: Model): - """Test that if a primary key is not defined a default is added. + """Test that if no primary key is defined, an "id" field is added. Args: model_default_primary: Fixture. @@ -59,12 +59,12 @@ def test_default_primary_key(model_default_primary: Model): def test_custom_primary_key(model_custom_primary: Model): - """Test that if a primary key is defined no default key is added. + """Test that if a primary key is defined it is not overridden. Args: model_custom_primary: Fixture. """ - assert "id" not in type(model_custom_primary).model_fields + assert "id" in type(model_custom_primary).model_fields @pytest.mark.filterwarnings( diff --git a/tests/units/test_state.py b/tests/units/test_state.py index a61add0a435..0e0dfa596cb 100644 --- a/tests/units/test_state.py +++ b/tests/units/test_state.py @@ -142,6 +142,33 @@ class TestState(TestMixin, BaseState): # pyright: ignore[reportUnsafeMultipleIn _backend: int = 0 asynctest: int = 0 + @rx.event + def set_num1(self, value: int): + """Set num1. + + Args: + value: The new value for num1. + """ + self.num1 = value + + @rx.event + def set_num2(self, value: float): + """Set num2. + + Args: + value: The new value for num2. + """ + self.num2 = value + + @rx.event + def set_array(self, value: list[float]): + """Set array. + + Args: + value: The new value for array. + """ + self.array = value + @computed_var def sum(self) -> float: """Dynamically sum the numbers. @@ -206,6 +233,15 @@ class GrandchildState(ChildState): value2: str + @rx.event + def set_value2(self, value: str): + """Set value2. + + Args: + value: The new value for value2. + """ + self.value2 = value + def do_nothing(self): """Do something.""" @@ -367,14 +403,8 @@ def test_event_handlers(test_state): """ expected_keys = ( "do_something", - "set_array", - "set_complex", - "set_fig", - "set_key", - "set_mapping", "set_num1", "set_num2", - "set_obj", ) cls = type(test_state) @@ -436,17 +466,6 @@ def test_dict(test_state: TestState): } -def test_default_setters(test_state): - """Test that we can set default values. - - Args: - test_state: A state. - """ - for prop_name in test_state.base_vars: - # Each base var should have a default setter. - assert hasattr(test_state, f"set_{prop_name}") - - def test_class_indexing_with_vars(): """Test that we can index into a state var with another var.""" prop = TestState.array[TestState.num1] # pyright: ignore [reportCallIssue, reportArgumentType] @@ -1019,12 +1038,6 @@ class DynamicState(BaseState): assert DynamicState().dynamic_dict == {"k1": 5, "k2": 10} # pyright: ignore[reportAttributeAccessIssue] -def test_add_var_default_handlers(test_state): - test_state.add_var("rand_int", int, 10) - assert "set_rand_int" in test_state.event_handlers - assert isinstance(test_state.event_handlers["set_rand_int"], EventHandler) - - class InterdependentState(BaseState): """A state with 3 vars and 3 computed vars. @@ -3490,8 +3503,8 @@ async def test_setvar( """ # Set Var in same state (with Var type casting) events = Event.from_event_type([ - TestState.setvar("num1", 42), - TestState.setvar("num2", "4.2"), + TestState.set_num1(42), + TestState.set_num2(4.2), ]) async with mock_base_state_event_processor as processor: for fut in asyncio.as_completed(await processor.enqueue_many(token, *events)): @@ -3695,6 +3708,34 @@ class TestState(State): assert list(TestState.event_handlers) == ["setvar"] +def test_auto_setters_on(tmp_path): + proj_root = tmp_path / "project1" + proj_root.mkdir() + + config_string = """ +import reflex as rx +config = rx.Config( + app_name="project1", + state_auto_setters=True, +) + """ + + (proj_root / "rxconfig.py").write_text(dedent(config_string)) + + with chdir(proj_root): + # reload config for each parameter to avoid stale values + reflex_base.config.get_config(reload=True) + from reflex.state import State + + class TestState(State): + """A test state.""" + + num: int = 0 + + assert "set_num" in TestState.event_handlers + assert "setvar" in TestState.event_handlers + + class MixinState(State, mixin=True): """A mixin state for testing.""" @@ -4073,21 +4114,6 @@ class GetValueState(rx.State): # Valid string keys (lambda state: "foo", "FOO", False), (lambda state: "bar", "BAR", False), - # MutableProxy keys (deprecated but supported) - ( - lambda state: MutableProxy( - wrapped="test_wrapped_value", state=state, field_name="test_field" - ), - "test_wrapped_value", - False, - ), - ( - lambda state: MutableProxy( - wrapped=42, state=state, field_name="test_field" - ), - 42, - False, - ), # Invalid key types (lambda state: 123, None, True), (lambda state: [], None, True),