-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Validate reserved state names before registration #7136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
masenf
merged 6 commits into
reflex-dev:main
from
FarhanAliRaza:fix-reserved-state-names
Sep 17, 2026
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
f901a6b
Validate reserved state names before class and dynamic registration
FarhanAliRaza 7ed33c7
docs: rename the dynamic form handler so it does not shadow State.add…
FarhanAliRaza c8b4a20
Merge remote-tracking branch 'upstream/main' into pr-7136
FarhanAliRaza c5adb5e
fix(state): report every reserved name under the legacy flag and flat…
FarhanAliRaza dfe6ded
fix(state): drop the reserved-name compatibility flag
FarhanAliRaza 3aa4c66
refactor(state): compute the fast-path names once at class creation
FarhanAliRaza File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| State vars, event handlers, and dynamic route arguments now reject names reserved by framework methods and bookkeeping before registration. Rename conflicting members. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| """Validate the framework namespace before constructing or extending a state.""" | ||
|
|
||
| from functools import cache | ||
| from types import FunctionType | ||
| from typing import Any | ||
|
|
||
| from reflex_base.utils.compat import annotations_from_namespace | ||
| from reflex_base.utils.exceptions import ( | ||
| EventHandlerShadowsBuiltInStateMethodError, | ||
| StateValueError, | ||
| ) | ||
| from reflex_base.vars.base import ( | ||
| BaseStateMeta, | ||
| EvenMoreBasicBaseState, | ||
| _linearize_bases, | ||
| ) | ||
|
|
||
| _FIELD_MAP_NAMES = frozenset({"__fields__", "__own_fields__", "__inherited_fields__"}) | ||
|
|
||
|
|
||
| @cache | ||
| def _reserved_state_members() -> dict[str, Any]: | ||
| """Return framework members, excluding state vars and Python protocols. | ||
|
|
||
| Returns: | ||
| Reserved names and their original descriptors, without invoking them. | ||
| """ | ||
| # BaseState must exist before its namespace can be inspected. | ||
| from reflex.state import BaseState | ||
|
|
||
| members = {} | ||
| for base in reversed(BaseState.__mro__[:-1]): | ||
| namespace = vars(base) | ||
| members.update( | ||
| (name, namespace.get(name)) | ||
| for name in namespace.keys() | annotations_from_namespace(namespace).keys() | ||
| if not name.startswith("__") or name in _FIELD_MAP_NAMES | ||
| ) | ||
| for name, field in BaseState.__fields__.items(): | ||
| if field.is_var: | ||
| members.pop(name, None) | ||
| return members | ||
|
|
||
|
|
||
| def _validate_state_name(name: str, value: Any = None) -> None: | ||
| """Reject declarations that replace framework methods or bookkeeping. | ||
|
|
||
| Args: | ||
| name: The declared or dynamically registered name. | ||
| value: The raw class declaration, when available. | ||
|
|
||
| Raises: | ||
| StateValueError: If a declaration uses a reserved name. | ||
| EventHandlerShadowsBuiltInStateMethodError: If a method overrides a builtin. | ||
| """ | ||
| members = _reserved_state_members() | ||
| if name not in members: | ||
| return | ||
| method = value.__func__ if isinstance(value, (classmethod, staticmethod)) else value | ||
| if isinstance(method, FunctionType): | ||
| if value is members[name] or getattr(method, "__override_base_method__", False): | ||
| return | ||
| msg = f"The event handler name `{name}` shadows a builtin State method; use a different name instead" | ||
| raise EventHandlerShadowsBuiltInStateMethodError(msg) | ||
| msg = f"State name `{name}` is reserved by BaseState; use a different name instead." | ||
| raise StateValueError(msg) | ||
|
|
||
|
FarhanAliRaza marked this conversation as resolved.
|
||
|
|
||
| def _validate_inherited_members(base: type, seen: set[str]) -> None: | ||
| """Check the members a Python mixin or model base adds to a state. | ||
|
|
||
| Args: | ||
| base: A base class that is not itself a validated state. | ||
| seen: Names an earlier base already provides in the MRO. | ||
| """ | ||
| is_model = isinstance(base, BaseStateMeta) | ||
| if is_model: | ||
| # Model fields are inherited even when an earlier base masks their | ||
| # class attributes in the MRO. | ||
| for member in base.__own_fields__: | ||
| _validate_state_name(member) | ||
| seen.update(base.__own_fields__) | ||
| for member, value in vars(base).items(): | ||
| if member not in seen and not ( | ||
| is_model and (member in _FIELD_MAP_NAMES or member == "_mixin") | ||
| ): | ||
| _validate_state_name(member, value) | ||
|
|
||
|
|
||
| class _StateMeta(BaseStateMeta): | ||
| """Check state declarations before field collection and subclass initialization.""" | ||
|
|
||
| def __new__( | ||
| cls, | ||
| name: str, | ||
| bases: tuple[type, ...], | ||
| namespace: dict[str, Any], | ||
| mixin: bool = False, | ||
| ) -> type: | ||
| """Construct a state after checking its declarations and Python mixins. | ||
|
|
||
| Args: | ||
| name: The class name. | ||
| bases: The parent classes. | ||
| namespace: The unmodified class namespace. | ||
| mixin: Whether the class is a state mixin. | ||
|
|
||
| Returns: | ||
| The validated state class. | ||
| """ | ||
| if any(isinstance(base, _StateMeta) for base in bases): | ||
| seen = namespace.keys() | annotations_from_namespace(namespace).keys() | ||
| for member in seen: | ||
| _validate_state_name(member, namespace.get(member)) | ||
| for base in _linearize_bases(bases): | ||
| if not isinstance(base, _StateMeta) and base not in ( | ||
| EvenMoreBasicBaseState, | ||
| object, | ||
| ): | ||
| _validate_inherited_members(base, seen) | ||
| seen.update(vars(base)) | ||
| return super().__new__(cls, name, bases, namespace, mixin=mixin) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.