diff --git a/integration/test_var_operations.py b/integration/test_var_operations.py
index 5cf644ca2..5898e4e55 100644
--- a/integration/test_var_operations.py
+++ b/integration/test_var_operations.py
@@ -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 = "
hello
"
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)
diff --git a/reflex/__init__.py b/reflex/__init__.py
index faf3e087e..9364f02a2 100644
--- a/reflex/__init__.py
+++ b/reflex/__init__.py
@@ -338,6 +338,7 @@ _SUBMODULES: set[str] = {
"testing",
"utils",
"vars",
+ "ivars",
"config",
"compiler",
}
diff --git a/reflex/__init__.pyi b/reflex/__init__.pyi
index 8fe42cfb3..94103a1d0 100644
--- a/reflex/__init__.pyi
+++ b/reflex/__init__.pyi
@@ -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
diff --git a/reflex/app.py b/reflex/app.py
index 7e40a95bf..90c29fefa 100644
--- a/reflex/app.py
+++ b/reflex/app.py
@@ -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
diff --git a/reflex/compiler/compiler.py b/reflex/compiler/compiler.py
index 4345e244f..525f75cfb 100644
--- a/reflex/compiler/compiler.py
+++ b/reflex/compiler/compiler.py
@@ -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())
diff --git a/reflex/components/base/app_wrap.py b/reflex/components/base/app_wrap.py
index 76bf2d99b..7d6a085b4 100644
--- a/reflex/components/base/app_wrap.py
+++ b/reflex/components/base/app_wrap.py
@@ -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"))
diff --git a/reflex/components/base/bare.py b/reflex/components/base/bare.py
index 0de7307db..8cc83b83e 100644
--- a/reflex/components/base/bare.py
+++ b/reflex/components/base/bare.py
@@ -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]:
diff --git a/reflex/components/base/error_boundary.py b/reflex/components/base/error_boundary.py
index e90f0ed63..058976498 100644
--- a/reflex/components/base/error_boundary.py
+++ b/reflex/components/base/error_boundary.py
@@ -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."),
diff --git a/reflex/components/base/script.py b/reflex/components/base/script.py
index bef2f036b..7b0596966 100644
--- a/reflex/components/base/script.py
+++ b/reflex/components/base/script.py
@@ -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
diff --git a/reflex/components/chakra/base.py b/reflex/components/chakra/base.py
index 0b6b97b61..bd55a9415 100644
--- a/reflex/components/chakra/base.py
+++ b/reflex/components/chakra/base.py
@@ -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:
diff --git a/reflex/components/chakra/forms/checkbox.py b/reflex/components/chakra/forms/checkbox.py
index 7b5ed7b50..bf38b8efe 100644
--- a/reflex/components/chakra/forms/checkbox.py
+++ b/reflex/components/chakra/forms/checkbox.py
@@ -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]
diff --git a/reflex/components/chakra/media/image.py b/reflex/components/chakra/media/image.py
index b68fe8e6a..6a4f59711 100644
--- a/reflex/components/chakra/media/image.py
+++ b/reflex/components/chakra/media/image.py
@@ -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)
diff --git a/reflex/components/component.py b/reflex/components/component.py
index bb3e9053f..159c6e6ca 100644
--- a/reflex/components/component.py
+++ b/reflex/components/component.py
@@ -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)}])",
diff --git a/reflex/components/core/banner.py b/reflex/components/core/banner.py
index c6b46696c..acdab19c5 100644
--- a/reflex/components/core/banner.py
+++ b/reflex/components/core/banner.py
@@ -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,
)
diff --git a/reflex/components/core/cond.py b/reflex/components/core/cond.py
index 5d9ab7544..0f1376d3c 100644
--- a/reflex/components/core/cond.py
+++ b/reflex/components/core/cond.py
@@ -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,
)
diff --git a/reflex/components/core/foreach.py b/reflex/components/core/foreach.py
index 44e0685dd..327680a2b 100644
--- a/reflex/components/core/foreach.py
+++ b/reflex/components/core/foreach.py
@@ -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. "
diff --git a/reflex/components/core/match.py b/reflex/components/core/match.py
index e85739605..a88757263 100644
--- a/reflex/components/core/match.py
+++ b/reflex/components/core/match.py
@@ -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
diff --git a/reflex/components/datadisplay/code.py b/reflex/components/datadisplay/code.py
index 47cba3736..56ea1fd9a 100644
--- a/reflex/components/datadisplay/code.py
+++ b/reflex/components/datadisplay/code.py
@@ -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:
diff --git a/reflex/components/datadisplay/dataeditor.py b/reflex/components/datadisplay/dataeditor.py
index c6d9c1981..f3296e0b7 100644
--- a/reflex/components/datadisplay/dataeditor.py
+++ b/reflex/components/datadisplay/dataeditor.py
@@ -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])" "{"]
diff --git a/reflex/components/el/elements/forms.py b/reflex/components/el/elements/forms.py
index cdad212aa..5c78816a2 100644
--- a/reflex/components/el/elements/forms.py
+++ b/reflex/components/el/elements/forms.py
@@ -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) => {
diff --git a/reflex/components/markdown/markdown.py b/reflex/components/markdown/markdown.py
index e62c85af3..536b43930 100644
--- a/reflex/components/markdown/markdown.py
+++ b/reflex/components/markdown/markdown.py
@@ -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()))}
)
}}
"""
diff --git a/reflex/components/next/image.py b/reflex/components/next/image.py
index 108d17452..ed9e534fb 100644
--- a/reflex/components/next/image.py
+++ b/reflex/components/next/image.py
@@ -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)
diff --git a/reflex/components/radix/primitives/accordion.py b/reflex/components/radix/primitives/accordion.py
index b93bbf284..7c6ab3c63 100644
--- a/reflex/components/radix/primitives/accordion.py
+++ b/reflex/components/radix/primitives/accordion.py
@@ -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 {
diff --git a/reflex/components/radix/themes/base.py b/reflex/components/radix/themes/base.py
index e0e05cc81..8ec509ab8 100644
--- a/reflex/components/radix/themes/base.py
+++ b/reflex/components/radix/themes/base.py
@@ -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
diff --git a/reflex/components/radix/themes/color_mode.py b/reflex/components/radix/themes/color_mode.py
index 8243a822a..f0ef477cc 100644
--- a/reflex/components/radix/themes/color_mode.py
+++ b/reflex/components/radix/themes/color_mode.py
@@ -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,
)
diff --git a/reflex/components/radix/themes/components/radio_group.py b/reflex/components/radix/themes/components/radio_group.py
index 20ccbd5b5..c159742d7 100644
--- a/reflex/components/radix/themes/components/radio_group.py
+++ b/reflex/components/radix/themes/components/radio_group.py
@@ -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(
diff --git a/reflex/components/tags/iter_tag.py b/reflex/components/tags/iter_tag.py
index 95a496b71..0828f8b10 100644
--- a/reflex/components/tags/iter_tag.py
+++ b/reflex/components/tags/iter_tag.py
@@ -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.
diff --git a/reflex/components/tags/tag.py b/reflex/components/tags/tag.py
index bacea6a41..5a26a164f 100644
--- a/reflex/components/tags/tag.py
+++ b/reflex/components/tags/tag.py
@@ -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)
diff --git a/reflex/event.py b/reflex/event.py
index 7e98ade38..1c43334a1 100644
--- a/reflex/event.py
+++ b/reflex/event.py
@@ -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
]
diff --git a/reflex/experimental/__init__.py b/reflex/experimental/__init__.py
index 060f7e7f1..f0eca0c84 100644
--- a/reflex/experimental/__init__.py
+++ b/reflex/experimental/__init__.py
@@ -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,
diff --git a/reflex/experimental/vars/__init__.py b/reflex/ivars/__init__.py
similarity index 100%
rename from reflex/experimental/vars/__init__.py
rename to reflex/ivars/__init__.py
diff --git a/reflex/experimental/vars/base.py b/reflex/ivars/base.py
similarity index 54%
rename from reflex/experimental/vars/base.py
rename to reflex/ivars/base.py
index 7da1e6537..dd12dcb07 100644
--- a/reflex/experimental/vars/base.py
+++ b/reflex/ivars/base.py
@@ -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
diff --git a/reflex/experimental/vars/function.py b/reflex/ivars/function.py
similarity index 98%
rename from reflex/experimental/vars/function.py
rename to reflex/ivars/function.py
index 4514a482d..c20c90772 100644
--- a/reflex/experimental/vars/function.py
+++ b/reflex/ivars/function.py
@@ -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")
diff --git a/reflex/experimental/vars/number.py b/reflex/ivars/number.py
similarity index 88%
rename from reflex/experimental/vars/number.py
rename to reflex/ivars/number.py
index 6bd3a7ff7..0e3424a33 100644
--- a/reflex/experimental/vars/number.py
+++ b/reflex/ivars/number.py
@@ -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
diff --git a/reflex/experimental/vars/object.py b/reflex/ivars/object.py
similarity index 87%
rename from reflex/experimental/vars/object.py
rename to reflex/ivars/object.py
index a227f0d7c..aebc23e1e 100644
--- a/reflex/experimental/vars/object.py
+++ b/reflex/ivars/object.py
@@ -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
diff --git a/reflex/experimental/vars/sequence.py b/reflex/ivars/sequence.py
similarity index 88%
rename from reflex/experimental/vars/sequence.py
rename to reflex/ivars/sequence.py
index f622159a6..5c34886e6 100644
--- a/reflex/experimental/vars/sequence.py
+++ b/reflex/ivars/sequence.py
@@ -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
diff --git a/reflex/state.py b/reflex/state.py
index e29336042..2d90902d4 100644
--- a/reflex/state.py
+++ b/reflex/state.py
@@ -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:
diff --git a/reflex/style.py b/reflex/style.py
index 69e93ed39..a4aeecdc3 100644
--- a/reflex/style.py
+++ b/reflex/style.py
@@ -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)
diff --git a/reflex/utils/format.py b/reflex/utils/format.py
index 59bbbd91c..01b7cb712 100644
--- a/reflex/utils/format.py
+++ b/reflex/utils/format.py
@@ -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,
}
diff --git a/reflex/vars.py b/reflex/vars.py
index ffaf16455..23f2fe6a7 100644
--- a/reflex/vars.py
+++ b/reflex/vars.py
@@ -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
diff --git a/reflex/vars.pyi b/reflex/vars.pyi
index 47d433374..69041d563 100644
--- a/reflex/vars.pyi
+++ b/reflex/vars.pyi
@@ -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)
diff --git a/tests/test_var.py b/tests/test_var.py
index 5c67d9924..3269706d0 100644
--- a/tests/test_var.py
+++ b/tests/test_var.py
@@ -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,