Fix config reload state imports (#7028) - #7035
Conversation
Greptile SummaryThe PR prevents duplicate state registration when
Confidence Score: 5/5The PR appears safe to merge, with no concrete blocking or independently actionable non-blocking issue identified. The changed loader continues to reload
|
| Filename | Overview |
|---|---|
| packages/reflex-base/src/reflex_base/config.py | Adjusts dependency eviction according to registration-context freshness and project-root identity; no actionable defect was established. |
| tests/units/test_config.py | Adds focused regression tests for retained state modules, fresh-context state registration, and same-named dependencies across projects. |
| packages/reflex-base/news/+config-reload-state-imports.bugfix.md | Accurately documents the user-visible duplicate-state reload fix. |
Reviews (1): Last reviewed commit: "Fix config reload state imports (#7028)" | Re-trigger Greptile
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
1 issue found across 3 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/reflex-base/src/reflex_base/config.py">
<violation number="1" location="packages/reflex-base/src/reflex_base/config.py:1034">
P1: When an older `RegistrationContext` is re-entered after another same-project context has loaded, `ctx._config is None` is false, so `_get_config` retains the globally cached dependency modules from the newer context. `rxconfig.py` then references state classes registered in the newer context while the active context retains its original classes, causing registry/runtime state mismatches; track dependency modules per context or otherwise restore the context-owned modules instead of using the global root-only cache.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| """ | ||
| ctx = RegistrationContext.ensure_context() | ||
| config = _get_config() | ||
| config = _get_config(reload_dependencies=ctx._config is None) |
There was a problem hiding this comment.
P1: When an older RegistrationContext is re-entered after another same-project context has loaded, ctx._config is None is false, so _get_config retains the globally cached dependency modules from the newer context. rxconfig.py then references state classes registered in the newer context while the active context retains its original classes, causing registry/runtime state mismatches; track dependency modules per context or otherwise restore the context-owned modules instead of using the global root-only cache.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/reflex-base/src/reflex_base/config.py, line 1034:
<comment>When an older `RegistrationContext` is re-entered after another same-project context has loaded, `ctx._config is None` is false, so `_get_config` retains the globally cached dependency modules from the newer context. `rxconfig.py` then references state classes registered in the newer context while the active context retains its original classes, causing registry/runtime state mismatches; track dependency modules per context or otherwise restore the context-owned modules instead of using the global root-only cache.</comment>
<file context>
@@ -1021,6 +1031,6 @@ def reload_config() -> Config:
"""
ctx = RegistrationContext.ensure_context()
- config = _get_config()
+ config = _get_config(reload_dependencies=ctx._config is None)
ctx._set_config(config)
return config
</file context>
FarhanAliRaza
left a comment
There was a problem hiding this comment.
Tested with the appmod.py / rxconfig.py pair from #7028.
On main, get_config() followed by reload_config() in one RegistrationContext raises StateValueError. The new regression test fails there with that error and passes on this branch. ruff, pyright, and tests/units/test_config.py pass on the branch.
I also ran the same reload on a forked context, the shape AppHarness uses. That still raises the same StateValueError. I removed the _config_module_deps_root comparison and re-ran the config tests to check the root guard. They all pass without it.
See the inline comments for the requested changes.
| # before probing: find_spec answers from sys.modules, so modules | ||
| # left behind by another project directory would fake the existence | ||
| # check below. | ||
| # Always reload rxconfig, but retain its dependencies when reloading |
There was a problem hiding this comment.
Too long comments, explaining what is already can be seen in code. happens in many places.
we might want to clean up these.
| """ | ||
| ctx = RegistrationContext.ensure_context() | ||
| config = _get_config() | ||
| config = _get_config(reload_dependencies=ctx._config is None) |
There was a problem hiding this comment.
ctx._config is None is the wrong signal. RegistrationContext.fork() copies base_states but resets _config to None. So a forked context evicts and re-imports the state module into a context that already holds the class, and the shadow check fires again.
Repro with the appmod.py / rxconfig.py pair from #7028:
with RegistrationContext() as ctx:
c.get_config()
forked = ctx.fork()
tok = RegistrationContext.set(forked)
c.reload_config()
# StateValueError: The substate class 'appmod____my_state' has been defined multiple times.This is the path AppHarness takes (reflex/testing.py:286: fork, then reload_config()), so the harness still crashes on such a project. Please key the decision on whether the current context already holds the states those modules registered, not on whether it has a cached config. Add the fork case to the tests.
| for dep in _config_module_deps: | ||
| sys.modules.pop(dep, None) | ||
| _config_module_deps.clear() | ||
| if reload_dependencies or _config_module_deps_root != project_root: |
There was a problem hiding this comment.
The _config_module_deps_root != project_root branch is not exercised. With the comparison removed, all 118 tests in tests/units/test_config.py still pass. test_get_config_evicts_dependencies_from_another_project calls _get_config() with the default reload_dependencies=True, so it never reaches this check.
Either drop _config_module_deps_root or add a test that reloads in one context after the cwd moved to a second project. Note that in that scenario a same-named state module would still hit the shadow error, so the guard may not buy anything.
| ) | ||
|
|
||
| assert reflex_base.config._get_config(first_project).app_name == "first" | ||
| assert reflex_base.config._get_config(second_project).app_name == "second" |
There was a problem hiding this comment.
Both calls use reload_dependencies=True, so this passes on main too (after the fixture is adjusted) and does not cover the new root check. To cover it, load first_project into a context, then call reload_config() from second_project in the same context.
• ## Summary
Fixes: #7028
Fix reload_config() raising StateValueError when rxconfig.py imports a
module that defines a rx.State class.
Changes
Preserve project-local dependencies during reloads within the same
RegistrationContext.
Continue evicting dependencies for new contexts or different project
roots.
Added regression tests for state reloads, fresh contexts, and cross-
project isolation.
Added a reflex-base bugfix changelog fragment.
Testing