Fix config reload state imports (#7028) - #7035
Conversation
Greptile SummaryThis PR changes config reload dependency tracking so project-local state modules retain their registered class identities while ordinary configuration dependencies are re-imported.
Confidence Score: 4/5The PR is not yet safe to merge because a package that both defines a registered state and contains another state module is still reloaded, recreating the package-level state class. The unresolved previous finding in Files Needing Attention: packages/reflex-base/src/reflex_base/config.py
|
| Filename | Overview |
|---|---|
| packages/reflex-base/src/reflex_base/config.py | Implements context-aware module preservation and reload tracking; the previously reported package/state overlap remains outstanding. |
| packages/reflex-base/src/reflex_base/registry.py | Stores and copies each registration context's config dependency modules and associated project root. |
| tests/units/test_config.py | Adds comprehensive regression tests for dependency freshness, state identity, context isolation, package reloads, and retry behavior. |
| packages/reflex-base/news/+config-reload-state-imports.bugfix.md | Documents the user-visible duplicate-state reload fix. |
Reviews (8): Last reviewed commit: "Track imports after failed package reloa..." | Re-trigger Greptile
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
All reported issues were addressed across 3 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
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.
|
Addressed the remaining review feedback in commit 4d8f57f.
Verification:
Please re-review the latest commit. |
There was a problem hiding this comment.
All reported issues were addressed across 3 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| for dep in sorted(package_modules, key=lambda name: name.count(".")): | ||
| importlib.reload(ctx._config_module_deps[dep]) |
There was a problem hiding this comment.
When a package defines a registered state in its __init__.py and also contains another state module, it belongs to both state_modules and package_modules. This unconditional reload re-executes the initializer and creates a new state class while the context still retains the original class. Module exports and the state registry can then refer to different class objects, defeating the state preservation this reload path is intended to provide.
Knowledge Base Used: Application lifecycle and configuration
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
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:967">
P1: When a package's `__init__.py` defines a registered `rx.State`, this reload creates a replacement class while `RegistrationContext` retains the original. Skip reloading package modules that are themselves registered state modules.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| sys.modules[dep] = module | ||
| _config_module_deps.add(dep) | ||
| for dep in sorted(package_modules, key=lambda name: name.count(".")): | ||
| importlib.reload(ctx._config_module_deps[dep]) |
There was a problem hiding this comment.
P1: When a package's __init__.py defines a registered rx.State, this reload creates a replacement class while RegistrationContext retains the original. Skip reloading package modules that are themselves registered state modules.
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 967:
<comment>When a package's `__init__.py` defines a registered `rx.State`, this reload creates a replacement class while `RegistrationContext` retains the original. Skip reloading package modules that are themselves registered state modules.</comment>
<file context>
@@ -951,10 +951,20 @@ def _get_config(
sys.modules[dep] = module
_config_module_deps.add(dep)
+ for dep in sorted(package_modules, key=lambda name: name.count(".")):
+ importlib.reload(ctx._config_module_deps[dep])
# only import the module if it exists. If a module spec exists then
# the module exists.
</file context>
| importlib.reload(ctx._config_module_deps[dep]) | |
| if dep not in state_modules: | |
| importlib.reload(ctx._config_module_deps[dep]) |
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
• ## 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