fully migrate vars into new system

This commit is contained in:
Khaleel Al-Adhami 2024-08-05 15:16:21 -07:00
parent ad14f38329
commit de1fbdbe5b
42 changed files with 1258 additions and 513 deletions

View File

@ -15,6 +15,8 @@ def VarOperations():
from typing import Dict, List
import reflex as rx
from reflex.ivars.base import LiteralVar
from reflex.ivars.sequence import ArrayVar
class VarOperationState(rx.State):
int_var1: int = 10
@ -29,8 +31,8 @@ def VarOperations():
str_var2: str = "second"
str_var3: str = "ThIrD"
str_var4: str = "a long string"
dict1: Dict = {1: 2}
dict2: Dict = {3: 4}
dict1: Dict[int, int] = {1: 2}
dict2: Dict[int, int] = {3: 4}
html_str: str = "<div>hello</div>"
app = rx.App(state=rx.State)
@ -547,29 +549,29 @@ def VarOperations():
"second",
query=[VarOperationState.str_var2],
),
rx.text(rx.Var.range(2, 5).join(","), id="list_join_range1"),
rx.text(rx.Var.range(2, 10, 2).join(","), id="list_join_range2"),
rx.text(rx.Var.range(5, 0, -1).join(","), id="list_join_range3"),
rx.text(rx.Var.range(0, 3).join(","), id="list_join_range4"),
rx.text(ArrayVar.range(2, 5).join(","), id="list_join_range1"),
rx.text(ArrayVar.range(2, 10, 2).join(","), id="list_join_range2"),
rx.text(ArrayVar.range(5, 0, -1).join(","), id="list_join_range3"),
rx.text(ArrayVar.range(0, 3).join(","), id="list_join_range4"),
rx.box(
rx.foreach(
rx.Var.range(0, 2),
ArrayVar.range(0, 2),
lambda x: rx.text(VarOperationState.list1[x], as_="p"),
),
id="foreach_list_arg",
),
rx.box(
rx.foreach(
rx.Var.range(0, 2),
ArrayVar.range(0, 2),
lambda x, ix: rx.text(VarOperationState.list1[ix], as_="p"),
),
id="foreach_list_ix",
),
rx.box(
rx.foreach(
rx.Var.create_safe(list(range(0, 3))).to(List[int]),
LiteralVar.create(list(range(0, 3))).to(ArrayVar, List[int]),
lambda x: rx.foreach(
rx.Var.range(x),
ArrayVar.range(x),
lambda y: rx.text(VarOperationState.list1[y], as_="p"),
),
),
@ -783,6 +785,7 @@ def test_var_operations(driver, var_operations: AppHarness):
]
for tag, expected in tests:
print(tag)
assert driver.find_element(By.ID, tag).text == expected
# Highlight component with var query (does not plumb ID)

View File

@ -338,6 +338,7 @@ _SUBMODULES: set[str] = {
"testing",
"utils",
"vars",
"ivars",
"config",
"compiler",
}

View File

@ -12,6 +12,7 @@ from . import compiler as compiler
from . import components as components
from . import config as config
from . import event as event
from . import ivars as ivars
from . import model as model
from . import style as style
from . import testing as testing

View File

@ -527,9 +527,10 @@ class App(MiddlewareMixin, LifespanMixin, Base):
self._enable_state()
else:
for var in component._get_vars(include_children=True):
if not var._var_data:
var_data = var._get_all_var_data()
if not var_data:
continue
if not var._var_data.state:
if not var_data.state:
continue
self._enable_state()
break

View File

@ -17,6 +17,7 @@ from reflex.components.component import (
StatefulComponent,
)
from reflex.config import get_config
from reflex.ivars.base import ImmutableVar
from reflex.state import BaseState
from reflex.style import SYSTEM_COLOR_MODE
from reflex.utils.exec import is_prod_mode
@ -80,7 +81,7 @@ def _compile_contexts(state: Optional[Type[BaseState]], theme: Component | None)
The compiled context file.
"""
appearance = getattr(theme, "appearance", None)
if appearance is None or Var.create_safe(appearance)._var_name == "inherit":
if appearance is None or str(ImmutableVar.create_safe(appearance)) == "inherit":
appearance = SYSTEM_COLOR_MODE
last_compiled_time = str(datetime.now())

View File

@ -2,7 +2,7 @@
from reflex.components.base.fragment import Fragment
from reflex.components.component import Component
from reflex.vars import Var
from reflex.ivars.base import ImmutableVar
class AppWrap(Fragment):
@ -15,6 +15,4 @@ class AppWrap(Fragment):
Returns:
A new AppWrap component containing {children}.
"""
return super().create(
Var.create("{children}", _var_is_local=False, _var_is_string=False)
)
return super().create(ImmutableVar.create("children"))

View File

@ -7,13 +7,14 @@ from typing import Any, Iterator
from reflex.components.component import Component
from reflex.components.tags import Tag
from reflex.components.tags.tagless import Tagless
from reflex.ivars.base import ImmutableVar
from reflex.vars import Var
class Bare(Component):
"""A component with no tag."""
contents: Var[str]
contents: Var[Any]
@classmethod
def create(cls, contents: Any) -> Component:
@ -25,6 +26,8 @@ class Bare(Component):
Returns:
The component.
"""
if isinstance(contents, ImmutableVar):
return cls(contents=contents)
if isinstance(contents, Var) and contents._var_data:
contents = contents.to(str)
else:
@ -32,6 +35,8 @@ class Bare(Component):
return cls(contents=contents) # type: ignore
def _render(self) -> Tag:
if isinstance(self.contents, ImmutableVar):
return Tagless(contents=f"{{{str(self.contents)}}}")
return Tagless(contents=str(self.contents))
def _get_vars(self, include_children: bool = False) -> Iterator[Var]:

View File

@ -9,6 +9,8 @@ from reflex.components.component import Component
from reflex.components.el import div, p
from reflex.constants import Hooks, Imports
from reflex.event import EventChain, EventHandler
from reflex.ivars.base import ImmutableVar
from reflex.ivars.function import FunctionVar
from reflex.utils.imports import ImportVar
from reflex.vars import Var
@ -20,14 +22,14 @@ class ErrorBoundary(Component):
tag = "ErrorBoundary"
# Fired when the boundary catches an error.
on_error: EventHandler[lambda error, info: [error, info]] = Var.create_safe( # type: ignore
"logFrontendError", _var_is_string=False, _var_is_local=False
).to(EventChain)
on_error: EventHandler[lambda error, info: [error, info]] = ImmutableVar( # type: ignore
"logFrontendError"
).to(FunctionVar, EventChain)
# Rendered instead of the children when an error is caught.
Fallback_component: Var[Component] = Var.create_safe(
"Fallback", _var_is_string=False, _var_is_local=False
).to(Component)
Fallback_component: Var[Component] = ImmutableVar.create_safe("Fallback")._replace(
_var_type=Component
)
def add_imports(self) -> dict[str, list[ImportVar]]:
"""Add imports for the component.
@ -56,7 +58,7 @@ class ErrorBoundary(Component):
fallback_container = div(
p("Ooops...Unknown Reflex error has occured:"),
p(
Var.create("error.message", _var_is_local=False, _var_is_string=False),
ImmutableVar.create("error.message"),
color="red",
),
p("Please contact the support."),

View File

@ -9,6 +9,7 @@ from typing import Literal
from reflex.components.component import Component
from reflex.event import EventHandler
from reflex.ivars.base import LiteralVar
from reflex.vars import Var
@ -31,7 +32,7 @@ class Script(Component):
# When the script will execute: afterInteractive (defer-like behavior) | beforeInteractive | lazyOnload (async-like behavior)
strategy: Var[Literal["afterInteractive", "beforeInteractive", "lazyOnload"]] = (
Var.create_safe("afterInteractive", _var_is_string=True)
LiteralVar.create("afterInteractive")
)
# Triggered when the script is loading

View File

@ -6,6 +6,7 @@ from functools import lru_cache
from typing import List, Literal
from reflex.components.component import Component
from reflex.ivars.base import ImmutableVar
from reflex.utils.imports import ImportDict, ImportVar
from reflex.vars import Var
@ -66,9 +67,7 @@ class ChakraProvider(ChakraComponent):
A new ChakraProvider component.
"""
return super().create(
theme=Var.create(
"extendTheme(theme)", _var_is_local=False, _var_is_string=False
),
theme=ImmutableVar.create("extendTheme(theme)"),
)
def add_imports(self) -> ImportDict:

View File

@ -8,6 +8,7 @@ from reflex.components.chakra import (
LiteralTagSize,
)
from reflex.event import EventHandler
from reflex.ivars.base import LiteralVar
from reflex.vars import Var
@ -50,7 +51,7 @@ class Checkbox(ChakraComponent):
name: Var[str]
# The value of the input field when checked (use is_checked prop for a bool)
value: Var[str] = Var.create("true", _var_is_string=True) # type: ignore
value: Var[str] = LiteralVar.create("true")
# The spacing between the checkbox and its label text (0.5rem)
spacing: Var[str]

View File

@ -7,6 +7,7 @@ from typing import Any, Optional
from reflex.components.chakra import ChakraComponent, LiteralImageLoading
from reflex.components.component import Component
from reflex.event import EventHandler
from reflex.ivars.base import LiteralVar
from reflex.vars import Var
@ -70,5 +71,5 @@ class Image(ChakraComponent):
"""
src = props.get("src", None)
if src is not None and not isinstance(src, (Var)):
props["src"] = Var.create(value=src, _var_is_string=True)
props["src"] = LiteralVar.create(value=src)
return super().create(*children, **props)

View File

@ -43,11 +43,12 @@ from reflex.event import (
call_event_handler,
get_handler_args,
)
from reflex.ivars.base import ImmutableVar, LiteralVar
from reflex.style import Style, format_as_emotion
from reflex.utils import console, format, imports, types
from reflex.utils.imports import ImportDict, ImportVar, ParsedImportDict, parse_imports
from reflex.utils.serializers import serializer
from reflex.vars import BaseVar, Var, VarData
from reflex.vars import BaseVar, ImmutableVarData, Var, VarData
class BaseComponent(Base, ABC):
@ -320,9 +321,8 @@ class Component(BaseComponent, ABC):
# Set default values for any props.
if types._issubclass(field.type_, Var):
field.required = False
field.default = Var.create(
field.default, _var_is_string=isinstance(field.default, str)
)
if field.default is not None:
field.default = LiteralVar.create(field.default)
elif types._issubclass(field.type_, EventHandler):
field.required = False
@ -351,10 +351,7 @@ class Component(BaseComponent, ABC):
"id": kwargs.get("id"),
"children": children,
**{
prop: Var.create(
kwargs[prop],
_var_is_string=False if isinstance(kwargs[prop], str) else None,
)
prop: LiteralVar.create(kwargs[prop])
for prop in self.get_initial_props()
if prop in kwargs
},
@ -401,10 +398,10 @@ class Component(BaseComponent, ABC):
passed_types = None
try:
# Try to create a var from the value.
kwargs[key] = Var.create(
value,
_var_is_string=False if isinstance(value, str) else None,
)
if isinstance(value, Var):
kwargs[key] = value
else:
kwargs[key] = LiteralVar.create(value)
# Check that the var type is not None.
if kwargs[key] is None:
@ -448,7 +445,6 @@ class Component(BaseComponent, ABC):
raise TypeError(
f"Invalid var passed for prop {type(self).__name__}.{key}, expected type {expected_type}, got value {value_name} of type {passed_types or passed_type}."
)
# Check if the key is an event trigger.
if key in component_specific_triggers:
# Temporarily disable full control for event triggers.
@ -692,9 +688,7 @@ class Component(BaseComponent, ABC):
# Add ref to element if `id` is not None.
ref = self.get_ref()
if ref is not None:
props["ref"] = Var.create(
ref, _var_is_local=False, _var_is_string=False
)
props["ref"] = ImmutableVar.create(ref)
else:
props = props.copy()
@ -809,7 +803,7 @@ class Component(BaseComponent, ABC):
else (
Fragment.create(*child)
if isinstance(child, tuple)
else Bare.create(contents=Var.create(child, _var_is_string=True))
else Bare.create(contents=LiteralVar.create(child))
)
)
for child in children
@ -936,7 +930,12 @@ class Component(BaseComponent, ABC):
"""
if isinstance(self.style, Var):
return {"css": self.style}
return {"css": Var.create(format_as_emotion(self.style))}
emotion_style = format_as_emotion(self.style)
return (
{"css": LiteralVar.create(emotion_style)}
if emotion_style is not None
else {}
)
def render(self) -> Dict:
"""Render the component.
@ -1091,10 +1090,10 @@ class Component(BaseComponent, ABC):
# Style keeps track of its own VarData instance, so embed in a temp Var that is yielded.
if isinstance(self.style, dict) and self.style or isinstance(self.style, Var):
vars.append(
BaseVar(
ImmutableVar(
_var_name="style",
_var_type=str,
_var_data=self.style._var_data,
_var_data=ImmutableVarData.merge(self.style._var_data),
)
)
@ -1113,10 +1112,8 @@ class Component(BaseComponent, ABC):
vars.append(comp_prop)
elif isinstance(comp_prop, str):
# Collapse VarData encoded in f-strings.
var = Var.create_safe(
comp_prop, _var_is_string=isinstance(comp_prop, str)
)
if var._var_data is not None:
var = LiteralVar.create(comp_prop)
if var._get_all_var_data() is not None:
vars.append(var)
# Get Vars associated with children.
@ -1358,8 +1355,9 @@ class Component(BaseComponent, ABC):
event_imports = Imports.EVENTS if self.event_triggers else {}
# Collect imports from Vars used directly by this component.
var_datas = [var._get_all_var_data() for var in self._get_vars()]
var_imports = [
var._var_data.imports for var in self._get_vars() if var._var_data
var_data.imports for var_data in var_datas if var_data is not None
]
added_import_dicts: list[ParsedImportDict] = []
@ -1427,7 +1425,7 @@ class Component(BaseComponent, ABC):
"""
ref = self.get_ref()
if ref is not None:
return f"const {ref} = useRef(null); {str(Var.create_safe(ref, _var_is_string=False).as_ref())} = {ref};"
return f"const {ref} = useRef(null); {str(ImmutableVar.create_safe(ref).as_ref())} = {ref};"
def _get_vars_hooks(self) -> dict[str, None]:
"""Get the hooks required by vars referenced in this component.
@ -1437,8 +1435,13 @@ class Component(BaseComponent, ABC):
"""
vars_hooks = {}
for var in self._get_vars():
if var._var_data:
vars_hooks.update(var._var_data.hooks)
var_data = var._get_all_var_data()
if var_data is not None:
vars_hooks.update(
var_data.hooks
if isinstance(var_data.hooks, dict)
else {k: None for k in var_data.hooks}
)
return vars_hooks
def _get_events_hooks(self) -> dict[str, None]:
@ -1487,11 +1490,12 @@ class Component(BaseComponent, ABC):
def extract_var_hooks(hook: Var):
_imports = {}
if hook._var_data is not None:
for sub_hook in hook._var_data.hooks:
var_data = VarData.merge(hook._get_all_var_data())
if var_data is not None:
for sub_hook in var_data.hooks:
code[sub_hook] = {}
if hook._var_data.imports:
_imports = hook._var_data.imports
if var_data.imports:
_imports = var_data.imports
if str(hook) in code:
code[str(hook)] = imports.merge_imports(code[str(hook)], _imports)
else:
@ -1505,6 +1509,7 @@ class Component(BaseComponent, ABC):
extract_var_hooks(hook)
else:
code[hook] = {}
return code
def _get_hooks(self) -> str | None:
@ -1561,7 +1566,7 @@ class Component(BaseComponent, ABC):
The ref name.
"""
# do not create a ref if the id is dynamic or unspecified
if self.id is None or isinstance(self.id, BaseVar):
if self.id is None or isinstance(self.id, (BaseVar, ImmutableVar)):
return None
return format.format_ref(self.id)
@ -1707,7 +1712,7 @@ class CustomComponent(Component):
# Handle subclasses of Base.
if isinstance(value, Base):
base_value = Var.create(value)
base_value = LiteralVar.create(value)
# Track hooks and imports associated with Component instances.
if base_value is not None and isinstance(value, Component):
@ -1721,7 +1726,7 @@ class CustomComponent(Component):
else:
value = base_value
else:
value = Var.create(value, _var_is_string=isinstance(value, str))
value = LiteralVar.create(value)
# Set the prop.
self.props[format.to_camel_case(key)] = value
@ -1800,19 +1805,19 @@ class CustomComponent(Component):
"""
return super()._render(props=self.props)
def get_prop_vars(self) -> List[BaseVar]:
def get_prop_vars(self) -> List[ImmutableVar]:
"""Get the prop vars.
Returns:
The prop vars.
"""
return [
BaseVar(
ImmutableVar(
_var_name=name,
_var_type=(
prop._var_type if types._isinstance(prop, Var) else type(prop)
),
)
).guess_type()
for name, prop in self.props.items()
]
@ -1981,7 +1986,7 @@ class StatefulComponent(BaseComponent):
if not should_memoize:
# Determine if any Vars have associated data.
for prop_var in component._get_vars():
if prop_var._var_data:
if prop_var._get_all_var_data():
should_memoize = True
break
@ -1996,7 +2001,7 @@ class StatefulComponent(BaseComponent):
should_memoize = True
break
child = cls._child_var(child)
if isinstance(child, Var) and child._var_data:
if isinstance(child, Var) and child._get_all_var_data():
should_memoize = True
break
@ -2187,12 +2192,12 @@ class StatefulComponent(BaseComponent):
# Calculate Var dependencies accessed by the handler for useCallback dep array.
var_deps = ["addEvents", "Event"]
for arg in event_args:
if arg._var_data is None:
if arg._get_all_var_data() is None:
continue
for hook in arg._var_data.hooks:
for hook in arg._get_all_var_data().hooks:
var_deps.extend(cls._get_hook_deps(hook))
memo_var_data = VarData.merge(
*[var._var_data for var in event_args],
*[var._get_all_var_data() for var in event_args],
VarData(
imports={"react": [ImportVar(tag="useCallback")]},
),
@ -2200,7 +2205,7 @@ class StatefulComponent(BaseComponent):
# Store the memoized function name and hook code for this event trigger.
trigger_memo[event_trigger] = (
Var.create_safe(memo_name, _var_is_string=False)._replace(
ImmutableVar.create_safe(memo_name)._replace(
_var_type=EventChain, merge_var_data=memo_var_data
),
f"const {memo_name} = useCallback({rendered_chain}, [{', '.join(var_deps)}])",

View File

@ -19,47 +19,42 @@ from reflex.components.radix.themes.typography.text import Text
from reflex.components.sonner.toast import Toaster, ToastProps
from reflex.constants import Dirs, Hooks, Imports
from reflex.constants.compiler import CompileVars
from reflex.ivars.base import ImmutableVar, LiteralVar
from reflex.ivars.function import FunctionStringVar
from reflex.ivars.number import BooleanVar
from reflex.ivars.sequence import LiteralArrayVar
from reflex.utils.imports import ImportDict, ImportVar
from reflex.utils.serializers import serialize
from reflex.vars import Var, VarData
from reflex.vars import ImmutableVarData, Var, VarData
connect_error_var_data: VarData = VarData( # type: ignore
imports=Imports.EVENTS,
hooks={Hooks.EVENTS: None},
)
connect_errors: Var = Var.create_safe(
connect_errors: Var = ImmutableVar.create_safe(
value=CompileVars.CONNECT_ERROR,
_var_is_local=True,
_var_is_string=False,
_var_data=connect_error_var_data,
)
connection_error: Var = Var.create_safe(
value="(connectErrors.length > 0) ? connectErrors[connectErrors.length - 1].message : ''",
_var_is_local=False,
_var_is_string=False,
connection_error: Var = ImmutableVar.create_safe(
value="((connectErrors.length > 0) ? connectErrors[connectErrors.length - 1].message : '')",
_var_data=connect_error_var_data,
)
connection_errors_count: Var = Var.create_safe(
connection_errors_count: Var = ImmutableVar.create_safe(
value="connectErrors.length",
_var_is_string=False,
_var_is_local=False,
_var_data=connect_error_var_data,
)
has_connection_errors: Var = Var.create_safe(
value="connectErrors.length > 0",
_var_is_string=False,
has_connection_errors: Var = ImmutableVar.create_safe(
value="(connectErrors.length > 0)",
_var_data=connect_error_var_data,
).to(bool)
).to(BooleanVar)
has_too_many_connection_errors: Var = Var.create_safe(
value="connectErrors.length >= 2",
_var_is_string=False,
has_too_many_connection_errors: Var = ImmutableVar.create_safe(
value="(connectErrors.length >= 2)",
_var_data=connect_error_var_data,
).to(bool)
).to(BooleanVar)
class WebsocketTargetURL(Bare):
@ -77,13 +72,21 @@ class WebsocketTargetURL(Bare):
}
@classmethod
def create(cls) -> Component:
def create(cls) -> ImmutableVar:
"""Create a websocket target URL component.
Returns:
The websocket target URL component.
"""
return super().create(contents="{getBackendURL(env.EVENT).href}")
return ImmutableVar(
_var_name="getBackendURL(env.EVENT).href",
_var_data=ImmutableVarData(
imports={
"/env.json": [ImportVar(tag="env", is_default=True)],
f"/{Dirs.STATE_PATH}": [ImportVar(tag="getBackendURL")],
},
),
)
def default_connection_error() -> list[str | Var | Component]:
@ -112,24 +115,34 @@ class ConnectionToaster(Toaster):
toast_id = "websocket-error"
target_url = WebsocketTargetURL.create()
props = ToastProps( # type: ignore
description=Var.create(
f"`Check if server is reachable at ${target_url}`",
_var_is_string=False,
_var_is_local=False,
description=LiteralVar.create(
f"Check if server is reachable at {target_url}",
),
close_button=True,
duration=120000,
id=toast_id,
)
hook = Var.create_safe(
f"""
const toast_props = {serialize(props)};
const [userDismissed, setUserDismissed] = useState(false);
useEffect(() => {{
if ({has_too_many_connection_errors}) {{
individual_hooks = [
f"const toast_props = {str(LiteralVar.create(props))};",
f"const [userDismissed, setUserDismissed] = useState(false);",
FunctionStringVar(
"useEffect",
_var_data=VarData(
imports={
"react": ["useEffect", "useState"],
**dict(target_url._get_all_var_data().imports), # type: ignore
}
),
).call(
# TODO: This breaks the assumption that Vars are JS expressions
ImmutableVar.create_safe(
f"""
() => {{
if ({str(has_too_many_connection_errors)}) {{
if (!userDismissed) {{
toast.error(
`Cannot connect to server: {connection_error}.`,
`Cannot connect to server: ${{{connection_error}}}.`,
{{...toast_props, onDismiss: () => setUserDismissed(true)}},
)
}}
@ -137,20 +150,16 @@ useEffect(() => {{
toast.dismiss("{toast_id}");
setUserDismissed(false); // after reconnection reset dismissed state
}}
}}, [{connect_errors}]);""",
_var_is_string=False,
)
imports: ImportDict = {
"react": ["useEffect", "useState"],
**target_url._get_imports(), # type: ignore
}
hook._var_data = VarData.merge(
connect_errors._var_data,
VarData(imports=imports),
)
}}
"""
),
LiteralArrayVar([connect_errors]),
),
]
return [
Hooks.EVENTS,
hook,
*individual_hooks,
]
@classmethod
@ -240,6 +249,7 @@ class WifiOffPulse(Icon):
Returns:
The icon component with default props applied.
"""
pulse_var = ImmutableVar.create("pulse")
return super().create(
"wifi_off",
color=props.pop("color", "crimson"),
@ -248,7 +258,7 @@ class WifiOffPulse(Icon):
position=props.pop("position", "fixed"),
bottom=props.pop("botton", "33px"),
right=props.pop("right", "33px"),
animation=Var.create(f"${{pulse}} 1s infinite", _var_is_string=True),
animation=LiteralVar.create(f"{pulse_var} 1s infinite"),
**props,
)

View File

@ -2,17 +2,17 @@
from __future__ import annotations
from typing import Any, Dict, Optional, Union, overload
from typing import Any, Dict, Optional, overload
from reflex.components.base.fragment import Fragment
from reflex.components.component import BaseComponent, Component, MemoizationLeaf
from reflex.components.tags import CondTag, Tag
from reflex.constants import Dirs
from reflex.constants.colors import Color
from reflex.ivars.base import ImmutableVar, LiteralVar
from reflex.ivars.number import TernaryOperator
from reflex.style import LIGHT_COLOR_MODE, resolved_color_mode
from reflex.utils import format
from reflex.utils.imports import ImportDict, ImportVar
from reflex.vars import BaseVar, Var, VarData
from reflex.vars import Var, VarData
_IS_TRUE_IMPORT: ImportDict = {
f"/{Dirs.STATE_PATH}": [ImportVar(tag="isTrue")],
@ -118,10 +118,10 @@ def cond(condition: Any, c1: Component) -> Component: ...
@overload
def cond(condition: Any, c1: Any, c2: Any) -> BaseVar: ...
def cond(condition: Any, c1: Any, c2: Any) -> ImmutableVar: ...
def cond(condition: Any, c1: Any, c2: Any = None):
def cond(condition: Any, c1: Any, c2: Any = None) -> Component | ImmutableVar:
"""Create a conditional component or Prop.
Args:
@ -135,14 +135,8 @@ def cond(condition: Any, c1: Any, c2: Any = None):
Raises:
ValueError: If the arguments are invalid.
"""
var_datas: list[VarData | None] = [
VarData( # type: ignore
imports=_IS_TRUE_IMPORT,
),
]
# Convert the condition to a Var.
cond_var = Var.create(condition)
cond_var = LiteralVar.create(condition)
assert cond_var is not None, "The condition must be set."
# If the first component is a component, create a Cond component.
@ -151,8 +145,6 @@ def cond(condition: Any, c1: Any, c2: Any = None):
c2, BaseComponent
), "Both arguments must be components."
return Cond.create(cond_var, c1, c2)
if isinstance(c1, Var):
var_datas.append(c1._var_data)
# Otherwise, create a conditional Var.
# Check that the second argument is valid.
@ -160,37 +152,20 @@ def cond(condition: Any, c1: Any, c2: Any = None):
raise ValueError("Both arguments must be props.")
if c2 is None:
raise ValueError("For conditional vars, the second argument must be set.")
if isinstance(c2, Var):
var_datas.append(c2._var_data)
def create_var(cond_part):
return Var.create_safe(
cond_part,
_var_is_string=isinstance(cond_part, (str, Color)),
)
return LiteralVar.create_safe(cond_part)
# convert the truth and false cond parts into vars so the _var_data can be obtained.
c1 = create_var(c1)
c2 = create_var(c2)
var_datas.extend([c1._var_data, c2._var_data])
c1_type = c1._var_type if isinstance(c1, Var) else type(c1)
c2_type = c2._var_type if isinstance(c2, Var) else type(c2)
var_type = c1_type if c1_type == c2_type else Union[c1_type, c2_type]
# Create the conditional var.
return cond_var._replace(
_var_name=format.format_cond(
cond=cond_var._var_full_name,
true_value=c1,
false_value=c2,
is_prop=True,
),
_var_type=var_type,
_var_is_local=False,
_var_full_name_needs_state_prefix=False,
merge_var_data=VarData.merge(*var_datas),
return TernaryOperator(
condition=cond_var,
if_true=c1,
if_false=c2,
_var_data=VarData(imports=_IS_TRUE_IMPORT),
)
@ -205,7 +180,7 @@ def color_mode_cond(light: Any, dark: Any = None) -> Var | Component:
The conditional component or prop.
"""
return cond(
resolved_color_mode == Var.create(LIGHT_COLOR_MODE, _var_is_string=True),
resolved_color_mode == LiteralVar.create(LIGHT_COLOR_MODE),
light,
dark,
)

View File

@ -9,6 +9,7 @@ from reflex.components.base.fragment import Fragment
from reflex.components.component import Component
from reflex.components.tags import IterTag
from reflex.constants import MemoizationMode
from reflex.ivars.base import ImmutableVar
from reflex.state import ComponentState
from reflex.utils import console
from reflex.vars import Var
@ -61,7 +62,7 @@ class Foreach(Component):
deprecation_version="0.5.0",
removal_version="0.6.0",
)
iterable = Var.create_safe(iterable, _var_is_string=False)
iterable = ImmutableVar.create_safe(iterable)
if iterable._var_type == Any:
raise ForeachVarError(
f"Could not foreach over var `{iterable._var_full_name}` of type Any. "

View File

@ -5,8 +5,8 @@ from typing import Any, Dict, List, Optional, Tuple, Union
from reflex.components.base import Fragment
from reflex.components.component import BaseComponent, Component, MemoizationLeaf
from reflex.components.core.colors import Color
from reflex.components.tags import MatchTag, Tag
from reflex.ivars.base import LiteralVar
from reflex.style import Style
from reflex.utils import format, types
from reflex.utils.exceptions import MatchTypeError
@ -68,7 +68,7 @@ class Match(MemoizationLeaf):
Raises:
ValueError: If the condition is not provided.
"""
match_cond_var = Var.create(cond, _var_is_string=isinstance(cond, str))
match_cond_var = LiteralVar.create(cond)
if match_cond_var is None:
raise ValueError("The condition must be set")
@ -118,12 +118,11 @@ class Match(MemoizationLeaf):
The case element Var.
"""
_var_data = case_element._var_data if isinstance(case_element, Style) else None # type: ignore
case_element = Var.create(
case_element,
_var_is_string=isinstance(case_element, (str, Color)),
)
case_element = LiteralVar.create(case_element)
if _var_data is not None:
case_element._var_data = VarData.merge(case_element._var_data, _var_data) # type: ignore
case_element._var_data = VarData.merge(
case_element._get_all_var_data(), _var_data
) # type: ignore
return case_element
@classmethod

View File

@ -12,6 +12,7 @@ from reflex.components.radix.themes.components.button import Button
from reflex.components.radix.themes.layout.box import Box
from reflex.constants.colors import Color
from reflex.event import set_clipboard
from reflex.ivars.base import ImmutableVar, LiteralVar
from reflex.style import Style
from reflex.utils import format
from reflex.utils.imports import ImportDict, ImportVar
@ -484,7 +485,7 @@ class CodeBlock(Component):
if children:
props["code"] = children[0]
if not isinstance(props["code"], Var):
props["code"] = Var.create(props["code"], _var_is_string=True)
props["code"] = LiteralVar.create(props["code"])
# Create the component.
code_block = super().create(
@ -505,10 +506,8 @@ class CodeBlock(Component):
out = super()._render()
predicate, qmark, value = self.theme._var_name.partition("?")
out.add_props(
style=Var.create(
style=ImmutableVar.create(
format.to_camel_case(f"{predicate}{qmark}{value.replace('`', '')}"),
_var_is_local=False,
_var_is_string=False,
)
).remove_props("theme", "code")
if self.code is not None:

View File

@ -9,6 +9,7 @@ from reflex.base import Base
from reflex.components.component import Component, NoSSRComponent
from reflex.components.literals import LiteralRowMarker
from reflex.event import EventHandler
from reflex.ivars.base import ImmutableVar
from reflex.utils import console, format, types
from reflex.utils.imports import ImportDict, ImportVar
from reflex.utils.serializers import serializer
@ -293,9 +294,7 @@ class DataEditor(NoSSRComponent):
# Define the name of the getData callback associated with this component and assign to get_cell_content.
data_callback = f"getData_{editor_id}"
self.get_cell_content = Var.create(
data_callback, _var_is_local=False, _var_is_string=False
) # type: ignore
self.get_cell_content = ImmutableVar.create(data_callback) # type: ignore
code = [f"function {data_callback}([col, row])" "{"]

View File

@ -11,13 +11,14 @@ from reflex.components.el.element import Element
from reflex.components.tags.tag import Tag
from reflex.constants import Dirs, EventTriggers
from reflex.event import EventChain, EventHandler
from reflex.ivars.base import ImmutableVar
from reflex.utils.format import format_event_chain
from reflex.utils.imports import ImportDict
from reflex.vars import BaseVar, Var
from .base import BaseHTML
FORM_DATA = Var.create("form_data", _var_is_string=False)
FORM_DATA = ImmutableVar.create("form_data")
HANDLE_SUBMIT_JS_JINJA2 = Environment().from_string(
"""
const handleSubmit_{{ handle_submit_unique_name }} = useCallback((ev) => {

View File

@ -17,6 +17,7 @@ from reflex.components.radix.themes.typography.heading import Heading
from reflex.components.radix.themes.typography.link import Link
from reflex.components.radix.themes.typography.text import Text
from reflex.components.tags.tag import Tag
from reflex.ivars.base import LiteralVar
from reflex.utils import types
from reflex.utils.imports import ImportDict, ImportVar
from reflex.vars import Var
@ -287,7 +288,7 @@ class Markdown(Component):
function {self._get_component_map_name()} () {{
{formatted_hooks}
return (
{str(Var.create(self.format_component_map()))}
{str(LiteralVar.create(self.format_component_map()))}
)
}}
"""

View File

@ -3,6 +3,7 @@
from typing import Any, Literal, Optional, Union
from reflex.event import EventHandler
from reflex.ivars.base import LiteralVar
from reflex.utils import types
from reflex.vars import Var
@ -104,6 +105,6 @@ class Image(NextComponent):
src = props.get("src", None)
if src is not None and not isinstance(src, (Var)):
props["src"] = Var.create(value=src, _var_is_string=True)
props["src"] = LiteralVar.create(src)
return super().create(*children, **props)

View File

@ -11,6 +11,7 @@ from reflex.components.lucide.icon import Icon
from reflex.components.radix.primitives.base import RadixPrimitiveComponent
from reflex.components.radix.themes.base import LiteralAccentColor, LiteralRadius
from reflex.event import EventHandler
from reflex.ivars.base import LiteralVar
from reflex.style import Style
from reflex.vars import Var, get_uuid_string_var
@ -464,14 +465,12 @@ to {
Returns:
The style of the component.
"""
slideDown = Var.create(
slideDown = LiteralVar.create(
f"${{slideDown}} var(--animation-duration) var(--animation-easing)",
_var_is_string=True,
)
slideUp = Var.create(
slideUp = LiteralVar.create(
f"${{slideUp}} var(--animation-duration) var(--animation-easing)",
_var_is_string=True,
)
return {

View File

@ -7,6 +7,7 @@ from typing import Any, Dict, Literal
from reflex.components import Component
from reflex.components.tags import Tag
from reflex.config import get_config
from reflex.ivars.base import ImmutableVar
from reflex.utils.imports import ImportDict, ImportVar
from reflex.vars import Var
@ -230,10 +231,8 @@ class Theme(RadixThemesComponent):
def _render(self, props: dict[str, Any] | None = None) -> Tag:
tag = super()._render(props)
tag.add_props(
css=Var.create(
"{{...theme.styles.global[':root'], ...theme.styles.global.body}}",
_var_is_local=False,
_var_is_string=False,
css=ImmutableVar.create(
f"{{...theme.styles.global[':root'], ...theme.styles.global.body}}"
),
)
return tag

View File

@ -17,7 +17,6 @@ rx.text(
from __future__ import annotations
import dataclasses
from typing import Literal, get_args
from reflex.components.component import BaseComponent
@ -25,6 +24,7 @@ from reflex.components.core.cond import Cond, color_mode_cond, cond
from reflex.components.lucide.icon import Icon
from reflex.components.radix.themes.components.dropdown_menu import dropdown_menu
from reflex.components.radix.themes.components.switch import Switch
from reflex.ivars.base import ImmutableVar
from reflex.style import (
LIGHT_COLOR_MODE,
color_mode,
@ -33,7 +33,7 @@ from reflex.style import (
toggle_color_mode,
)
from reflex.utils import console
from reflex.vars import BaseVar, Var
from reflex.vars import Var
from .components.icon_button import IconButton
@ -195,7 +195,7 @@ class ColorModeSwitch(Switch):
)
class ColorModeNamespace(BaseVar):
class ColorModeNamespace(ImmutableVar):
"""Namespace for color mode components."""
icon = staticmethod(ColorModeIcon.create)
@ -204,5 +204,7 @@ class ColorModeNamespace(BaseVar):
color_mode = color_mode_var_and_namespace = ColorModeNamespace(
**dataclasses.asdict(color_mode)
_var_name=color_mode._var_name,
_var_type=color_mode._var_type,
_var_data=color_mode._var_data,
)

View File

@ -10,6 +10,8 @@ from reflex.components.core.breakpoints import Responsive
from reflex.components.radix.themes.layout.flex import Flex
from reflex.components.radix.themes.typography.text import Text
from reflex.event import EventHandler
from reflex.ivars.base import ImmutableVar, LiteralVar
from reflex.ivars.function import JSON_STRINGIFY
from reflex.vars import Var
from ..base import (
@ -147,28 +149,24 @@ class HighLevelRadioGroup(RadixThemesComponent):
color_scheme = props.pop("color_scheme", None)
default_value = props.pop("default_value", "")
default_value = Var.create(default_value, _var_is_string=True)
default_value = LiteralVar.create(default_value)
# convert only non-strings to json(JSON.stringify) so quotes are not rendered
# for string literal types.
if isinstance(default_value, str) or (
isinstance(default_value, Var) and default_value._var_type is str
):
default_value = Var.create(default_value, _var_is_string=True) # type: ignore
default_value = LiteralVar.create(default_value) # type: ignore
else:
default_value = (
Var.create(default_value, _var_is_string=False)
.to_string() # type: ignore
._replace(_var_is_local=False)
)
default_value = JSON_STRINGIFY.call(ImmutableVar.create(default_value))
def radio_group_item(value: str | Var) -> Component:
item_value = Var.create(value, _var_is_string=False) # type: ignore
item_value = rx.cond(
item_value._type() == str, # type: ignore
item_value,
item_value.to_string()._replace(_var_is_local=False), # type: ignore
)._replace(_var_type=str)
JSON_STRINGIFY.call(item_value), # type: ignore
)
return Text.create(
Flex.create(

View File

@ -6,7 +6,8 @@ import inspect
from typing import TYPE_CHECKING, Any, Callable, List, Tuple, Type, Union, get_args
from reflex.components.tags.tag import Tag
from reflex.vars import BaseVar, Var
from reflex.ivars.base import ImmutableVar
from reflex.vars import Var
if TYPE_CHECKING:
from reflex.components.component import Component
@ -53,10 +54,10 @@ class IterTag(Tag):
Returns:
The index var.
"""
return BaseVar(
return ImmutableVar(
_var_name=self.index_var_name,
_var_type=int,
)
).guess_type()
def get_arg_var(self) -> Var:
"""Get the arg var for the tag (with curly braces).
@ -66,10 +67,10 @@ class IterTag(Tag):
Returns:
The arg var.
"""
return BaseVar(
return ImmutableVar(
_var_name=self.arg_var_name,
_var_type=self.get_iterable_var_type(),
)
).guess_type()
def get_index_var_arg(self) -> Var:
"""Get the index var for the tag (without curly braces).
@ -79,11 +80,10 @@ class IterTag(Tag):
Returns:
The index var.
"""
return BaseVar(
return ImmutableVar(
_var_name=self.index_var_name,
_var_type=int,
_var_is_local=True,
)
).guess_type()
def get_arg_var_arg(self) -> Var:
"""Get the arg var for the tag (without curly braces).
@ -93,11 +93,10 @@ class IterTag(Tag):
Returns:
The arg var.
"""
return BaseVar(
return ImmutableVar(
_var_name=self.arg_var_name,
_var_type=self.get_iterable_var_type(),
_var_is_local=True,
)
).guess_type()
def render_component(self) -> Component:
"""Render the component.

View File

@ -6,6 +6,7 @@ from typing import Any, Dict, List, Optional, Set, Tuple, Union
from reflex.base import Base
from reflex.event import EventChain
from reflex.ivars.base import ImmutableVar, LiteralVar
from reflex.utils import format, types
from reflex.vars import Var
@ -41,7 +42,7 @@ class Tag(Base):
# Convert any props to vars.
if "props" in kwargs:
kwargs["props"] = {
name: Var.create(value, _var_is_string=False)
name: ImmutableVar.create(value)
for name, value in kwargs["props"].items()
}
super().__init__(*args, **kwargs)
@ -63,14 +64,12 @@ class Tag(Base):
Returns:
The tag with the props added.
"""
from reflex.components.core.colors import Color
self.props.update(
{
format.to_camel_case(name, allow_hyphens=True): prop
if types._isinstance(prop, Union[EventChain, dict])
else Var.create(
prop, _var_is_string=isinstance(prop, Color)
format.to_camel_case(name, allow_hyphens=True): (
prop
if types._isinstance(prop, Union[EventChain, dict])
else LiteralVar.create(prop)
) # rx.color is always a string
for name, prop in kwargs.items()
if self.is_valid_prop(prop)

View File

@ -18,9 +18,12 @@ from typing import (
from reflex import constants
from reflex.base import Base
from reflex.ivars.base import ImmutableVar, LiteralVar
from reflex.ivars.function import FunctionStringVar, FunctionVar
from reflex.ivars.object import ObjectVar
from reflex.utils import format
from reflex.utils.types import ArgsSpec
from reflex.vars import BaseVar, Var
from reflex.vars import ImmutableVarData, Var
try:
from typing import Annotated
@ -186,7 +189,7 @@ class EventHandler(EventActionsMixin):
# Get the function args.
fn_args = inspect.getfullargspec(self.fn).args[1:]
fn_args = (Var.create_safe(arg, _var_is_string=False) for arg in fn_args)
fn_args = (ImmutableVar.create_safe(arg) for arg in fn_args)
# Construct the payload.
values = []
@ -197,7 +200,7 @@ class EventHandler(EventActionsMixin):
# Otherwise, convert to JSON.
try:
values.append(Var.create(arg, _var_is_string=isinstance(arg, str)))
values.append(LiteralVar.create(arg))
except TypeError as e:
raise EventHandlerTypeError(
f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}."
@ -264,13 +267,13 @@ class EventSpec(EventActionsMixin):
# Get the remaining unfilled function args.
fn_args = inspect.getfullargspec(self.handler.fn).args[1 + len(self.args) :]
fn_args = (Var.create_safe(arg, _var_is_string=False) for arg in fn_args)
fn_args = (ImmutableVar.create_safe(arg) for arg in fn_args)
# Construct the payload.
values = []
for arg in args:
try:
values.append(Var.create(arg, _var_is_string=isinstance(arg, str)))
values.append(LiteralVar.create(arg))
except TypeError as e:
raise EventHandlerTypeError(
f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}."
@ -388,15 +391,16 @@ class FileUpload(Base):
upload_id = self.upload_id or DEFAULT_UPLOAD_ID
spec_args = [
(
Var.create_safe("files", _var_is_string=False),
Var.create_safe(
f"filesById[{Var.create_safe(upload_id, _var_is_string=True)._var_name_unwrapped}]",
_var_is_string=False,
)._replace(_var_data=upload_files_context_var_data),
ImmutableVar.create_safe("files"),
ImmutableVar(
_var_name="filesById",
_var_type=dict[str, Any],
_var_data=ImmutableVarData.merge(upload_files_context_var_data),
).to(ObjectVar)[LiteralVar.create_safe(upload_id)],
),
(
Var.create_safe("upload_id", _var_is_string=False),
Var.create_safe(upload_id, _var_is_string=True),
ImmutableVar.create_safe("upload_id"),
LiteralVar.create_safe(upload_id),
),
]
if self.on_upload_progress is not None:
@ -424,11 +428,10 @@ class FileUpload(Base):
formatted_chain = str(format.format_prop(on_upload_progress_chain))
spec_args.append(
(
Var.create_safe("on_upload_progress", _var_is_string=False),
BaseVar(
_var_name=formatted_chain.strip("{}"),
_var_type=EventChain,
),
ImmutableVar.create_safe("on_upload_progress"),
FunctionStringVar(
formatted_chain.strip("{}"),
).to(FunctionVar, EventChain),
),
)
return EventSpec(
@ -465,8 +468,8 @@ def server_side(name: str, sig: inspect.Signature, **kwargs) -> EventSpec:
handler=EventHandler(fn=fn),
args=tuple(
(
Var.create_safe(k, _var_is_string=False),
Var.create_safe(v, _var_is_string=isinstance(v, str)),
ImmutableVar.create_safe(k),
LiteralVar.create(v),
)
for k, v in kwargs.items()
),
@ -542,7 +545,7 @@ def set_focus(ref: str) -> EventSpec:
return server_side(
"_set_focus",
get_fn_signature(set_focus),
ref=Var.create_safe(format.format_ref(ref), _var_is_string=True),
ref=ImmutableVar.create_safe(format.format_ref(ref)),
)
@ -573,7 +576,7 @@ def set_value(ref: str, value: Any) -> EventSpec:
return server_side(
"_set_value",
get_fn_signature(set_value),
ref=Var.create_safe(format.format_ref(ref), _var_is_string=True),
ref=ImmutableVar.create_safe(format.format_ref(ref)),
value=value,
)
@ -757,11 +760,13 @@ def _callback_arg_spec(eval_result):
def call_script(
javascript_code: str | Var[str],
callback: EventSpec
| EventHandler
| Callable
| List[EventSpec | EventHandler | Callable]
| None = None,
callback: (
EventSpec
| EventHandler
| Callable
| List[EventSpec | EventHandler | Callable]
| None
) = None,
) -> EventSpec:
"""Create an event handler that executes arbitrary javascript code.
@ -865,10 +870,8 @@ def parse_args_spec(arg_spec: ArgsSpec):
annotations = get_type_hints(arg_spec)
return arg_spec(
*[
BaseVar(
_var_name=f"_{l_arg}",
_var_type=annotations.get(l_arg, FrontendEvent),
_var_is_local=True,
ImmutableVar(f"_{l_arg}").to(
ObjectVar, annotations.get(l_arg, FrontendEvent)
)
for l_arg in spec.args
]

View File

@ -8,7 +8,6 @@ from reflex.components.sonner.toast import toast as toast
from ..utils.console import warn
from . import hooks as hooks
from . import vars as vars
from .assets import asset as asset
from .client_state import ClientStateVar as ClientStateVar
from .layout import layout as layout
@ -43,7 +42,6 @@ _x = ExperimentalNamespace(
asset=asset,
client_state=ClientStateVar.create,
hooks=hooks,
vars=vars,
layout=layout,
progress=progress,
PropsBase=PropsBase,

View File

@ -6,6 +6,7 @@ import dataclasses
import functools
import inspect
import sys
import traceback
from typing import (
TYPE_CHECKING,
Any,
@ -13,12 +14,14 @@ from typing import (
Dict,
Generic,
List,
Literal,
Optional,
Set,
Tuple,
Type,
TypeVar,
Union,
get_args,
overload,
)
@ -26,7 +29,7 @@ from typing_extensions import ParamSpec, get_origin
from reflex import constants
from reflex.base import Base
from reflex.utils import serializers, types
from reflex.utils import console, imports, serializers, types
from reflex.utils.exceptions import VarTypeError
from reflex.vars import (
ImmutableVarData,
@ -44,9 +47,16 @@ if TYPE_CHECKING:
NumberVar,
ToBooleanVarOperation,
ToNumberVarOperation,
EqualOperation,
GreaterThanOperation,
GreaterThanOrEqualOperation,
LessThanOperation,
LessThanOrEqualOperation,
)
from .object import ObjectVar, ToObjectOperation
from .sequence import ArrayVar, StringVar, ToArrayOperation, ToStringOperation
from reflex.state import BaseState
VAR_TYPE = TypeVar("VAR_TYPE")
@ -376,10 +386,10 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
from .function import FunctionVar, ToFunctionOperation
if issubclass(output, FunctionVar):
if fixed_type is not None and not issubclass(fixed_type, Callable):
raise TypeError(
f"Unsupported type {var_type} for FunctionVar. Must be Callable."
)
# if fixed_type is not None and not issubclass(fixed_type, Callable):
# raise TypeError(
# f"Unsupported type {var_type} for FunctionVar. Must be Callable."
# )
return ToFunctionOperation(self, var_type or Callable)
return output(
@ -405,6 +415,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
fixed_type = var_type if inspect.isclass(var_type) else get_origin(var_type)
if fixed_type is Union:
return self
if issubclass(fixed_type, (int, float)):
return self.to(NumberVar, var_type)
if issubclass(fixed_type, dict):
@ -417,6 +430,276 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
return self.to(ObjectVar, var_type)
return self
def get_default_value(self) -> Any:
"""Get the default value of the var.
Returns:
The default value of the var.
Raises:
ImportError: If the var is a dataframe and pandas is not installed.
"""
if types.is_optional(self._var_type):
return None
type_ = (
get_origin(self._var_type)
if types.is_generic_alias(self._var_type)
else self._var_type
)
if type_ is Literal:
args = get_args(self._var_type)
return args[0] if args else None
if issubclass(type_, str):
return ""
if issubclass(type_, types.get_args(Union[int, float])):
return 0
if issubclass(type_, bool):
return False
if issubclass(type_, list):
return []
if issubclass(type_, dict):
return {}
if issubclass(type_, tuple):
return ()
if types.is_dataframe(type_):
try:
import pandas as pd
return pd.DataFrame()
except ImportError as e:
raise ImportError(
"Please install pandas to use dataframes in your app."
) from e
return set() if issubclass(type_, set) else None
def get_setter_name(self, include_state: bool = True) -> str:
"""Get the name of the var's generated setter function.
Args:
include_state: Whether to include the state name in the setter name.
Returns:
The name of the setter function.
"""
setter = constants.SETTER_PREFIX + self._var_name
if self._var_data is None:
return setter
if not include_state or self._var_data.state == "":
return setter
print("get_setter_name", self._var_data.state, setter)
return ".".join((self._var_data.state, setter))
def get_setter(self) -> Callable[[BaseState, Any], None]:
"""Get the var's setter function.
Returns:
A function that that creates a setter for the var.
"""
def setter(state: BaseState, value: Any):
"""Get the setter for the var.
Args:
state: The state within which we add the setter function.
value: The value to set.
"""
if self._var_type in [int, float]:
try:
value = self._var_type(value)
setattr(state, self._var_name, value)
except ValueError:
console.debug(
f"{type(state).__name__}.{self._var_name}: Failed conversion of {value} to '{self._var_type.__name__}'. Value not set.",
)
else:
setattr(state, self._var_name, value)
setter.__qualname__ = self.get_setter_name()
return setter
def __eq__(self, other: Var | Any) -> BooleanVar:
"""
Check if the current variable is equal to the given variable.
Args:
other (Var | Any): The variable to compare with.
Returns:
BooleanVar: A BooleanVar object representing the result of the equality check.
"""
from .number import EqualOperation
return EqualOperation(self, other)
def __ne__(self, other: Var | Any) -> BooleanVar:
"""
Check if the current object is not equal to the given object.
Parameters:
other (Var | Any): The object to compare with.
Returns:
BooleanVar: A BooleanVar object representing the result of the comparison.
"""
from .number import EqualOperation
return ~EqualOperation(self, other)
def __gt__(self, other: Var | Any) -> BooleanVar:
"""
Compare the current instance with another variable and return a BooleanVar representing the result of the greater than operation.
Args:
other (Var | Any): The variable to compare with.
Returns:
BooleanVar: A BooleanVar representing the result of the greater than operation.
"""
from .number import GreaterThanOperation
return GreaterThanOperation(self, other)
def __ge__(self, other: Var | Any) -> BooleanVar:
"""
Check if the value of this variable is greater than or equal to the value of another variable or object.
Args:
other (Var | Any): The variable or object to compare with.
Returns:
BooleanVar: A BooleanVar object representing the result of the comparison.
"""
from .number import GreaterThanOrEqualOperation
return GreaterThanOrEqualOperation(self, other)
def __lt__(self, other: Var | Any) -> BooleanVar:
"""
Compare the current instance with another variable using the less than (<) operator.
Args:
other: The variable to compare with.
Returns:
A `BooleanVar` object representing the result of the comparison.
"""
from .number import LessThanOperation
return LessThanOperation(self, other)
def __le__(self, other: Var | Any) -> BooleanVar:
"""
Compare if the current instance is less than or equal to the given value.
Args:
other: The value to compare with.
Returns:
A BooleanVar object representing the result of the comparison.
"""
from .number import LessThanOrEqualOperation
return LessThanOrEqualOperation(self, other)
def bool(self) -> BooleanVar:
"""Convert the var to a boolean.
Returns:
The boolean var.
"""
from .number import ToBooleanVarOperation
return ToBooleanVarOperation(self)
def __and__(self, other: Var | Any) -> ImmutableVar:
"""Perform a logical AND operation on the current instance and another variable.
Args:
other: The variable to perform the logical AND operation with.
Returns:
A `BooleanVar` object representing the result of the logical AND operation.
"""
return AndOperation(self, other)
def __rand__(self, other: Var | Any) -> ImmutableVar:
"""Perform a logical AND operation on the current instance and another variable.
Args:
other: The variable to perform the logical AND operation with.
Returns:
A `BooleanVar` object representing the result of the logical AND operation.
"""
return AndOperation(other, self)
def __or__(self, other: Var | Any) -> ImmutableVar:
"""Perform a logical OR operation on the current instance and another variable.
Args:
other: The variable to perform the logical OR operation with.
Returns:
A `BooleanVar` object representing the result of the logical OR operation.
"""
return OrOperation(self, other)
def __ror__(self, other: Var | Any) -> ImmutableVar:
"""Perform a logical OR operation on the current instance and another variable.
Args:
other: The variable to perform the logical OR operation with.
Returns:
A `BooleanVar` object representing the result of the logical OR operation.
"""
return OrOperation(other, self)
def __invert__(self) -> BooleanVar:
"""Perform a logical NOT operation on the current instance.
Returns:
A `BooleanVar` object representing the result of the logical NOT operation.
"""
from .number import BooleanNotOperation
return BooleanNotOperation(self.bool())
def to_string(self) -> ImmutableVar:
"""Convert the var to a string.
Returns:
The string var.
"""
from .function import JSON_STRINGIFY
return JSON_STRINGIFY.call(self)
def as_ref(self) -> ImmutableVar:
"""Get a reference to the var.
Returns:
The reference to the var.
"""
from .object import ObjectVar
refs = ImmutableVar(
_var_name="refs",
_var_data=ImmutableVarData(
imports={
f"/{constants.Dirs.STATE_PATH}": [imports.ImportVar(tag="refs")]
}
),
).to(ObjectVar)
return refs[self]
OUTPUT = TypeVar("OUTPUT", bound=ImmutableVar)
@ -457,6 +740,9 @@ class LiteralVar(ImmutableVar):
value.dict(), _var_type=type(value), _var_data=_var_data
)
if isinstance(value, dict):
return LiteralObjectVar(value, _var_data=_var_data)
from .number import LiteralBooleanVar, LiteralNumberVar
from .sequence import LiteralArrayVar, LiteralStringVar
@ -467,7 +753,6 @@ class LiteralVar(ImmutableVar):
int: LiteralNumberVar,
float: LiteralNumberVar,
bool: LiteralBooleanVar,
dict: LiteralObjectVar,
list: LiteralArrayVar,
tuple: LiteralArrayVar,
set: LiteralArrayVar,
@ -581,3 +866,165 @@ def figure_out_type(value: Any) -> Type:
unionize(*(figure_out_type(v) for v in value.values())),
]
return type(value)
@dataclasses.dataclass(
eq=False,
frozen=True,
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class AndOperation(ImmutableVar):
"""Class for the logical AND operation."""
# The first var.
_var1: Var = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
# The second var.
_var2: Var = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
def __init__(
self, var1: Var | Any, var2: Var | Any, _var_data: VarData | None = None
):
"""Initialize the AndOperation.
Args:
var1: The first var.
var2: The second var.
_var_data: Additional hooks and imports associated with the Var.
"""
super(type(self), self).__init__(
_var_name="",
_var_type=Union[var1._var_type, var2._var_type],
_var_data=ImmutableVarData.merge(_var_data),
)
object.__setattr__(
self, "_var1", var1 if isinstance(var1, Var) else LiteralVar.create(var1)
)
object.__setattr__(
self, "_var2", var2 if isinstance(var2, Var) else LiteralVar.create(var2)
)
object.__delattr__(self, "_var_name")
@functools.cached_property
def _cached_var_name(self) -> str:
"""Get the cached var name.
Returns:
The cached var name.
"""
return f"({str(self._var1)} && {str(self._var2)})"
def __getattr__(self, name: str) -> Any:
"""Get an attribute of the var.
Args:
name: The name of the attribute.
Returns:
The attribute.
"""
if name == "_var_name":
return self._cached_var_name
return getattr(super(type(self), self), name)
@functools.cached_property
def _cached_get_all_var_data(self) -> ImmutableVarData | None:
"""Get the cached VarData.
Returns:
The cached VarData.
"""
return ImmutableVarData.merge(
self._var1._get_all_var_data(),
self._var2._get_all_var_data(),
self._var_data,
)
def _get_all_var_data(self) -> ImmutableVarData | None:
"""Get all VarData associated with the Var.
Returns:
The VarData of the components and all of its children.
"""
return self._cached_get_all_var_data
@dataclasses.dataclass(
eq=False,
frozen=True,
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class OrOperation(ImmutableVar):
"""Class for the logical OR operation."""
# The first var.
_var1: Var = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
# The second var.
_var2: Var = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
def __init__(
self, var1: Var | Any, var2: Var | Any, _var_data: VarData | None = None
):
"""Initialize the OrOperation.
Args:
var1: The first var.
var2: The second var.
_var_data: Additional hooks and imports associated with the Var.
"""
super(type(self), self).__init__(
_var_name="",
_var_type=Union[var1._var_type, var2._var_type],
_var_data=ImmutableVarData.merge(_var_data),
)
object.__setattr__(
self, "_var1", var1 if isinstance(var1, Var) else LiteralVar.create(var1)
)
object.__setattr__(
self, "_var2", var2 if isinstance(var2, Var) else LiteralVar.create(var2)
)
object.__delattr__(self, "_var_name")
@functools.cached_property
def _cached_var_name(self) -> str:
"""Get the cached var name.
Returns:
The cached var name.
"""
return f"({str(self._var1)} || {str(self._var2)})"
def __getattr__(self, name: str) -> Any:
"""Get an attribute of the var.
Args:
name: The name of the attribute.
Returns:
The attribute.
"""
if name == "_var_name":
return self._cached_var_name
return getattr(super(type(self), self), name)
@functools.cached_property
def _cached_get_all_var_data(self) -> ImmutableVarData | None:
"""Get the cached VarData.
Returns:
The cached VarData.
"""
return ImmutableVarData.merge(
self._var1._get_all_var_data(),
self._var2._get_all_var_data(),
self._var_data,
)
def _get_all_var_data(self) -> ImmutableVarData | None:
"""Get all VarData associated with the Var.
Returns:
The VarData of the components and all of its children.
"""
return self._cached_get_all_var_data

View File

@ -7,7 +7,7 @@ import sys
from functools import cached_property
from typing import Any, Callable, Optional, Tuple, Type, Union
from reflex.experimental.vars.base import ImmutableVar, LiteralVar
from .base import ImmutableVar, LiteralVar
from reflex.vars import ImmutableVarData, Var, VarData
@ -288,3 +288,6 @@ class ToFunctionOperation(FunctionVar):
The VarData of the components and all of its children.
"""
return self._cached_get_all_var_data
JSON_STRINGIFY = FunctionStringVar("JSON.stringify")

View File

@ -8,7 +8,9 @@ import sys
from functools import cached_property
from typing import Any, Union
from reflex.experimental.vars.base import (
from reflex.utils.types import GenericType
from .base import (
ImmutableVar,
LiteralVar,
)
@ -188,54 +190,6 @@ class NumberVar(ImmutableVar[Union[int, float]]):
"""
return NumberNegateOperation(self)
def __and__(self, other: number_types | boolean_types) -> BooleanAndOperation:
"""Boolean AND two numbers.
Args:
other: The other number.
Returns:
The boolean AND operation.
"""
boolified_other = other.bool() if isinstance(other, Var) else bool(other)
return BooleanAndOperation(self.bool(), boolified_other)
def __rand__(self, other: number_types | boolean_types) -> BooleanAndOperation:
"""Boolean AND two numbers.
Args:
other: The other number.
Returns:
The boolean AND operation.
"""
boolified_other = other.bool() if isinstance(other, Var) else bool(other)
return BooleanAndOperation(boolified_other, self.bool())
def __or__(self, other: number_types | boolean_types) -> BooleanOrOperation:
"""Boolean OR two numbers.
Args:
other: The other number.
Returns:
The boolean OR operation.
"""
boolified_other = other.bool() if isinstance(other, Var) else bool(other)
return BooleanOrOperation(self.bool(), boolified_other)
def __ror__(self, other: number_types | boolean_types) -> BooleanOrOperation:
"""Boolean OR two numbers.
Args:
other: The other number.
Returns:
The boolean OR operation.
"""
boolified_other = other.bool() if isinstance(other, Var) else bool(other)
return BooleanOrOperation(boolified_other, self.bool())
def __invert__(self) -> BooleanNotOperation:
"""Boolean NOT the number.
@ -284,7 +238,7 @@ class NumberVar(ImmutableVar[Union[int, float]]):
"""
return NumberTruncOperation(self)
def __lt__(self, other: number_types | boolean_types) -> LessThanOperation:
def __lt__(self, other: Any) -> LessThanOperation:
"""Less than comparison.
Args:
@ -293,9 +247,11 @@ class NumberVar(ImmutableVar[Union[int, float]]):
Returns:
The result of the comparison.
"""
return LessThanOperation(self, +other)
if isinstance(other, (NumberVar, BooleanVar, int, float, bool)):
return LessThanOperation(self, +other)
return LessThanOperation(self, other)
def __le__(self, other: number_types | boolean_types) -> LessThanOrEqualOperation:
def __le__(self, other: Any) -> LessThanOrEqualOperation:
"""Less than or equal comparison.
Args:
@ -304,9 +260,11 @@ class NumberVar(ImmutableVar[Union[int, float]]):
Returns:
The result of the comparison.
"""
return LessThanOrEqualOperation(self, +other)
if isinstance(other, (NumberVar, BooleanVar, int, float, bool)):
return LessThanOrEqualOperation(self, +other)
return LessThanOrEqualOperation(self, other)
def __eq__(self, other: number_types | boolean_types) -> EqualOperation:
def __eq__(self, other: Any) -> EqualOperation:
"""Equal comparison.
Args:
@ -315,9 +273,11 @@ class NumberVar(ImmutableVar[Union[int, float]]):
Returns:
The result of the comparison.
"""
return EqualOperation(self, +other)
if isinstance(other, (NumberVar, BooleanVar, int, float, bool)):
return EqualOperation(self, +other)
return EqualOperation(self, other)
def __ne__(self, other: number_types | boolean_types) -> NotEqualOperation:
def __ne__(self, other: Any) -> NotEqualOperation:
"""Not equal comparison.
Args:
@ -326,9 +286,11 @@ class NumberVar(ImmutableVar[Union[int, float]]):
Returns:
The result of the comparison.
"""
return NotEqualOperation(self, +other)
if isinstance(other, (NumberVar, BooleanVar, int, float, bool)):
return NotEqualOperation(self, +other)
return NotEqualOperation(self, other)
def __gt__(self, other: number_types | boolean_types) -> GreaterThanOperation:
def __gt__(self, other: Any) -> GreaterThanOperation:
"""Greater than comparison.
Args:
@ -337,11 +299,11 @@ class NumberVar(ImmutableVar[Union[int, float]]):
Returns:
The result of the comparison.
"""
return GreaterThanOperation(self, +other)
if isinstance(other, (NumberVar, BooleanVar, int, float, bool)):
return GreaterThanOperation(self, +other)
return GreaterThanOperation(self, other)
def __ge__(
self, other: number_types | boolean_types
) -> GreaterThanOrEqualOperation:
def __ge__(self, other: Any) -> GreaterThanOrEqualOperation:
"""Greater than or equal comparison.
Args:
@ -350,7 +312,9 @@ class NumberVar(ImmutableVar[Union[int, float]]):
Returns:
The result of the comparison.
"""
return GreaterThanOrEqualOperation(self, +other)
if isinstance(other, (NumberVar, BooleanVar, int, float, bool)):
return GreaterThanOrEqualOperation(self, +other)
return GreaterThanOrEqualOperation(self, other)
def bool(self) -> NotEqualOperation:
"""Boolean conversion.
@ -696,50 +660,6 @@ class NumberTruncOperation(UnaryNumberOperation):
class BooleanVar(ImmutableVar[bool]):
"""Base class for immutable boolean vars."""
def __and__(self, other: bool) -> BooleanAndOperation:
"""AND two booleans.
Args:
other: The other boolean.
Returns:
The boolean AND operation.
"""
return BooleanAndOperation(self, other)
def __rand__(self, other: bool) -> BooleanAndOperation:
"""AND two booleans.
Args:
other: The other boolean.
Returns:
The boolean AND operation.
"""
return BooleanAndOperation(other, self)
def __or__(self, other: bool) -> BooleanOrOperation:
"""OR two booleans.
Args:
other: The other boolean.
Returns:
The boolean OR operation.
"""
return BooleanOrOperation(self, other)
def __ror__(self, other: bool) -> BooleanOrOperation:
"""OR two booleans.
Args:
other: The other boolean.
Returns:
The boolean OR operation.
"""
return BooleanOrOperation(other, self)
def __invert__(self) -> BooleanNotOperation:
"""NOT the boolean.
@ -913,16 +833,16 @@ class BooleanToIntOperation(NumberVar):
frozen=True,
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class NumberComparisonOperation(BooleanVar):
class ComparisonOperation(BooleanVar):
"""Base class for immutable boolean vars that are the result of a comparison operation."""
a: number_types = dataclasses.field(default=0)
b: number_types = dataclasses.field(default=0)
a: Var = dataclasses.field(default_factory=lambda: LiteralBooleanVar(True))
b: Var = dataclasses.field(default_factory=lambda: LiteralBooleanVar(True))
def __init__(
self,
a: number_types,
b: number_types,
a: Var | Any,
b: Var | Any,
_var_data: VarData | None = None,
):
"""Initialize the comparison operation var.
@ -932,13 +852,13 @@ class NumberComparisonOperation(BooleanVar):
b: The second value.
_var_data: Additional hooks and imports associated with the Var.
"""
super(NumberComparisonOperation, self).__init__(
super(ComparisonOperation, self).__init__(
_var_name="",
_var_type=bool,
_var_data=ImmutableVarData.merge(_var_data),
)
object.__setattr__(self, "a", a)
object.__setattr__(self, "b", b)
object.__setattr__(self, "a", a if isinstance(a, Var) else LiteralVar.create(a))
object.__setattr__(self, "b", b if isinstance(b, Var) else LiteralVar.create(b))
object.__delattr__(self, "_var_name")
@cached_property
@ -961,7 +881,7 @@ class NumberComparisonOperation(BooleanVar):
"""
if name == "_var_name":
return self._cached_var_name
getattr(super(NumberComparisonOperation, self), name)
getattr(super(ComparisonOperation, self), name)
@cached_property
def _cached_get_all_var_data(self) -> ImmutableVarData | None:
@ -980,7 +900,7 @@ class NumberComparisonOperation(BooleanVar):
return self._cached_get_all_var_data
class GreaterThanOperation(NumberComparisonOperation):
class GreaterThanOperation(ComparisonOperation):
"""Base class for immutable boolean vars that are the result of a greater than operation."""
@cached_property
@ -995,7 +915,7 @@ class GreaterThanOperation(NumberComparisonOperation):
return f"({str(first_value)} > {str(second_value)})"
class GreaterThanOrEqualOperation(NumberComparisonOperation):
class GreaterThanOrEqualOperation(ComparisonOperation):
"""Base class for immutable boolean vars that are the result of a greater than or equal operation."""
@cached_property
@ -1010,7 +930,7 @@ class GreaterThanOrEqualOperation(NumberComparisonOperation):
return f"({str(first_value)} >= {str(second_value)})"
class LessThanOperation(NumberComparisonOperation):
class LessThanOperation(ComparisonOperation):
"""Base class for immutable boolean vars that are the result of a less than operation."""
@cached_property
@ -1025,7 +945,7 @@ class LessThanOperation(NumberComparisonOperation):
return f"({str(first_value)} < {str(second_value)})"
class LessThanOrEqualOperation(NumberComparisonOperation):
class LessThanOrEqualOperation(ComparisonOperation):
"""Base class for immutable boolean vars that are the result of a less than or equal operation."""
@cached_property
@ -1040,7 +960,7 @@ class LessThanOrEqualOperation(NumberComparisonOperation):
return f"({str(first_value)} <= {str(second_value)})"
class EqualOperation(NumberComparisonOperation):
class EqualOperation(ComparisonOperation):
"""Base class for immutable boolean vars that are the result of an equal operation."""
@cached_property
@ -1052,10 +972,10 @@ class EqualOperation(NumberComparisonOperation):
"""
first_value = self.a if isinstance(self.a, Var) else LiteralVar.create(self.a)
second_value = self.b if isinstance(self.b, Var) else LiteralVar.create(self.b)
return f"({str(first_value)} == {str(second_value)})"
return f"({str(first_value)} === {str(second_value)})"
class NotEqualOperation(NumberComparisonOperation):
class NotEqualOperation(ComparisonOperation):
"""Base class for immutable boolean vars that are the result of a not equal operation."""
@cached_property
@ -1139,36 +1059,6 @@ class LogicalOperation(BooleanVar):
return self._cached_get_all_var_data
class BooleanAndOperation(LogicalOperation):
"""Base class for immutable boolean vars that are the result of a logical AND operation."""
@cached_property
def _cached_var_name(self) -> str:
"""The name of the var.
Returns:
The name of the var.
"""
first_value = self.a if isinstance(self.a, Var) else LiteralVar.create(self.a)
second_value = self.b if isinstance(self.b, Var) else LiteralVar.create(self.b)
return f"({str(first_value)} && {str(second_value)})"
class BooleanOrOperation(LogicalOperation):
"""Base class for immutable boolean vars that are the result of a logical OR operation."""
@cached_property
def _cached_var_name(self) -> str:
"""The name of the var.
Returns:
The name of the var.
"""
first_value = self.a if isinstance(self.a, Var) else LiteralVar.create(self.a)
second_value = self.b if isinstance(self.b, Var) else LiteralVar.create(self.b)
return f"({str(first_value)} || {str(second_value)})"
class BooleanNotOperation(BooleanVar):
"""Base class for immutable boolean vars that are the result of a logical NOT operation."""
@ -1428,7 +1318,7 @@ class ToBooleanVarOperation(BooleanVar):
Returns:
The name of the var.
"""
return str(self._original_value)
return f"Boolean({str(self._original_value)})"
def __getattr__(self, name: str) -> Any:
"""Get an attribute of the var.
@ -1456,3 +1346,84 @@ class ToBooleanVarOperation(BooleanVar):
def _get_all_var_data(self) -> ImmutableVarData | None:
return self._cached_get_all_var_data
class TernaryOperator(ImmutableVar):
"""Base class for immutable vars that are the result of a ternary operation."""
condition: Var = dataclasses.field(default_factory=lambda: LiteralBooleanVar(False))
if_true: Var = dataclasses.field(default_factory=lambda: LiteralNumberVar(0))
if_false: Var = dataclasses.field(default_factory=lambda: LiteralNumberVar(0))
def __init__(
self,
condition: Var | Any,
if_true: Var | Any,
if_false: Var | Any,
_var_type: GenericType | None = None,
_var_data: VarData | None = None,
):
"""Initialize the ternary operation var.
Args:
condition: The condition.
if_true: The value if the condition is true.
if_false: The value if the condition is false.
_var_data: Additional hooks and imports associated with the Var.
"""
condition = (
condition if isinstance(condition, Var) else LiteralVar.create(condition)
)
if_true = if_true if isinstance(if_true, Var) else LiteralVar.create(if_true)
if_false = (
if_false if isinstance(if_false, Var) else LiteralVar.create(if_false)
)
super(TernaryOperator, self).__init__(
_var_name="",
_var_type=_var_type or Union[if_true._var_type, if_false._var_type],
_var_data=ImmutableVarData.merge(_var_data),
)
object.__setattr__(self, "condition", condition)
object.__setattr__(self, "if_true", if_true)
object.__setattr__(self, "if_false", if_false)
object.__delattr__(self, "_var_name")
@cached_property
def _cached_var_name(self) -> str:
"""The name of the var.
Returns:
The name of the var.
"""
return f"({str(self.condition)} ? {str(self.if_true)} : {str(self.if_false)})"
def __getattr__(self, name: str) -> Any:
"""Get an attribute of the var.
Args:
name: The name of the attribute.
Returns:
The attribute value.
"""
if name == "_var_name":
return self._cached_var_name
getattr(super(TernaryOperator, self), name)
@cached_property
def _cached_get_all_var_data(self) -> ImmutableVarData | None:
"""Get all VarData associated with the Var.
Returns:
The VarData of the components and all of its children.
"""
return ImmutableVarData.merge(
self.condition._get_all_var_data(),
self.if_true._get_all_var_data(),
self.if_false._get_all_var_data(),
self._var_data,
)
def _get_all_var_data(self) -> ImmutableVarData | None:
return self._cached_get_all_var_data

View File

@ -22,13 +22,15 @@ from typing import (
from typing_extensions import get_origin
from reflex.experimental.vars.base import (
from reflex.utils import console
from .base import (
ImmutableVar,
LiteralVar,
figure_out_type,
)
from reflex.experimental.vars.number import NumberVar
from reflex.experimental.vars.sequence import ArrayVar, StringVar
from .number import BooleanVar, NumberVar
from .sequence import ArrayVar, StringVar
from reflex.utils.exceptions import VarAttributeError
from reflex.utils.types import GenericType, get_attribute_access_type
from reflex.vars import ImmutableVarData, Var, VarData
@ -46,23 +48,13 @@ OTHER_KEY_TYPE = TypeVar("OTHER_KEY_TYPE")
class ObjectVar(ImmutableVar[OBJECT_TYPE]):
"""Base class for immutable object vars."""
@overload
def _key_type(self: ObjectVar[Dict[KEY_TYPE, VALUE_TYPE]]) -> KEY_TYPE: ...
@overload
def _key_type(self) -> Type: ...
def _key_type(self) -> Type:
"""Get the type of the keys of the object.
Returns:
The type of the keys of the object.
"""
fixed_type = (
self._var_type if isclass(self._var_type) else get_origin(self._var_type)
)
args = get_args(self._var_type) if issubclass(fixed_type, dict) else ()
return args[0] if args else Any
return str
@overload
def _value_type(self: ObjectVar[Dict[KEY_TYPE, VALUE_TYPE]]) -> VALUE_TYPE: ...
@ -82,15 +74,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
args = get_args(self._var_type) if issubclass(fixed_type, dict) else ()
return args[1] if args else Any
@overload
def keys(
self: ObjectVar[Dict[KEY_TYPE, VALUE_TYPE]],
) -> ArrayVar[List[KEY_TYPE]]: ...
@overload
def keys(self) -> ArrayVar: ...
def keys(self) -> ArrayVar:
def keys(self) -> ArrayVar[List[str]]:
"""Get the keys of the object.
Returns:
@ -117,7 +101,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
@overload
def entries(
self: ObjectVar[Dict[KEY_TYPE, VALUE_TYPE]],
) -> ArrayVar[List[Tuple[KEY_TYPE, VALUE_TYPE]]]: ...
) -> ArrayVar[List[Tuple[str, VALUE_TYPE]]]: ...
@overload
def entries(self) -> ArrayVar: ...
@ -258,6 +242,8 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
Returns:
The attribute of the var.
"""
if name.startswith("__") and name.endswith("__"):
return getattr(super(type(self), self), name)
fixed_type = (
self._var_type if isclass(self._var_type) else get_origin(self._var_type)
)
@ -272,6 +258,17 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
else:
return ObjectItemOperation(self, name).guess_type()
def contains(self, key: Var | Any) -> BooleanVar:
"""Check if the object contains a key.
Args:
key: The key to check.
Returns:
The result of the check.
"""
return ObjectHasOwnProperty(self, key)
@dataclasses.dataclass(
eq=False,
@ -369,12 +366,12 @@ class LiteralObjectVar(LiteralVar, ObjectVar[OBJECT_TYPE]):
return ImmutableVarData.merge(
*[
value._get_all_var_data()
for key, value in self._var_value
for value in self._var_value.values()
if isinstance(value, Var)
],
*[
key._get_all_var_data()
for key, value in self._var_value
for key in self._var_value.keys()
if isinstance(key, Var)
],
self._var_data,
@ -802,3 +799,81 @@ class ToObjectOperation(ObjectVar):
The VarData of the components and all of its children.
"""
return self._cached_get_all_var_data
@dataclasses.dataclass(
eq=False,
frozen=True,
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class ObjectHasOwnProperty(BooleanVar):
"""Operation to check if an object has a property."""
value: ObjectVar = dataclasses.field(default_factory=lambda: LiteralObjectVar({}))
key: Var | Any = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
def __init__(
self,
value: ObjectVar,
key: Var | Any,
_var_data: VarData | None = None,
):
"""Initialize the object has own property operation.
Args:
value: The value of the operation.
key: The key to check.
_var_data: Additional hooks and imports associated with the operation.
"""
super(ObjectHasOwnProperty, self).__init__(
_var_name="",
_var_data=ImmutableVarData.merge(_var_data),
)
object.__setattr__(self, "value", value)
object.__setattr__(
self, "key", key if isinstance(key, Var) else LiteralVar.create(key)
)
object.__delattr__(self, "_var_name")
@cached_property
def _cached_var_name(self) -> str:
"""The name of the operation.
Returns:
The name of the operation.
"""
return f"{str(self.value)}.hasOwnProperty({str(self.key)})"
def __getattr__(self, name):
"""Get an attribute of the operation.
Args:
name: The name of the attribute.
Returns:
The attribute of the operation.
"""
if name == "_var_name":
return self._cached_var_name
return super(type(self), self).__getattr__(name)
@cached_property
def _cached_get_all_var_data(self) -> ImmutableVarData | None:
"""Get all VarData associated with the operation.
Returns:
The VarData of the components and all of its children.
"""
return ImmutableVarData.merge(
self.value._get_all_var_data(),
self.key._get_all_var_data(),
self._var_data,
)
def _get_all_var_data(self) -> ImmutableVarData | None:
"""Wrapper method for cached property.
Returns:
The VarData of the components and all of its children.
"""
return self._cached_get_all_var_data

View File

@ -27,13 +27,13 @@ from typing_extensions import get_origin
from reflex import constants
from reflex.constants.base import REFLEX_VAR_OPENING_TAG
from reflex.experimental.vars.base import (
from .base import (
ImmutableVar,
LiteralVar,
figure_out_type,
unionize,
)
from reflex.experimental.vars.number import (
from .number import (
BooleanVar,
LiteralNumberVar,
NotEqualOperation,
@ -71,27 +71,29 @@ class StringVar(ImmutableVar[str]):
"""
return ConcatVarOperation(other, self)
def __mul__(self, other: int) -> ConcatVarOperation:
"""Concatenate two strings.
def __mul__(self, other: NumberVar | int) -> StringVar:
"""
Multiply the sequence by a number or an integer.
Args:
other: The other string.
other (NumberVar | int): The number or integer to multiply the sequence by.
Returns:
The string concatenation operation.
StringVar: The resulting sequence after multiplication.
"""
return ConcatVarOperation(*[self for _ in range(other)])
return (self.split() * other).join()
def __rmul__(self, other: int) -> ConcatVarOperation:
"""Concatenate two strings.
def __rmul__(self, other: NumberVar | int) -> StringVar:
"""
Multiply the sequence by a number or an integer.
Args:
other: The other string.
other (NumberVar | int): The number or integer to multiply the sequence by.
Returns:
The string concatenation operation.
StringVar: The resulting sequence after multiplication.
"""
return ConcatVarOperation(*[self for _ in range(other)])
return (self.split() * other).join()
@overload
def __getitem__(self, i: slice) -> ArrayJoinOperation: ...
@ -596,10 +598,17 @@ class LiteralStringVar(LiteralVar, StringVar):
var_data.interpolations = [
(realstart, realstart + string_length)
]
var_content = value[end : (end + string_length)]
if (
var_content[0] == "{"
and var_content[-1] == "}"
and strings_and_vals
and strings_and_vals[-1][-1] == "$"
):
strings_and_vals[-1] = strings_and_vals[-1][:-1]
var_content = "(" + var_content[1:-1] + ")"
strings_and_vals.append(
ImmutableVar.create_safe(
value[end : (end + string_length)], _var_data=var_data
)
ImmutableVar.create_safe(var_content, _var_data=var_data)
)
value = value[(end + string_length) :]
@ -728,8 +737,6 @@ VALUE_TYPE = TypeVar("VALUE_TYPE")
class ArrayVar(ImmutableVar[ARRAY_VAR_TYPE]):
"""Base class for immutable array vars."""
from reflex.experimental.vars.sequence import StringVar
def join(self, sep: StringVar | str = "") -> ArrayJoinOperation:
"""Join the elements of the array.
@ -739,7 +746,6 @@ class ArrayVar(ImmutableVar[ARRAY_VAR_TYPE]):
Returns:
The joined elements.
"""
from reflex.experimental.vars.sequence import ArrayJoinOperation
return ArrayJoinOperation(self, sep)
@ -751,6 +757,18 @@ class ArrayVar(ImmutableVar[ARRAY_VAR_TYPE]):
"""
return ArrayReverseOperation(self)
def __add__(self, other: ArrayVar[ARRAY_VAR_TYPE]) -> ArrayConcatOperation:
"""
Concatenate two arrays.
Parameters:
other (ArrayVar[ARRAY_VAR_TYPE]): The other array to concatenate.
Returns:
ArrayConcatOperation: The concatenation of the two arrays.
"""
return ArrayConcatOperation(self, other)
@overload
def __getitem__(self, i: slice) -> ArrayVar[ARRAY_VAR_TYPE]: ...
@ -915,6 +933,30 @@ class ArrayVar(ImmutableVar[ARRAY_VAR_TYPE]):
"""
return ArrayContainsOperation(self, other)
def __mul__(self, other: NumberVar | int) -> ArrayVar[ARRAY_VAR_TYPE]:
"""
Multiply the sequence by a number or integer.
Parameters:
other (NumberVar | int): The number or integer to multiply the sequence by.
Returns:
ArrayVar[ARRAY_VAR_TYPE]: The result of multiplying the sequence by the given number or integer.
"""
return ArrayRepeatOperation(self, other)
def __rmul__(self, other: NumberVar | int) -> ArrayVar[ARRAY_VAR_TYPE]:
"""
Multiply the sequence by a number or integer.
Parameters:
other (NumberVar | int): The number or integer to multiply the sequence by.
Returns:
ArrayVar[ARRAY_VAR_TYPE]: The result of multiplying the sequence by the given number or integer.
"""
return ArrayRepeatOperation(self, other)
LIST_ELEMENT = TypeVar("LIST_ELEMENT")
@ -1296,7 +1338,7 @@ class ArrayReverseOperation(ArrayToArrayOperation):
Returns:
The name of the var.
"""
return f"{str(self.a)}.reverse()"
return f"{str(self.a)}.slice().reverse()"
@dataclasses.dataclass(
@ -1762,3 +1804,140 @@ class ToArrayOperation(ArrayVar):
def _get_all_var_data(self) -> ImmutableVarData | None:
return self._cached_get_all_var_data
@dataclasses.dataclass(
eq=False,
frozen=True,
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class ArrayRepeatOperation(ArrayVar):
"""Base class for immutable array vars that are the result of an array repeat operation."""
a: ArrayVar = dataclasses.field(default_factory=lambda: LiteralArrayVar([]))
n: NumberVar = dataclasses.field(default_factory=lambda: LiteralNumberVar(0))
def __init__(
self, a: ArrayVar, n: NumberVar | int, _var_data: VarData | None = None
):
"""Initialize the array repeat operation var.
Args:
a: The array.
n: The number of times to repeat the array.
_var_data: Additional hooks and imports associated with the Var.
"""
super(ArrayRepeatOperation, self).__init__(
_var_name="",
_var_type=a._var_type,
_var_data=ImmutableVarData.merge(_var_data),
)
object.__setattr__(self, "a", a)
object.__setattr__(
self,
"n",
n if isinstance(n, Var) else LiteralNumberVar(n),
)
object.__delattr__(self, "_var_name")
@cached_property
def _cached_var_name(self) -> str:
"""The name of the var.
Returns:
The name of the var.
"""
return f"Array.from({{ length: {str(self.n)} }}).flatMap(() => {str(self.a)})"
def __getattr__(self, name: str) -> Any:
"""Get an attribute of the var.
Args:
name: The name of the attribute.
Returns:
The attribute value.
"""
if name == "_var_name":
return self._cached_var_name
getattr(super(ArrayRepeatOperation, self), name)
@cached_property
def _cached_get_all_var_data(self) -> ImmutableVarData | None:
"""Get all VarData associated with the Var.
Returns:
The VarData of the components and all of its children.
"""
return ImmutableVarData.merge(
self.a._get_all_var_data(), self.n._get_all_var_data(), self._var_data
)
def _get_all_var_data(self) -> ImmutableVarData | None:
return self._cached_get_all_var_data
@dataclasses.dataclass(
eq=False,
frozen=True,
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class ArrayConcatOperation(ArrayVar):
"""Base class for immutable array vars that are the result of an array concat operation."""
a: ArrayVar = dataclasses.field(default_factory=lambda: LiteralArrayVar([]))
b: ArrayVar = dataclasses.field(default_factory=lambda: LiteralArrayVar([]))
def __init__(self, a: ArrayVar, b: ArrayVar, _var_data: VarData | None = None):
"""Initialize the array concat operation var.
Args:
a: The first array.
b: The second array.
_var_data: Additional hooks and imports associated with the Var.
"""
# TODO: Figure out how to merge the types of a and b
super(ArrayConcatOperation, self).__init__(
_var_name="",
_var_type=List[ARRAY_VAR_TYPE],
_var_data=ImmutableVarData.merge(_var_data),
)
object.__setattr__(self, "a", a)
object.__setattr__(self, "b", b)
object.__delattr__(self, "_var_name")
@cached_property
def _cached_var_name(self) -> str:
"""The name of the var.
Returns:
The name of the var.
"""
return f"[...{str(self.a)}, ...{str(self.b)}]"
def __getattr__(self, name: str) -> Any:
"""Get an attribute of the var.
Args:
name: The name of the attribute.
Returns:
The attribute value.
"""
if name == "_var_name":
return self._cached_var_name
getattr(super(ArrayConcatOperation, self), name)
@cached_property
def _cached_get_all_var_data(self) -> ImmutableVarData | None:
"""Get all VarData associated with the Var.
Returns:
The VarData of the components and all of its children.
"""
return ImmutableVarData.merge(
self.a._get_all_var_data(), self.b._get_all_var_data(), self._var_data
)
def _get_all_var_data(self) -> ImmutableVarData | None:
return self._cached_get_all_var_data

View File

@ -32,6 +32,7 @@ import dill
from sqlalchemy.orm import DeclarativeBase
from reflex.config import get_config
from reflex.ivars.base import ImmutableVar
try:
import pydantic.v1 as pydantic
@ -55,7 +56,12 @@ from reflex.utils import console, format, prerequisites, types
from reflex.utils.exceptions import ImmutableStateError, LockExpiredError
from reflex.utils.exec import is_testing_env
from reflex.utils.serializers import SerializedType, serialize, serializer
from reflex.vars import BaseVar, ComputedVar, Var, computed_var
from reflex.vars import (
ComputedVar,
ImmutableVarData,
Var,
computed_var,
)
if TYPE_CHECKING:
from reflex.components.component import Component
@ -298,7 +304,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
vars: ClassVar[Dict[str, Var]] = {}
# The base vars of the class.
base_vars: ClassVar[Dict[str, BaseVar]] = {}
base_vars: ClassVar[Dict[str, ImmutableVar]] = {}
# The computed vars of the class.
computed_vars: ClassVar[Dict[str, ComputedVar]] = {}
@ -520,9 +526,11 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
# Set the base and computed vars.
cls.base_vars = {
f.name: BaseVar(_var_name=f.name, _var_type=f.outer_type_)._var_set_state(
cls
)
f.name: ImmutableVar(
_var_name=format.format_state_name(cls.get_full_name()) + "." + f.name,
_var_type=f.outer_type_,
_var_data=ImmutableVarData.from_state(cls),
).guess_type()
for f in cls.get_fields().values()
if f.name not in cls.get_skip_vars()
}
@ -846,7 +854,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
return getattr(substate, name)
@classmethod
def _init_var(cls, prop: BaseVar):
def _init_var(cls, prop: ImmutableVar):
"""Initialize a variable.
Args:
@ -889,7 +897,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
)
# create the variable based on name and type
var = BaseVar(_var_name=name, _var_type=type_)
var = ImmutableVar(_var_name=name, _var_type=type_).guess_type()
var._var_set_state(cls)
# add the pydantic field dynamically (must be done before _init_var)
@ -909,13 +917,18 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
cls._init_var_dependency_dicts()
@classmethod
def _set_var(cls, prop: BaseVar):
def _set_var(cls, prop: ImmutableVar):
"""Set the var as a class member.
Args:
prop: The var instance to set.
"""
setattr(cls, prop._var_name, prop)
acutal_var_name = (
prop._var_name
if "." not in prop._var_name
else prop._var_name.split(".")[-1]
)
setattr(cls, acutal_var_name, prop)
@classmethod
def _create_event_handler(cls, fn):
@ -935,7 +948,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
cls.setvar = cls.event_handlers["setvar"] = EventHandlerSetVar(state_cls=cls)
@classmethod
def _create_setter(cls, prop: BaseVar):
def _create_setter(cls, prop: ImmutableVar):
"""Create a setter for the var.
Args:
@ -948,14 +961,17 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
setattr(cls, setter_name, event_handler)
@classmethod
def _set_default_value(cls, prop: BaseVar):
def _set_default_value(cls, prop: ImmutableVar):
"""Set the default value for the var.
Args:
prop: The var to set the default value for.
"""
# Get the pydantic field for the var.
field = cls.get_fields()[prop._var_name]
if "." in prop._var_name:
field = cls.get_fields()[prop._var_name.split(".")[-1]]
else:
field = cls.get_fields()[prop._var_name]
if field.required:
default_value = prop.get_default_value()
if default_value is not None:

View File

@ -7,9 +7,11 @@ from typing import Any, Literal, Tuple, Type
from reflex import constants
from reflex.components.core.breakpoints import Breakpoints, breakpoints_values
from reflex.event import EventChain
from reflex.ivars.base import ImmutableVar, LiteralVar
from reflex.ivars.function import FunctionVar
from reflex.utils import format
from reflex.utils.imports import ImportVar
from reflex.vars import BaseVar, CallableVar, Var, VarData
from reflex.vars import ImmutableVarData, Var, VarData
VarData.update_forward_refs() # Ensure all type definitions are resolved
@ -25,7 +27,7 @@ color_mode_imports = {
}
def _color_mode_var(_var_name: str, _var_type: Type = str) -> BaseVar:
def _color_mode_var(_var_name: str, _var_type: Type = str) -> ImmutableVar:
"""Create a Var that destructs the _var_name from ColorModeContext.
Args:
@ -33,24 +35,22 @@ def _color_mode_var(_var_name: str, _var_type: Type = str) -> BaseVar:
_var_type: The type of the Var.
Returns:
The BaseVar for accessing _var_name from ColorModeContext.
The Var that resolves to the color mode.
"""
return BaseVar(
return ImmutableVar(
_var_name=_var_name,
_var_type=_var_type,
_var_is_local=False,
_var_is_string=False,
_var_data=VarData(
_var_data=ImmutableVarData(
imports=color_mode_imports,
hooks={f"const {{ {_var_name} }} = useContext(ColorModeContext)": None},
),
)
).guess_type()
@CallableVar
# @CallableVar
def set_color_mode(
new_color_mode: LiteralColorMode | Var[LiteralColorMode] | None = None,
) -> BaseVar[EventChain]:
) -> Var[EventChain]:
"""Create an EventChain Var that sets the color mode to a specific value.
Note: `set_color_mode` is not a real event and cannot be triggered from a
@ -70,11 +70,14 @@ def set_color_mode(
return base_setter
if not isinstance(new_color_mode, Var):
new_color_mode = Var.create_safe(new_color_mode, _var_is_string=True)
return base_setter._replace(
_var_name=f"() => {base_setter._var_name}({new_color_mode._var_name_unwrapped})",
merge_var_data=new_color_mode._var_data,
)
new_color_mode = LiteralVar.create(new_color_mode)
return ImmutableVar(
f"() => {str(base_setter)}({str(new_color_mode)})",
_var_data=ImmutableVarData.merge(
base_setter._get_all_var_data(), new_color_mode._get_all_var_data()
),
).to(FunctionVar, EventChain)
# Var resolves to the current color mode for the app ("light", "dark" or "system")
@ -111,7 +114,9 @@ def media_query(breakpoint_expr: str):
return f"@media screen and (min-width: {breakpoint_expr})"
def convert_item(style_item: str | Var) -> tuple[str, VarData | None]:
def convert_item(
style_item: str | Var,
) -> tuple[str, VarData | ImmutableVarData | None]:
"""Format a single value in a style dictionary.
Args:
@ -122,13 +127,13 @@ def convert_item(style_item: str | Var) -> tuple[str, VarData | None]:
"""
if isinstance(style_item, Var):
# If the value is a Var, extract the var_data and cast as str.
return str(style_item), style_item._var_data
return str(style_item), style_item._get_all_var_data()
# Otherwise, convert to Var to collapse VarData encoded in f-string.
new_var = Var.create(style_item, _var_is_string=False)
new_var = ImmutableVar.create(style_item)
if new_var is not None and new_var._var_data:
# The wrapped backtick is used to identify the Var for interpolation.
return f"`{str(new_var)}`", new_var._var_data
return f"`{str(new_var)}`", new_var._get_all_var_data()
return style_item, None
@ -175,7 +180,11 @@ def convert(style_dict):
for key, value in style_dict.items():
keys = format_style_key(key)
if isinstance(value, dict):
if isinstance(value, Var):
return_val = value
new_var_data = value._get_all_var_data()
update_out_dict(return_val, keys)
elif isinstance(value, dict):
# Recursively format nested style dictionaries.
return_val, new_var_data = convert(value)
update_out_dict(return_val, keys)
@ -254,7 +263,7 @@ class Style(dict):
value: The value to set.
"""
# Create a Var to collapse VarData encoded in f-string.
_var = Var.create(value, _var_is_string=False)
_var = ImmutableVar.create(value)
if _var is not None:
# Carry the imports/hooks when setting a Var as a value.
self._var_data = VarData.merge(self._var_data, _var._var_data)

View File

@ -9,6 +9,8 @@ import re
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Union
from reflex import constants
from reflex.ivars.base import ImmutableVar
from reflex.ivars.function import FunctionVar
from reflex.utils import exceptions, types
from reflex.vars import BaseVar, Var
@ -483,8 +485,14 @@ def format_props(*single_props, **key_value_props) -> list[str]:
The formatted props list.
"""
# Format all the props.
from reflex.ivars.base import ImmutableVar
return [
f"{name}={format_prop(prop)}"
(
f"{name}={{{format_prop(prop)}}}"
if isinstance(prop, ImmutableVar)
else f"{name}={format_prop(prop)}"
)
for name, prop in sorted(key_value_props.items())
if prop is not None
] + [str(prop) for prop in single_props]
@ -613,11 +621,13 @@ def format_event_chain(
def format_queue_events(
events: EventSpec
| EventHandler
| Callable
| List[EventSpec | EventHandler | Callable]
| None = None,
events: (
EventSpec
| EventHandler
| Callable
| List[EventSpec | EventHandler | Callable]
| None
) = None,
args_spec: Optional[ArgsSpec] = None,
) -> Var[EventChain]:
"""Format a list of event handler / event spec as a javascript callback.
@ -647,9 +657,7 @@ def format_queue_events(
)
if not events:
return Var.create_safe(
"() => null", _var_is_string=False, _var_is_local=False
).to(EventChain)
return ImmutableVar("(() => null)").to(FunctionVar, EventChain)
# If no spec is provided, the function will take no arguments.
def _default_args_spec():
@ -682,12 +690,10 @@ def format_queue_events(
# Return the final code snippet, expecting queueEvents, processEvent, and socket to be in scope.
# Typically this snippet will _only_ run from within an rx.call_script eval context.
return Var.create_safe(
return ImmutableVar(
f"{arg_def} => {{queueEvents([{','.join(payloads)}], {constants.CompileVars.SOCKET}); "
f"processEvent({constants.CompileVars.SOCKET})}}",
_var_is_string=False,
_var_is_local=False,
).to(EventChain)
).to(FunctionVar, EventChain)
def format_query_params(router_data: dict[str, Any]) -> dict[str, str]:
@ -939,6 +945,6 @@ def format_data_editor_cell(cell: Any):
The formatted cell.
"""
return {
"kind": Var.create(value="GridCellKind.Text", _var_is_string=False),
"kind": ImmutableVar.create("GridCellKind.Text"),
"data": cell,
}

View File

@ -345,6 +345,33 @@ class ImmutableVarData:
== imports.collapse_imports(other.imports)
)
@classmethod
def from_state(cls, state: Type[BaseState] | str) -> ImmutableVarData:
"""Set the state of the var.
Args:
state: The state to set or the full name of the state.
Returns:
The var with the set state.
"""
from reflex.utils import format
state_name = state if isinstance(state, str) else state.get_full_name()
new_var_data = ImmutableVarData(
state=state_name,
hooks={
"const {0} = useContext(StateContexts.{0})".format(
format.format_state_name(state_name)
): None
},
imports={
f"/{constants.Dirs.CONTEXTS_PATH}": [ImportVar(tag="StateContexts")],
"react": [ImportVar(tag="useContext")],
},
)
return new_var_data
def _decode_var_immutable(value: str) -> tuple[ImmutableVarData | None, str]:
"""Decode the state name from a formatted var.
@ -800,11 +827,19 @@ class Var:
"""
from reflex.utils import format
out = (
self._var_full_name
if self._var_is_local
else format.wrap(self._var_full_name, "{")
)
if self._var_is_local:
console.deprecate(
feature_name="Local Vars",
reason=(
"Setting _var_is_local to True does not have any effect anymore. "
"Use the new ImmutableVar instead."
),
deprecation_version="0.5.9",
removal_version="0.6.0",
)
out = self._var_full_name
else:
out = format.wrap(self._var_full_name, "{")
if self._var_is_string:
out = format.format_string(out)
return out

View File

@ -63,6 +63,8 @@ class ImmutableVarData:
def merge(
cls, *others: ImmutableVarData | VarData | None
) -> ImmutableVarData | None: ...
@classmethod
def from_state(cls, state: Type[BaseState] | str) -> ImmutableVarData: ...
def _decode_var_immutable(value: str) -> tuple[ImmutableVarData, str]: ...
@ -150,7 +152,7 @@ class Var:
@property
def _var_full_name(self) -> str: ...
def _var_set_state(self, state: Type[BaseState] | str) -> Any: ...
def _get_all_var_data(self) -> VarData: ...
def _get_all_var_data(self) -> VarData | ImmutableVarData: ...
def json(self) -> str: ...
@dataclass(eq=False)

View File

@ -8,19 +8,19 @@ from pandas import DataFrame
from reflex.base import Base
from reflex.constants.base import REFLEX_VAR_CLOSING_TAG, REFLEX_VAR_OPENING_TAG
from reflex.experimental.vars.base import (
from reflex.ivars.base import (
ImmutableVar,
LiteralVar,
var_operation,
)
from reflex.experimental.vars.function import ArgsFunctionOperation, FunctionStringVar
from reflex.experimental.vars.number import (
from reflex.ivars.function import ArgsFunctionOperation, FunctionStringVar
from reflex.ivars.number import (
LiteralBooleanVar,
LiteralNumberVar,
NumberVar,
)
from reflex.experimental.vars.object import LiteralObjectVar
from reflex.experimental.vars.sequence import (
from reflex.ivars.object import LiteralObjectVar
from reflex.ivars.sequence import (
ArrayVar,
ConcatVarOperation,
LiteralArrayVar,