From 4571524e1c4556c8be8963ddeebae269f817a3d9 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 21 Nov 2024 04:11:34 -0800 Subject: [PATCH 1/6] [ENG-4130] Disable typer/rich integration appropriately (#4412) The `rich` module should be set to `None`, indicating that rich should not be used. Setting it to `False` worked before, but recently added code in typer fails when checking `if rich is not None`. ref: https://github.com/fastapi/typer/pull/847 --- reflex/reflex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reflex/reflex.py b/reflex/reflex.py index 3fedc2ef5..8c26e0470 100644 --- a/reflex/reflex.py +++ b/reflex/reflex.py @@ -20,7 +20,7 @@ from reflex.state import reset_disk_state_manager from reflex.utils import console, redir, telemetry # Disable typer+rich integration for help panels -typer.core.rich = False # type: ignore +typer.core.rich = None # type: ignore # Create the app. try: From 81583d45cafe80757bcd07be0d26ada6d928009a Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 21 Nov 2024 10:27:44 -0800 Subject: [PATCH 2/6] [HOS-313] state.js: when a routing error occurs, delete it (#4410) In some cases, a routing failure can cause the failure to be cached. When the router has a cached failure, pushing such a route will never call routeChangeComplete, and thus on_load event will never be fired for that route. Purposely clearing the error from the router allows the page to properly load on subsequent attempts without refreshing the app. --- reflex/.templates/web/utils/state.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/reflex/.templates/web/utils/state.js b/reflex/.templates/web/utils/state.js index 3899ddc89..72733777e 100644 --- a/reflex/.templates/web/utils/state.js +++ b/reflex/.templates/web/utils/state.js @@ -762,7 +762,7 @@ export const useEventLoop = ( window.onunhandledrejection = function (event) { addEvents([ Event(`${exception_state_name}.handle_frontend_exception`, { - stack: event.reason.stack, + stack: event.reason?.stack, component_stack: "", }), ]); @@ -837,11 +837,20 @@ export const useEventLoop = ( } }; const change_complete = () => addEvents(onLoadInternalEvent()); + const change_error = () => { + // Remove cached error state from router for this page, otherwise the + // page will never send on_load events again. + if (router.components[router.pathname].error) { + delete router.components[router.pathname].error; + } + } router.events.on("routeChangeStart", change_start); router.events.on("routeChangeComplete", change_complete); + router.events.on("routeChangeError", change_error); return () => { router.events.off("routeChangeStart", change_start); router.events.off("routeChangeComplete", change_complete); + router.events.off("routeChangeError", change_error); }; }, [router]); From 05956c84a7bea98d1d54e4584924cfaf432e9f55 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 21 Nov 2024 10:29:40 -0800 Subject: [PATCH 3/6] protect sys.path manipulation with a mutex (#4408) Compiling pages in separate threads can result in `sys.path` being cleared, which breaks subsequent imports. --- reflex/config.py | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/reflex/config.py b/reflex/config.py index e86959e57..962d6fec9 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -8,6 +8,7 @@ import importlib import inspect import os import sys +import threading import urllib.parse from importlib.util import find_spec from pathlib import Path @@ -816,6 +817,10 @@ def _get_config() -> Config: return rxconfig.config +# Protect sys.path from concurrent modification +_config_lock = threading.RLock() + + def get_config(reload: bool = False) -> Config: """Get the app config. @@ -825,21 +830,26 @@ def get_config(reload: bool = False) -> Config: Returns: The app config. """ - # Remove any cached module when `reload` is requested. - if reload and constants.Config.MODULE in sys.modules: - del sys.modules[constants.Config.MODULE] + cached_rxconfig = sys.modules.get(constants.Config.MODULE, None) + if cached_rxconfig is not None: + if reload: + # Remove any cached module when `reload` is requested. + del sys.modules[constants.Config.MODULE] + else: + return cached_rxconfig.config - sys_path = sys.path.copy() - sys.path.clear() - sys.path.append(os.getcwd()) - try: - # Try to import the module with only the current directory in the path. - return _get_config() - except Exception: - # If the module import fails, try to import with the original sys.path. - sys.path.extend(sys_path) - return _get_config() - finally: - # Restore the original sys.path. + with _config_lock: + sys_path = sys.path.copy() sys.path.clear() - sys.path.extend(sys_path) + sys.path.append(os.getcwd()) + try: + # Try to import the module with only the current directory in the path. + return _get_config() + except Exception: + # If the module import fails, try to import with the original sys.path. + sys.path.extend(sys_path) + return _get_config() + finally: + # Restore the original sys.path. + sys.path.clear() + sys.path.extend(sys_path) From 095c1e5b7f965e44425eccd7772102c0f17e50e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Brand=C3=A9ho?= Date: Thu, 21 Nov 2024 11:05:02 -0800 Subject: [PATCH 4/6] remove deprecation for these events (#4415) --- reflex/components/radix/primitives/drawer.py | 28 ++++---------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/reflex/components/radix/primitives/drawer.py b/reflex/components/radix/primitives/drawer.py index f99342a58..ed57dcbd8 100644 --- a/reflex/components/radix/primitives/drawer.py +++ b/reflex/components/radix/primitives/drawer.py @@ -11,7 +11,6 @@ from reflex.components.radix.primitives.base import RadixPrimitiveComponent from reflex.components.radix.themes.base import Theme from reflex.components.radix.themes.layout.flex import Flex from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec -from reflex.utils import console from reflex.vars.base import Var @@ -140,19 +139,19 @@ class DrawerContent(DrawerComponent): base_style.update(style) return {"css": base_style} - # Fired when the drawer content is opened. Deprecated. + # Fired when the drawer content is opened. on_open_auto_focus: EventHandler[no_args_event_spec] - # Fired when the drawer content is closed. Deprecated. + # Fired when the drawer content is closed. on_close_auto_focus: EventHandler[no_args_event_spec] - # Fired when the escape key is pressed. Deprecated. + # Fired when the escape key is pressed. on_escape_key_down: EventHandler[no_args_event_spec] - # Fired when the pointer is down outside the drawer content. Deprecated. + # Fired when the pointer is down outside the drawer content. on_pointer_down_outside: EventHandler[no_args_event_spec] - # Fired when interacting outside the drawer content. Deprecated. + # Fired when interacting outside the drawer content. on_interact_outside: EventHandler[no_args_event_spec] @classmethod @@ -170,23 +169,6 @@ class DrawerContent(DrawerComponent): Returns: The drawer content. """ - deprecated_properties = [ - "on_open_auto_focus", - "on_close_auto_focus", - "on_escape_key_down", - "on_pointer_down_outside", - "on_interact_outside", - ] - - for prop in deprecated_properties: - if prop in props: - console.deprecate( - feature_name="drawer content events", - reason=f"The `{prop}` event is deprecated and will be removed in 0.7.0.", - deprecation_version="0.6.3", - removal_version="0.7.0", - ) - comp = super().create(*children, **props) return Theme.create(comp) From ecb52651c3a134056bac5a59be1078685ec99d7d Mon Sep 17 00:00:00 2001 From: benedikt-bartscher <31854409+benedikt-bartscher@users.noreply.github.com> Date: Thu, 21 Nov 2024 20:06:47 +0100 Subject: [PATCH 5/6] allow to disable checking for the latest package version via env (#4407) --- reflex/config.py | 3 +++ reflex/utils/prerequisites.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/reflex/config.py b/reflex/config.py index 962d6fec9..953a92b64 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -547,6 +547,9 @@ class EnvironmentVariables: # Where to save screenshots when tests fail. SCREENSHOT_DIR: EnvVar[Optional[Path]] = env_var(None) + # Whether to check for outdated package versions. + REFLEX_CHECK_LATEST_VERSION: EnvVar[bool] = env_var(True) + environment = EnvironmentVariables() diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index b2b3b7f3b..68d198711 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -93,6 +93,8 @@ def check_latest_package_version(package_name: str): Args: package_name: The name of the package. """ + if environment.REFLEX_CHECK_LATEST_VERSION.get() is False: + return try: # Get the latest version from PyPI current_version = importlib.metadata.version(package_name) From e0984aa83431bc70a31a99f48c98e87470e1be48 Mon Sep 17 00:00:00 2001 From: benedikt-bartscher <31854409+benedikt-bartscher@users.noreply.github.com> Date: Thu, 21 Nov 2024 20:53:50 +0100 Subject: [PATCH 6/6] Allow bound method as event handler (#4348) * subtract 1 arg if the method is a bound method * fix it early in user_args * only bound methods pls * add test --- reflex/event.py | 4 ++++ tests/units/test_event.py | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/reflex/event.py b/reflex/event.py index 312c9887f..a9e92b635 100644 --- a/reflex/event.py +++ b/reflex/event.py @@ -1346,6 +1346,10 @@ def check_fn_match_arg_spec( EventFnArgMismatch: Raised if the number of mandatory arguments do not match """ user_args = inspect.getfullargspec(user_func).args + # Drop the first argument if it's a bound method + if inspect.ismethod(user_func) and user_func.__self__ is not None: + user_args = user_args[1:] + user_default_args = inspect.getfullargspec(user_func).defaults number_of_user_args = len(user_args) - number_of_bound_args number_of_user_default_args = len(user_default_args) if user_default_args else 0 diff --git a/tests/units/test_event.py b/tests/units/test_event.py index f17b3c4e4..4399ab2a0 100644 --- a/tests/units/test_event.py +++ b/tests/units/test_event.py @@ -2,6 +2,7 @@ from typing import Callable, List import pytest +import reflex as rx from reflex.event import ( Event, EventChain, @@ -439,3 +440,17 @@ def test_event_var_data(): # Ensure chain carries _var_data chain_var = Var.create(EventChain(events=[S.s(S.x)], args_spec=_args_spec)) assert chain_var._get_all_var_data() == S.x._get_all_var_data() + + +def test_event_bound_method() -> None: + class S(BaseState): + @event + def e(self, arg: str): + print(arg) + + class Wrapper: + def get_handler(self, arg: str): + return S.e(arg) + + w = Wrapper() + _ = rx.input(on_change=w.get_handler)