Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 41 additions & 6 deletions reflex/constants/compiler.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
"""Compiler variables."""

import enum
import os
from enum import Enum
from types import SimpleNamespace

from reflex.base import Base
from reflex.constants import Dirs
from reflex.constants import ENV_MODE_ENV_VAR, Dirs, Env
from reflex.utils.imports import ImportVar

# The prefix used to create setters for state vars.
Expand All @@ -14,6 +15,9 @@
# The file used to specify no compilation.
NOCOMPILE_FILE = "nocompile"

# The env var to toggle minification of states.
ENV_MINIFY_STATES = "REFLEX_MINIFY_STATES"


class Ext(SimpleNamespace):
"""Extension used in Reflex."""
Expand All @@ -30,6 +34,20 @@ class Ext(SimpleNamespace):
EXE = ".exe"


def minify_states() -> bool:
"""Whether to minify states.

Returns:
True if states should be minified.
"""
env = os.environ.get(ENV_MINIFY_STATES, None)
if env is not None:
return env.lower() == "true"

# minify states in prod by default
return os.environ.get(ENV_MODE_ENV_VAR, "") == Env.PROD.value


class CompileVars(SimpleNamespace):
"""The variables used during compilation."""

Expand Down Expand Up @@ -61,18 +79,35 @@ class CompileVars(SimpleNamespace):
CONNECT_ERROR = "connectErrors"
# The name of the function for converting a dict to an event.
TO_EVENT = "Event"

# Whether to minify states.
MINIFY_STATES = minify_states()

# The name of the OnLoadInternal state.
ON_LOAD_INTERNAL_STATE = (
"l" if MINIFY_STATES else "reflex___state____on_load_internal_state"
)
# The name of the internal on_load event.
ON_LOAD_INTERNAL = "reflex___state____on_load_internal_state.on_load_internal"
# The name of the internal event to update generic state vars.
UPDATE_VARS_INTERNAL = (
"reflex___state____update_vars_internal_state.update_vars_internal"
ON_LOAD_INTERNAL = f"{ON_LOAD_INTERNAL_STATE}.on_load_internal"
# The name of the UpdateVarsInternal state.
UPDATE_VARS_INTERNAL_STATE = (
"u" if MINIFY_STATES else "reflex___state____update_vars_internal_state"
)
# The name of the internal event to update generic state vars.
UPDATE_VARS_INTERNAL = f"{UPDATE_VARS_INTERNAL_STATE}.update_vars_internal"
# The name of the frontend event exception state
FRONTEND_EXCEPTION_STATE = "reflex___state____frontend_event_exception_state"
FRONTEND_EXCEPTION_STATE = (
"e" if MINIFY_STATES else "reflex___state____frontend_event_exception_state"
)
# The full name of the frontend exception state
FRONTEND_EXCEPTION_STATE_FULL = (
f"reflex___state____state.{FRONTEND_EXCEPTION_STATE}"
)
INTERNAL_STATE_NAMES = {
ON_LOAD_INTERNAL_STATE,
UPDATE_VARS_INTERNAL_STATE,
FRONTEND_EXCEPTION_STATE,
}


class PageNames(SimpleNamespace):
Expand Down
82 changes: 82 additions & 0 deletions reflex/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,62 @@ def __call__(self, *args: Any) -> EventSpec:
return super().__call__(*args)


# Keep track of all state instances to calculate minified state names
state_count: int = 0

all_state_names: Set[str] = set()


def next_minified_state_name() -> str:
"""Get the next minified state name.

Returns:
The next minified state name.

Raises:
RuntimeError: If the minified state name already exists.
"""
global state_count
global all_state_names
num = state_count

# All possible chars for minified state name
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_"
base = len(chars)
state_name = ""

if num == 0:
state_name = chars[0]

while num > 0:
state_name = chars[num % base] + state_name
num = num // base

state_count += 1

if state_name in all_state_names:
raise RuntimeError(f"Minified state name {state_name} already exists")
all_state_names.add(state_name)

return state_name


def generate_state_name() -> str:
"""Generate a minified state name.

Returns:
The minified state name.

Raises:
ValueError: If no more minified state names are available
"""
while name := next_minified_state_name():
if name in constants.CompileVars.INTERNAL_STATE_NAMES:
continue
return name
raise ValueError("No more minified state names available")


class BaseState(Base, ABC, extra=pydantic.Extra.allow):
"""The state of the app."""

Expand Down Expand Up @@ -360,6 +416,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
# A special event handler for setting base vars.
setvar: ClassVar[EventHandler]

# Minified state name
_state_name: ClassVar[Optional[str]] = None

def __init__(
self,
*args,
Expand Down Expand Up @@ -461,6 +520,10 @@ def __init_subclass__(cls, mixin: bool = False, **kwargs):
if mixin:
return

# Generate a minified state name by converting state count to string
if not cls._state_name or cls._state_name in all_state_names:
cls._state_name = generate_state_name()

# Validate the module name.
cls._validate_module_name()

Expand Down Expand Up @@ -780,7 +843,16 @@ def get_name(cls) -> str:

Returns:
The name of the state.

Raises:
RuntimeError: If the state name is not set.
"""
if constants.CompileVars.MINIFY_STATES:
if not cls._state_name:
raise RuntimeError(
"State name minification is enabled, but state name is not set."
)
return cls._state_name
module = cls.__module__.replace(".", "___")
return format.to_snake_case(f"{module}___{cls.__name__}")

Expand Down Expand Up @@ -1852,6 +1924,10 @@ class State(BaseState):
class FrontendEventExceptionState(State):
"""Substate for handling frontend exceptions."""

_state_name: ClassVar[Optional[str]] = (
constants.CompileVars.FRONTEND_EXCEPTION_STATE
)

def handle_frontend_exception(self, stack: str) -> None:
"""Handle frontend exceptions.

Expand All @@ -1869,6 +1945,10 @@ def handle_frontend_exception(self, stack: str) -> None:
class UpdateVarsInternalState(State):
"""Substate for handling internal state var updates."""

_state_name: ClassVar[Optional[str]] = (
constants.CompileVars.UPDATE_VARS_INTERNAL_STATE
)

async def update_vars_internal(self, vars: dict[str, Any]) -> None:
"""Apply updates to fully qualified state vars.

Expand All @@ -1894,6 +1974,8 @@ class OnLoadInternalState(State):
This is a separate substate to avoid deserializing the entire state tree for every page navigation.
"""

_state_name: ClassVar[Optional[str]] = constants.CompileVars.ON_LOAD_INTERNAL_STATE

def on_load_internal(self) -> list[Event | EventSpec] | None:
"""Queue on_load handlers for the current page.

Expand Down
17 changes: 17 additions & 0 deletions tests/test_minify_state.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from typing import Set

from reflex.state import all_state_names, next_minified_state_name


def test_next_minified_state_name():
"""Test that the next_minified_state_name function returns unique state names."""
current_state_count = len(all_state_names)
state_names: Set[str] = set()
gen: int = 10000
for _ in range(gen):
state_name = next_minified_state_name()
assert state_name not in state_names
state_names.add(state_name)
assert len(state_names) == gen

assert len(all_state_names) == current_state_count + gen