Skip to content

Commit be13be5

Browse files
minify event names as well
1 parent 0858c9e commit be13be5

8 files changed

Lines changed: 637 additions & 219 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ reflex.db
2121
node_modules
2222
package-lock.json
2323
*.pyi
24-
.pre-commit-config.yaml
24+
.pre-commit-config.yaml
25+
uploaded_files/*

reflex/environment.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -487,12 +487,12 @@ class PerformanceMode(enum.Enum):
487487

488488

489489
@enum.unique
490-
class StateMinifyMode(enum.Enum):
491-
"""Mode for state name minification."""
490+
class MinifyMode(enum.Enum):
491+
"""Mode for state/event name minification."""
492492

493-
DISABLED = "disabled" # Never minify state names (default)
494-
ENABLED = "enabled" # Minify states that have explicit state_id
495-
ENFORCE = "enforce" # Require all non-mixin states to have state_id
493+
DISABLED = "disabled" # Never minify names (default)
494+
ENABLED = "enabled" # Minify items that have explicit IDs
495+
ENFORCE = "enforce" # Require all items to have explicit IDs
496496

497497

498498
class ExecutorType(enum.Enum):
@@ -698,7 +698,10 @@ class EnvironmentVariables:
698698
REFLEX_STATE_SIZE_LIMIT: EnvVar[int] = env_var(1000)
699699

700700
# State name minification mode: disabled, enabled, or enforce.
701-
REFLEX_MINIFY_STATES: EnvVar[StateMinifyMode] = env_var(StateMinifyMode.DISABLED)
701+
REFLEX_MINIFY_STATES: EnvVar[MinifyMode] = env_var(MinifyMode.DISABLED)
702+
703+
# Event handler name minification mode: disabled, enabled, or enforce.
704+
REFLEX_MINIFY_EVENTS: EnvVar[MinifyMode] = env_var(MinifyMode.DISABLED)
702705

703706
# Whether to use the turbopack bundler.
704707
REFLEX_USE_TURBOPACK: EnvVar[bool] = env_var(False)

reflex/event.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ def substate_token(self) -> str:
8989
_EVENT_FIELDS: set[str] = {f.name for f in dataclasses.fields(Event)}
9090

9191
BACKGROUND_TASK_MARKER = "_reflex_background_task"
92+
EVENT_ID_MARKER = "_rx_event_id"
9293

9394

9495
@dataclasses.dataclass(
@@ -2311,6 +2312,7 @@ class EventNamespace:
23112312

23122313
# Constants
23132314
BACKGROUND_TASK_MARKER = BACKGROUND_TASK_MARKER
2315+
EVENT_ID_MARKER = EVENT_ID_MARKER
23142316
_EVENT_FIELDS = _EVENT_FIELDS
23152317
FORM_DATA = FORM_DATA
23162318
upload_files = upload_files
@@ -2334,6 +2336,7 @@ def __new__(
23342336
throttle: int | None = None,
23352337
debounce: int | None = None,
23362338
temporal: bool | None = None,
2339+
event_id: int | None = None,
23372340
) -> Callable[
23382341
[Callable[[BASE_STATE, Unpack[P]], Any]], EventCallback[Unpack[P]] # pyright: ignore [reportInvalidTypeVarUse]
23392342
]: ...
@@ -2349,6 +2352,7 @@ def __new__(
23492352
throttle: int | None = None,
23502353
debounce: int | None = None,
23512354
temporal: bool | None = None,
2355+
event_id: int | None = None,
23522356
) -> EventCallback[Unpack[P]]: ...
23532357

23542358
def __new__(
@@ -2361,6 +2365,7 @@ def __new__(
23612365
throttle: int | None = None,
23622366
debounce: int | None = None,
23632367
temporal: bool | None = None,
2368+
event_id: int | None = None,
23642369
) -> (
23652370
EventCallback[Unpack[P]]
23662371
| Callable[[Callable[[BASE_STATE, Unpack[P]], Any]], EventCallback[Unpack[P]]]
@@ -2375,6 +2380,7 @@ def __new__(
23752380
throttle: Throttle the event handler to limit calls (in milliseconds).
23762381
debounce: Debounce the event handler to delay calls (in milliseconds).
23772382
temporal: Whether the event should be dropped when the backend is down.
2383+
event_id: Optional integer ID for deterministic minified event names.
23782384
23792385
Raises:
23802386
TypeError: If background is True and the function is not a coroutine or async generator. # noqa: DAR402
@@ -2462,6 +2468,9 @@ def wrapper(
24622468
event_actions = _build_event_actions()
24632469
if event_actions:
24642470
func._rx_event_actions = event_actions # pyright: ignore [reportFunctionMemberAccess]
2471+
# Store event_id on the function for minification
2472+
if event_id is not None:
2473+
setattr(func, EVENT_ID_MARKER, event_id)
24652474
return func # pyright: ignore [reportReturnType]
24662475

24672476
if func is not None:

reflex/state.py

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
from reflex.environment import PerformanceMode, environment
4343
from reflex.event import (
4444
BACKGROUND_TASK_MARKER,
45+
EVENT_ID_MARKER,
4546
Event,
4647
EventHandler,
4748
EventSpec,
@@ -429,6 +430,9 @@ class BaseState(EvenMoreBasicBaseState):
429430
# The explicit state ID for minification (None = use full name).
430431
_state_id: ClassVar[int | None] = None
431432

433+
# Per-class registry mapping event_id -> event handler name for minification.
434+
_event_id_to_name: ClassVar[builtins.dict[int, str]] = {}
435+
432436
# The parent state.
433437
parent_state: BaseState | None = field(default=None, is_var=False)
434438

@@ -711,6 +715,36 @@ def __init_subclass__(
711715
cls.event_handlers[name] = handler
712716
setattr(cls, name, handler)
713717

718+
# Build event_id registry and validate uniqueness within this state class
719+
cls._event_id_to_name = {}
720+
missing_event_ids: list[str] = []
721+
for name, fn in events.items():
722+
event_id = getattr(fn, EVENT_ID_MARKER, None)
723+
if event_id is not None:
724+
if event_id in cls._event_id_to_name:
725+
existing_name = cls._event_id_to_name[event_id]
726+
msg = (
727+
f"Duplicate event_id={event_id} in state '{cls.__name__}': "
728+
f"handlers '{existing_name}' and '{name}' cannot share the same event_id."
729+
)
730+
raise StateValueError(msg)
731+
cls._event_id_to_name[event_id] = name
732+
else:
733+
missing_event_ids.append(name)
734+
735+
# In ENFORCE mode, all event handlers must have event_id
736+
from reflex.environment import MinifyMode
737+
738+
if (
739+
environment.REFLEX_MINIFY_EVENTS.get() == MinifyMode.ENFORCE
740+
and missing_event_ids
741+
):
742+
msg = (
743+
f"State '{cls.__name__}' in ENFORCE mode: event handlers "
744+
f"{missing_event_ids} are missing required event_id."
745+
)
746+
raise StateValueError(msg)
747+
714748
# Initialize per-class var dependency tracking.
715749
cls._var_dependencies = {}
716750
cls._init_var_dependency_dicts()
@@ -753,6 +787,10 @@ def _copy_fn(fn: Callable) -> Callable:
753787
newfn.__annotations__ = fn.__annotations__
754788
if mark := getattr(fn, BACKGROUND_TASK_MARKER, None):
755789
setattr(newfn, BACKGROUND_TASK_MARKER, mark)
790+
# Preserve event_id for minification
791+
event_id = getattr(fn, EVENT_ID_MARKER, None)
792+
if event_id is not None:
793+
object.__setattr__(newfn, EVENT_ID_MARKER, event_id)
756794
return newfn
757795

758796
@staticmethod
@@ -1059,22 +1097,22 @@ def get_name(cls) -> str:
10591097
Raises:
10601098
StateValueError: If ENFORCE mode is set and state_id is missing.
10611099
"""
1062-
from reflex.environment import StateMinifyMode
1100+
from reflex.environment import MinifyMode
10631101
from reflex.utils.exceptions import StateValueError
10641102

10651103
module = cls.__module__.replace(".", "___")
10661104
full_name = format.to_snake_case(f"{module}___{cls.__name__}")
10671105

10681106
minify_mode = environment.REFLEX_MINIFY_STATES.get()
10691107

1070-
if minify_mode == StateMinifyMode.DISABLED:
1108+
if minify_mode == MinifyMode.DISABLED:
10711109
return full_name
10721110

10731111
if cls._state_id is not None:
10741112
return _int_to_minified_name(cls._state_id)
10751113

10761114
# state_id not set
1077-
if minify_mode == StateMinifyMode.ENFORCE:
1115+
if minify_mode == MinifyMode.ENFORCE:
10781116
msg = (
10791117
f"State '{cls.__module__}.{cls.__name__}' is missing required state_id. "
10801118
f"Add state_id parameter: class {cls.__name__}(rx.State, state_id=N)"
@@ -1797,6 +1835,25 @@ async def get_var_value(self, var: Var[VAR_TYPE]) -> VAR_TYPE:
17971835
)
17981836
return getattr(other_state, var_data.field_name)
17991837

1838+
@classmethod
1839+
def _get_original_event_name(cls, minified_name: str) -> str | None:
1840+
"""Look up the original event handler name from a minified name.
1841+
1842+
This is used when the frontend sends back minified event names
1843+
and the backend needs to find the actual event handler.
1844+
1845+
Args:
1846+
minified_name: The minified event name (e.g., 'a').
1847+
1848+
Returns:
1849+
The original event handler name, or None if not found.
1850+
"""
1851+
# Build reverse lookup: minified_name -> original_name
1852+
for event_id, original_name in cls._event_id_to_name.items():
1853+
if _int_to_minified_name(event_id) == minified_name:
1854+
return original_name
1855+
return None
1856+
18001857
def _get_event_handler(
18011858
self, event: Event
18021859
) -> tuple[BaseState | StateProxy, EventHandler]:
@@ -1819,7 +1876,17 @@ def _get_event_handler(
18191876
if not substate:
18201877
msg = "The value of state cannot be None when processing an event."
18211878
raise ValueError(msg)
1822-
handler = substate.event_handlers[name]
1879+
1880+
# Try to look up the handler directly first
1881+
handler = substate.event_handlers.get(name)
1882+
if handler is None:
1883+
# If not found, the name might be minified - try reverse lookup
1884+
original_name = substate._get_original_event_name(name)
1885+
if original_name is not None:
1886+
handler = substate.event_handlers.get(original_name)
1887+
if handler is None:
1888+
msg = f"Event handler '{name}' not found in state '{type(substate).__name__}'"
1889+
raise KeyError(msg)
18231890

18241891
# For background tasks, proxy the state
18251892
if handler.is_background:

reflex/utils/format.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -446,8 +446,12 @@ def get_event_handler_parts(handler: EventHandler) -> tuple[str, str]:
446446
handler: The event handler to get the parts of.
447447
448448
Returns:
449-
The state and function name.
449+
The state and function name (possibly minified based on REFLEX_MINIFY_EVENTS).
450450
"""
451+
from reflex.environment import MinifyMode, environment
452+
from reflex.event import EVENT_ID_MARKER
453+
from reflex.state import State, _int_to_minified_name
454+
451455
# Get the class that defines the event handler.
452456
parts = handler.fn.__qualname__.split(".")
453457

@@ -461,11 +465,16 @@ def get_event_handler_parts(handler: EventHandler) -> tuple[str, str]:
461465
# Get the function name
462466
name = parts[-1]
463467

464-
from reflex.state import State
465-
466468
if state_full_name == FRONTEND_EVENT_STATE and name not in State.__dict__:
467469
return ("", to_snake_case(handler.fn.__qualname__))
468470

471+
# Check for event_id minification
472+
mode = environment.REFLEX_MINIFY_EVENTS.get()
473+
if mode != MinifyMode.DISABLED:
474+
event_id = getattr(handler.fn, EVENT_ID_MARKER, None)
475+
if event_id is not None:
476+
name = _int_to_minified_name(event_id)
477+
469478
return (state_full_name, name)
470479

471480

0 commit comments

Comments
 (0)