From b18fd0e5d7566a2d14f936a520f21106aee6cf26 Mon Sep 17 00:00:00 2001 From: Farhan Ali Raza Date: Tue, 16 Jun 2026 02:04:22 +0500 Subject: [PATCH 1/2] fix: avoid config re-entry when a State is defined in rxconfig.py State-class creation read get_config().state_auto_setters at class-creation time, which re-entered config loading while rxconfig.py was still importing and raised AttributeError. Cache the flag when the Config is built and read it via get_state_auto_setters(), falling back to the env var before any Config exists. --- .../reflex-base/src/reflex_base/config.py | 27 ++++ reflex/state.py | 4 +- tests/units/test_state.py | 123 ++++++++++++++++++ 3 files changed, 152 insertions(+), 2 deletions(-) diff --git a/packages/reflex-base/src/reflex_base/config.py b/packages/reflex-base/src/reflex_base/config.py index 5ea80afb439..0195ff87b7b 100644 --- a/packages/reflex-base/src/reflex_base/config.py +++ b/packages/reflex-base/src/reflex_base/config.py @@ -392,6 +392,11 @@ def _post_init(self, **kwargs): self._non_default_attributes = set(kwargs.keys()) self._replace_defaults(**kwargs) + # Publish for State-class creation so it never re-enters get_config() + # (which AttributeErrors if a State is defined while rxconfig.py is mid-import). + global _state_auto_setters + _state_auto_setters = self.state_auto_setters + if ( self.state_manager_mode == constants.StateManagerMode.REDIS and not self.redis_url @@ -739,6 +744,28 @@ def _get_config() -> Config: # Protect sys.path from concurrent modification _config_lock = threading.RLock() +# Cached state_auto_setters so State-class creation never re-enters get_config(). +_state_auto_setters: bool | None = None + + +def get_state_auto_setters() -> bool: + """Return whether state auto-setters are enabled, without importing rxconfig. + + Reads the value cached when the Config was built. Before any Config exists + (e.g. a State defined inside rxconfig.py during its import), falls back to the + REFLEX_STATE_AUTO_SETTERS env var, then the default (False). This never calls + get_config() or imports rxconfig, so it cannot re-enter config loading. + + Returns: + Whether state auto-setters are enabled. + """ + if _state_auto_setters is not None: + return _state_auto_setters + env_val = os.environ.get(Config._prefixes[0] + "STATE_AUTO_SETTERS") + if env_val and env_val.strip(): + return interpret_env_var_value(env_val, bool, "state_auto_setters") + return False + def get_config(reload: bool = False) -> Config: """Get the app config. diff --git a/reflex/state.py b/reflex/state.py index e7c96018654..9060eeee9f0 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -1139,7 +1139,7 @@ def _init_var(cls, name: str, prop: Var): Raises: VarTypeError: if the variable has an incorrect type """ - from reflex_base.config import get_config + from reflex_base.config import get_state_auto_setters from reflex_base.utils.exceptions import VarTypeError if not types.is_valid_var_type(prop._var_type): @@ -1151,7 +1151,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 True: + if cls.is_user_defined() and get_state_auto_setters() is True: cls._create_setter(name, prop) cls._set_default_value(name, prop) diff --git a/tests/units/test_state.py b/tests/units/test_state.py index 83aa823598a..cef7687fa3a 100644 --- a/tests/units/test_state.py +++ b/tests/units/test_state.py @@ -4005,6 +4005,129 @@ class TestState(State): assert "setvar" in TestState.event_handlers +def test_state_defined_in_rxconfig_does_not_crash(tmp_path): + """A State subclass defined in rxconfig.py must not crash config loading. + + Regression: _init_var read get_config().state_auto_setters at class-creation + time, which re-entered config loading while rxconfig was still importing and + raised AttributeError because the rxconfig module had no `config` attribute + yet. + """ + proj_root = tmp_path / "project1" + proj_root.mkdir() + + config_string = """ +import reflex as rx + + +class RxconfigDefinedState(rx.State): + n: int = 0 + + +config = rx.Config( + app_name="project1", +) +""" + + (proj_root / "rxconfig.py").write_text(dedent(config_string)) + + with chdir(proj_root): + # Must not raise (previously raised AttributeError mid-import). + reflex_base.config.get_config(reload=True) + del sys.modules[constants.Config.MODULE] + + +def test_state_in_rxconfig_honors_env_auto_setters(tmp_path, monkeypatch): + """A State defined in rxconfig.py (pre-config) honors REFLEX_STATE_AUTO_SETTERS. + + During rxconfig import the Config does not exist yet, so the cached value is + unset and get_state_auto_setters falls back to the env var. + """ + # Simulate a fresh process where no Config has been built yet. + monkeypatch.setattr(reflex_base.config, "_state_auto_setters", None) + monkeypatch.setenv("REFLEX_STATE_AUTO_SETTERS", "true") + + proj_root = tmp_path / "project1" + proj_root.mkdir() + config_string = """ +import reflex as rx + + +class RxconfigEnvSetterState(rx.State): + n: int = 0 + + +config = rx.Config(app_name="project1") +""" + (proj_root / "rxconfig.py").write_text(dedent(config_string)) + + with chdir(proj_root): + reflex_base.config.get_config(reload=True) + state_cls = sys.modules[constants.Config.MODULE].RxconfigEnvSetterState + assert "set_n" in state_cls.event_handlers + del sys.modules[constants.Config.MODULE] + + +def test_state_in_rxconfig_defaults_to_no_auto_setters(tmp_path, monkeypatch): + """A State defined in rxconfig.py gets no auto-setters by default (pre-config).""" + monkeypatch.setattr(reflex_base.config, "_state_auto_setters", None) + monkeypatch.delenv("REFLEX_STATE_AUTO_SETTERS", raising=False) + + proj_root = tmp_path / "project1" + proj_root.mkdir() + config_string = """ +import reflex as rx + + +class RxconfigNoSetterState(rx.State): + n: int = 0 + + +config = rx.Config(app_name="project1") +""" + (proj_root / "rxconfig.py").write_text(dedent(config_string)) + + with chdir(proj_root): + reflex_base.config.get_config(reload=True) + state_cls = sys.modules[constants.Config.MODULE].RxconfigNoSetterState + assert list(state_cls.event_handlers) == ["setvar"] + del sys.modules[constants.Config.MODULE] + + +def test_state_auto_setters_cache_tracks_reload(tmp_path): + """The cached state_auto_setters value follows config reloads (no stale flag).""" + proj_root = tmp_path / "project1" + proj_root.mkdir() + rxconfig_path = proj_root / "rxconfig.py" + off_config = """ +import reflex as rx +config = rx.Config(app_name="project1", state_auto_setters=False) +""" + on_config = """ +import reflex as rx +config = rx.Config(app_name="project1", state_auto_setters=True) +""" + + with chdir(proj_root): + rxconfig_path.write_text(dedent(off_config)) + reflex_base.config.get_config(reload=True) + from reflex.state import State + + class ReloadOffState(State): + num: int = 0 + + assert list(ReloadOffState.event_handlers) == ["setvar"] + + rxconfig_path.write_text(dedent(on_config)) + reflex_base.config.get_config(reload=True) + + class ReloadOnState(State): + num: int = 0 + + assert "set_num" in ReloadOnState.event_handlers + del sys.modules[constants.Config.MODULE] + + class MixinState(State, mixin=True): """A mixin state for testing.""" From 26b2f48e7372da5f3acca6705399c2ea733a1d73 Mon Sep 17 00:00:00 2001 From: Farhan Ali Raza Date: Tue, 16 Jun 2026 02:31:15 +0500 Subject: [PATCH 2/2] docs: add changelog entries for config re-entry fix (#6662) --- news/6662.bugfix.md | 1 + packages/reflex-base/news/6662.bugfix.md | 1 + 2 files changed, 2 insertions(+) create mode 100644 news/6662.bugfix.md create mode 100644 packages/reflex-base/news/6662.bugfix.md diff --git a/news/6662.bugfix.md b/news/6662.bugfix.md new file mode 100644 index 00000000000..a8e813f7cab --- /dev/null +++ b/news/6662.bugfix.md @@ -0,0 +1 @@ +Avoid re-entering config loading when a `State` subclass is defined in `rxconfig.py`. diff --git a/packages/reflex-base/news/6662.bugfix.md b/packages/reflex-base/news/6662.bugfix.md new file mode 100644 index 00000000000..a8e813f7cab --- /dev/null +++ b/packages/reflex-base/news/6662.bugfix.md @@ -0,0 +1 @@ +Avoid re-entering config loading when a `State` subclass is defined in `rxconfig.py`.