Repository navigation
Merge a repeated @rx.event; restore staticmethod/classmethod above @rx.event - #7807
Conversation
[Medium impact] This PR appears safe to merge; no new actionable issues were found. SummaryThis PR lets an outer
Reviews (6) · Last reviewed commit: "Say which handlers a repeated rx.event r..." · Reviewed by Greptile |
There was a problem hiding this comment.
All reported issues were addressed across 6 files
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
Merging this PR will degrade performance by 2.33%2 warnings
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi
|
Generated by Claude Code |
|
CodSpeed is red here ( Generated by Claude Code |
…runs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi
There was a problem hiding this comment.
All reported issues were addressed across 3 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi
…persedes `rx.event(A.h, ...)` in another state unwrapped A's handler and wrote the new options onto A's function, so `background=True` turned A.h into a background task. It now raises a TypeError, as before the repeated decorator was merged. Only an unbound handler (the inner decorator of a stack) or a decentralized one is decorated again. An outer `background=False` or `supersedes=False` left the inner True in place, while an outer False event action overrides the inner one. Both markers now take the explicit value, so the outer decorator wins for every option.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi
d318f18 to
84d0a23
Compare
|
#7810 was stacked on this PR, so its squash (b95fbf5) put this PR's first two commits on
The diff against Generated by Claude Code |
…maining commits Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi
|
CodSpeed on 84d0a23 flags Generated by Claude Code |
…x.event (#7807) * Reject another state's handler; let an outer False undo background/supersedes `rx.event(A.h, ...)` in another state unwrapped A's handler and wrote the new options onto A's function, so `background=True` turned A.h into a background task. It now raises a TypeError, as before the repeated decorator was merged. Only an unbound handler (the inner decorator of a stack) or a decentralized one is decorated again. An outer `background=False` or `supersedes=False` left the inner True in place, while an outer False event action overrides the inner one. Both markers now take the explicit value, so the outer decorator wins for every option. * Say which handlers a repeated rx.event rejects in the news entry Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi --------- Co-authored-by: Farhan <www.mfarvirus@gmail.com> Co-authored-by: Claude <noreply@anthropic.com> (cherry picked from commit 7e46fc1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi
All Submissions:
Type of change
Changes To Core Features:
This is a release fix-up for FINDING-001 (HIGH, regression) and FINDING-016 (LOW, regression) from the reflex 0.11.0a1 pre-release testing (artifacts on branch
claude/vibrant-pasteur-3921g3,prerelease-testing/runs/2026-11-11-v0.11.0a1/).Defect
FINDING-001. A second
@rx.eventon a handler makes class creation fail. reflex-examplesoverkey(overkey/overkey.py:164,@rx.eventdirectly above@rx.event(background=True)) fails at import, andreflex runexits 1. Since #7673,@rx.eventreturns anEventHandler, andEventNamespace.__new__.wrapperhad no case for an input that is already one. Depending on the outer decorator's options, one of three things happened:EventHandler(fn=EventHandler), and registration failed informat.py:448with'EventHandler' object has no attribute '__qualname__';Background task must be async function or generator.;setattron the frozen dataclass raisedFrozenInstanceError.All six stacking patterns failed:
event/event,event/event(background=True),event/event(throttle=300),event(background=True)/event,event(throttle=300)/eventandevent()/event.FINDING-016.
@staticmethod(or@classmethod) stacked above@rx.eventon a state returned anEventSpecinstead of calling the function.BaseState._item_is_event_handlerfound the inner handler throughstaticmethod.__wrapped__and emitted the generic, misleading "Decorators above @rx.event" warning. A staticmethod has no__code__, so the attribute stayedstaticmethod(EventHandler), and calls went toEventHandler.__call__.Fix
reflex_base/event/__init__.pywrapper: when the input is anEventHandler, unwrap it tofunc.fnand decorate the function again, silently (owner's decision: the deprecation covers non-rx.eventdecorators). Merge semantics:background/supersedesmarkers live on the function and take the explicit value of each decorator, so an outerbackground=Falseturns off an innerbackground=True. The outer decorator wins for every option, the same rule as for event actions below.{**inner, **outer}: the outer decorator wins where both set an action, because it is applied last, the same way chained.throttle(...)overrides. In 0.10.1 the outer decorator replaced the whole dict whenever it had actions. The merge gives the same result for every pattern above and drops nothing.rx.event(OtherState.handler)in another state, raises aTypeError, as on main. Unwrapping it would write the new options onto the other state's function:background=Truewould makeOtherState.handlera background task.reflex/state.py: a new_method_above_handlerturnsstaticmethod(EventHandler)/classmethod(EventHandler)back into the plain staticmethod/classmethod of its function. It is used in the handler loop of__init_subclass__and in_bind_mixin_members, so a state that copies the method from a mixin is fixed too. It warns with the existing@rx.event on a staticmethod or classmethoddeprecation (the decorator has no effect; remove it), as the Make event handlers awaitable and register them through @rx.event #7673 changelog says for@rx.eventbelow them.Test plan
Regression tests written first and shown failing on
origin/main(2d71015):tests/units/test_event.py::test_repeated_rx_event_is_one_handler[*]: the six patterns each register one handler with the right name, background flag and actions, emit no deprecation, and run. Alsotest_repeated_rx_event_merges_event_actionsandtest_repeated_rx_event_on_a_decentralized_handler.test_repeated_rx_event_outer_false_overrides_background_and_supersedesandtest_rx_event_on_a_handler_of_another_state_raises[*]. Before their fix:assert True is False(outerbackground=Falseignored) and 3×DID NOT RAISE TypeError(OtherState.handledecorated again, and made a background task withbackground=True).Before the fix, all 8 failed: 4×
AttributeError: 'EventHandler' object has no attribute '__qualname__'(format.py:448), 1×TypeError: Background task must be async function or generator., 3×FrozenInstanceError: cannot assign to field '_rx_event_actions'.tests/units/test_state.py::test_static_and_class_methods_above_rx_event_are_deprecated_and_ignored:S.helper(3) == 6,S().helper(3) == 6,S.cls_helper(3)runs, nothing is registered as a handler, and the same holds via a mixin. Before the fix it failed:assert {'Decorators above @rx.event'} == {'@rx.event on a staticmethod or classmethod'}, and the call returned anEventSpec.After the fix:
uv run pytest tests/units --cov --no-cov-on-fail --cov-report=: 15643 passed, 66 skipped, coverage 81.43%.tests/units/reflex_bench/drivers/test_browser.pywas excluded because the sandbox has no Chromium for it.ruff check,ruff formatanduv run pyright reflex testsare clean (0 errors).overkey(copied from @ebe19ff), with a throwaway venv that has this branch installed editable:import overkey.overkeyworks andOverkeyState.tick.is_backgroundis True.reflex runin dev serves the page, and in Chromium a key press starts the stacked backgroundtick: the timer goes 29 → 26 in 2.5 s, with no console or backend errors.Notes
@staticmethodabove@rx.eventon a class that is not a state still returns anEventSpec, with no warning. Nothing inspects such a class's attributes (staticmethoddoes not forward__set_name__). The Make event handlers awaitable and register them through @rx.event #7673 changelog already says calling such a method through the class is unsupported. A follow-up could warn inEventHandler.__call__for an unbound method-like handler.@rx.event(...)stacked above another@rx.event(theEventDecoratorsignature takes a plain function). The tests carry# pyright: ignore. Typing the repeated form is left out of this fix-up.@rx.eventon Python 3.11–3.13) is handled in a separate PR.Generated by Claude Code