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]); 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) diff --git a/reflex/config.py b/reflex/config.py index 05ff3fad5..0a1615a56 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 @@ -546,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) + # Optional redis key prefix for the state manager. REFLEX_REDIS_PREFIX: EnvVar[Optional[str]] = env_var(None) @@ -819,6 +823,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. @@ -828,21 +836,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) 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/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: 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) 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)