Fix config reload state imports (#7028) - #7035
Conversation
Merging this PR will improve performance by 3.17%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ⚡ | test_var_access[mutable_dict] |
47 ms | 45.5 ms | +3.17% |
Tip
Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.
Comparing harsh21234i:fix/config-reload-state-imports (88f15fa) with main (5d9724e)
Footnotes
-
8 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩
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
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
… into fix/config-reload-state-imports
|
hey @FarhanAliRaza please take a look whenever you get time :) |
• ## 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