get reflex-web to compile

This commit is contained in:
Khaleel Al-Adhami 2024-08-08 17:33:12 -07:00
parent 81cd679802
commit ea33c3987a
42 changed files with 2413 additions and 1388 deletions

View File

@ -324,7 +324,8 @@ _MAPPING: dict = {
"style": ["Style", "toggle_color_mode"],
"utils.imports": ["ImportVar"],
"utils.serializers": ["serializer"],
"vars": ["cached_var", "Var"],
"vars": ["Var"],
"ivars.base": ["cached_var"],
}
_SUBMODULES: set[str] = {

View File

@ -175,6 +175,7 @@ from .event import stop_propagation as stop_propagation
from .event import upload_files as upload_files
from .event import window_alert as window_alert
from .experimental import _x as _x
from .ivars.base import cached_var as cached_var
from .middleware import Middleware as Middleware
from .middleware import middleware as middleware
from .model import Model as Model
@ -191,7 +192,6 @@ from .style import toggle_color_mode as toggle_color_mode
from .utils.imports import ImportVar as ImportVar
from .utils.serializers import serializer as serializer
from .vars import Var as Var
from .vars import cached_var as cached_var
del compat
RADIX_THEMES_MAPPING: dict

View File

@ -442,7 +442,11 @@ class App(MiddlewareMixin, LifespanMixin, Base):
raise
except TypeError as e:
message = str(e)
if "BaseVar" in message or "ComputedVar" in message:
if (
"BaseVar" in message
or "ComputedVar" in message
or "ImmutableComputedVar" in message
):
raise VarOperationTypeError(
"You may be trying to use an invalid Python function on a state var. "
"When referencing a var inside your render code, only limited var operations are supported. "

View File

@ -28,7 +28,7 @@ class Bare(Component):
"""
if isinstance(contents, ImmutableVar):
return cls(contents=contents)
if isinstance(contents, Var) and contents._var_data:
if isinstance(contents, Var) and contents._get_all_var_data():
contents = contents.to(str)
else:
contents = str(contents) if contents is not None else ""

View File

@ -1121,7 +1121,8 @@ class Component(BaseComponent, ABC):
for child in self.children:
if not isinstance(child, Component):
continue
vars.extend(child._get_vars(include_children=include_children))
child_vars = child._get_vars(include_children=include_children)
vars.extend(child_vars)
return vars
@ -1326,13 +1327,13 @@ class Component(BaseComponent, ABC):
other_imports = []
user_hooks = self._get_hooks()
if (
user_hooks is not None
and isinstance(user_hooks, Var)
and user_hooks._var_data is not None
and user_hooks._var_data.imports
):
other_imports.append(user_hooks._var_data.imports)
user_hooks_data = (
VarData.merge(user_hooks._get_all_var_data())
if user_hooks is not None and isinstance(user_hooks, Var)
else None
)
if user_hooks_data is not None:
other_imports.append(user_hooks_data.imports)
other_imports.extend(
hook_imports for hook_imports in self._get_added_hooks().values()
)
@ -1830,9 +1831,11 @@ class CustomComponent(Component):
Returns:
Each var referenced by the component (props, styles, event handlers).
"""
return super()._get_vars(include_children=include_children) + [
prop for prop in self.props.values() if isinstance(prop, Var)
]
return (
super()._get_vars(include_children=include_children)
+ [prop for prop in self.props.values() if isinstance(prop, Var)]
+ self.get_component(self)._get_vars(include_children=include_children)
)
@lru_cache(maxsize=None) # noqa
def get_component(self) -> Component:

View File

@ -153,7 +153,7 @@ class ConnectionToaster(Toaster):
}}
"""
),
LiteralArrayVar([connect_errors]),
LiteralArrayVar.create([connect_errors]),
),
]

View File

@ -29,7 +29,7 @@ class WebsocketTargetURL(Bare):
def create( # type: ignore
cls,
*children,
contents: Optional[Union[Var[str], str]] = None,
contents: Optional[Union[Var[Any], Any]] = None,
style: Optional[Style] = None,
key: Optional[Any] = None,
id: Optional[Any] = None,

View File

@ -104,7 +104,7 @@ class Cond(MemoizationLeaf):
The import dict for the component.
"""
cond_imports: dict[str, str | ImportVar | list[str | ImportVar]] = getattr(
self.cond._var_data, "imports", {}
VarData.merge(self.cond._get_all_var_data()), "imports", {}
)
return {**cond_imports, **_IS_TRUE_IMPORT}
@ -135,6 +135,8 @@ def cond(condition: Any, c1: Any, c2: Any = None) -> Component | ImmutableVar:
Raises:
ValueError: If the arguments are invalid.
"""
if isinstance(condition, Var) and not isinstance(condition, ImmutableVar):
condition._var_is_local = True
# Convert the condition to a Var.
cond_var = LiteralVar.create(condition)
assert cond_var is not None, "The condition must be set."
@ -161,8 +163,8 @@ def cond(condition: Any, c1: Any, c2: Any = None) -> Component | ImmutableVar:
c2 = create_var(c2)
# Create the conditional var.
return TernaryOperator(
condition=cond_var,
return TernaryOperator.create(
condition=cond_var.to(bool),
if_true=c1,
if_false=c2,
_var_data=VarData(imports=_IS_TRUE_IMPORT),

View File

@ -11,7 +11,7 @@ from reflex.style import Style
from reflex.utils import format, types
from reflex.utils.exceptions import MatchTypeError
from reflex.utils.imports import ImportDict
from reflex.vars import ImmutableVarData, Var
from reflex.vars import ImmutableVarData, Var, VarData
class Match(MemoizationLeaf):
@ -264,7 +264,7 @@ class Match(MemoizationLeaf):
Returns:
The import dict.
"""
return getattr(self.cond._var_data, "imports", {})
return getattr(VarData.merge(self.cond._get_all_var_data()), "imports", {})
match = Match.create

View File

@ -22,7 +22,7 @@ from reflex.event import (
from reflex.ivars.base import ImmutableCallableVar, ImmutableVar
from reflex.ivars.sequence import LiteralStringVar
from reflex.utils.imports import ImportVar
from reflex.vars import Var, VarData
from reflex.vars import ImmutableVarData, Var, VarData
DEFAULT_UPLOAD_ID: str = "default"
@ -61,7 +61,7 @@ def upload_file(id_: str = DEFAULT_UPLOAD_ID) -> ImmutableVar:
return ImmutableVar(
_var_name=var_name,
_var_type=EventChain,
_var_data=VarData.merge(
_var_data=ImmutableVarData.merge(
upload_files_context_var_data, id_var._get_all_var_data()
),
)
@ -81,7 +81,7 @@ def selected_files(id_: str = DEFAULT_UPLOAD_ID) -> ImmutableVar:
return ImmutableVar(
_var_name=f"(filesById[{str(id_var)}] ? filesById[{str(id_var)}].map((f) => (f.path || f.name)) : [])",
_var_type=List[str],
_var_data=VarData.merge(
_var_data=ImmutableVarData.merge(
upload_files_context_var_data, id_var._get_all_var_data()
),
).guess_type()

View File

@ -12,16 +12,17 @@ from reflex.event import (
EventHandler,
EventSpec,
)
from reflex.ivars.base import ImmutableCallableVar, ImmutableVar
from reflex.style import Style
from reflex.vars import BaseVar, CallableVar, Var, VarData
from reflex.vars import BaseVar, Var, VarData
DEFAULT_UPLOAD_ID: str
upload_files_context_var_data: VarData
@CallableVar
def upload_file(id_: str = DEFAULT_UPLOAD_ID) -> BaseVar: ...
@CallableVar
def selected_files(id_: str = DEFAULT_UPLOAD_ID) -> BaseVar: ...
@ImmutableCallableVar
def upload_file(id_: str = DEFAULT_UPLOAD_ID) -> ImmutableVar: ...
@ImmutableCallableVar
def selected_files(id_: str = DEFAULT_UPLOAD_ID) -> ImmutableVar: ...
@CallableEventSpec
def clear_selected_files(id_: str = DEFAULT_UPLOAD_ID) -> EventSpec: ...
def cancel_upload(upload_id: str) -> EventSpec: ...

View File

@ -390,7 +390,7 @@ class CodeBlock(Component):
The import dict.
"""
imports_: ImportDict = {}
themes = re.findall(r"`(.*?)`", self.theme._var_name)
themes = re.findall(r'"(.*?)"', self.theme._var_name)
if not themes:
themes = [self.theme._var_name]
@ -509,11 +509,8 @@ class CodeBlock(Component):
style=ImmutableVar.create(
format.to_camel_case(f"{predicate}{qmark}{value.replace('`', '')}"),
)
).remove_props("theme", "code")
if self.code is not None:
out.special_props.add(
Var.create_safe(f"children={str(self.code)}", _var_is_string=False)
)
).remove_props("theme", "code").add_props(children=self.code)
return out
@staticmethod

View File

@ -14,7 +14,7 @@ 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 Var
from reflex.vars import Var, VarData
from .base import BaseHTML
@ -218,7 +218,7 @@ class Form(BaseHTML):
f"getRefValues({str(ref_var)})",
_var_is_local=False,
_var_is_string=False,
_var_data=ref_var._var_data,
_var_data=VarData.merge(ref_var._get_all_var_data()),
)
else:
ref_var = Var.create_safe(ref, _var_is_string=False).as_ref()
@ -226,7 +226,7 @@ class Form(BaseHTML):
f"getRefValue({str(ref_var)})",
_var_is_local=False,
_var_is_string=False,
_var_data=ref_var._var_data,
_var_data=VarData.merge(ref_var._get_all_var_data()),
)
return form_refs
@ -632,7 +632,7 @@ class Textarea(BaseHTML):
f"(e) => enterKeySubmitOnKeyDown(e, {self.enter_key_submit._var_name_unwrapped})",
_var_is_local=False,
_var_is_string=False,
_var_data=self.enter_key_submit._var_data,
_var_data=VarData.merge(self.enter_key_submit._get_all_var_data()),
)
)
if self.auto_height is not None:
@ -641,7 +641,7 @@ class Textarea(BaseHTML):
f"(e) => autoHeightOnInput(e, {self.auto_height._var_name_unwrapped})",
_var_is_local=False,
_var_is_string=False,
_var_data=self.auto_height._var_data,
_var_data=VarData.merge(self.auto_height._get_all_var_data()),
)
)
return tag

View File

@ -9,13 +9,14 @@ from jinja2 import Environment
from reflex.components.el.element import Element
from reflex.event import EventHandler, EventSpec
from reflex.ivars.base import ImmutableVar
from reflex.style import Style
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(
"\n const handleSubmit_{{ handle_submit_unique_name }} = useCallback((ev) => {\n const $form = ev.target\n ev.preventDefault()\n const {{ form_data }} = {...Object.fromEntries(new FormData($form).entries()), ...{{ field_ref_mapping }}}\n\n {{ on_submit_event_chain }}\n\n if ({{ reset_on_submit }}) {\n $form.reset()\n }\n })\n "
)

View File

@ -29,24 +29,49 @@ class Link(BaseHTML): # noqa: E742
tag = "link"
# Specifies the CORS settings for the linked resource
cross_origin: Var[Union[str, int, bool]]
# Specifies the URL of the linked document/resource
href: Var[Union[str, int, bool]]
# Specifies the language of the text in the linked document
href_lang: Var[Union[str, int, bool]]
# Allows a browser to check the fetched link for integrity
integrity: Var[Union[str, int, bool]]
# Specifies on what device the linked document will be displayed
media: Var[Union[str, int, bool]]
# Specifies the referrer policy of the linked document
referrer_policy: Var[Union[str, int, bool]]
# Specifies the relationship between the current document and the linked one
rel: Var[Union[str, int, bool]]
# Specifies the sizes of icons for visual media
sizes: Var[Union[str, int, bool]]
# Specifies the MIME type of the linked document
type: Var[Union[str, int, bool]]
class Meta(BaseHTML): # Inherits common attributes from BaseHTML
"""Display the meta element."""
tag = "meta"
tag = "meta" # The HTML tag for this element is <meta>
# Specifies the character encoding for the HTML document
char_set: Var[Union[str, int, bool]]
# Defines the content of the metadata
content: Var[Union[str, int, bool]]
# Provides an HTTP header for the information/value of the content attribute
http_equiv: Var[Union[str, int, bool]]
# Specifies a name for the metadata
name: Var[Union[str, int, bool]]

View File

@ -346,6 +346,15 @@ class Link(BaseHTML):
Args:
*children: The children of the component.
cross_origin: Specifies the CORS settings for the linked resource
href: Specifies the URL of the linked document/resource
href_lang: Specifies the language of the text in the linked document
integrity: Allows a browser to check the fetched link for integrity
media: Specifies on what device the linked document will be displayed
referrer_policy: Specifies the referrer policy of the linked document
rel: Specifies the relationship between the current document and the linked one
sizes: Specifies the sizes of icons for visual media
type: Specifies the MIME type of the linked document
access_key: Provides a hint for generating a keyboard shortcut for the current element.
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
content_editable: Indicates whether the element's content is editable.
@ -466,6 +475,10 @@ class Meta(BaseHTML):
Args:
*children: The children of the component.
char_set: Specifies the character encoding for the HTML document
content: Defines the content of the metadata
http_equiv: Provides an HTTP header for the information/value of the content attribute
name: Specifies a name for the metadata
access_key: Provides a hint for generating a keyboard shortcut for the current element.
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
content_editable: Indicates whether the element's content is editable.

View File

@ -6,6 +6,7 @@ from typing import Any, Dict, List, Union
from reflex.components.component import Component
from reflex.components.tags import Tag
from reflex.ivars.base import ImmutableComputedVar
from reflex.utils import types
from reflex.utils.imports import ImportDict
from reflex.utils.serializers import serialize
@ -65,14 +66,17 @@ class DataTable(Gridjs):
# The annotation should be provided if data is a computed var. We need this to know how to
# render pandas dataframes.
if isinstance(data, ComputedVar) and data._var_type == Any:
if (
isinstance(data, (ComputedVar, ImmutableComputedVar))
and data._var_type == Any
):
raise ValueError(
"Annotation of the computed var assigned to the data field should be provided."
)
if (
columns is not None
and isinstance(columns, ComputedVar)
and isinstance(columns, (ComputedVar, ImmutableComputedVar))
and columns._var_type == Any
):
raise ValueError(

View File

@ -17,30 +17,26 @@ 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.ivars.base import ImmutableVar, LiteralVar
from reflex.utils import types
from reflex.utils.imports import ImportDict, ImportVar
from reflex.vars import Var
# Special vars used in the component map.
_CHILDREN = Var.create_safe("children", _var_is_local=False, _var_is_string=False)
_PROPS = Var.create_safe("...props", _var_is_local=False, _var_is_string=False)
_MOCK_ARG = Var.create_safe("", _var_is_string=False)
_CHILDREN = ImmutableVar.create_safe("children")
_PROPS = ImmutableVar.create_safe("...props")
_MOCK_ARG = ImmutableVar.create_safe("")
# Special remark plugins.
_REMARK_MATH = Var.create_safe("remarkMath", _var_is_local=False, _var_is_string=False)
_REMARK_GFM = Var.create_safe("remarkGfm", _var_is_local=False, _var_is_string=False)
_REMARK_UNWRAP_IMAGES = Var.create_safe(
"remarkUnwrapImages", _var_is_local=False, _var_is_string=False
)
_REMARK_PLUGINS = Var.create_safe([_REMARK_MATH, _REMARK_GFM, _REMARK_UNWRAP_IMAGES])
_REMARK_MATH = ImmutableVar.create_safe("remarkMath")
_REMARK_GFM = ImmutableVar.create_safe("remarkGfm")
_REMARK_UNWRAP_IMAGES = ImmutableVar.create_safe("remarkUnwrapImages")
_REMARK_PLUGINS = LiteralVar.create([_REMARK_MATH, _REMARK_GFM, _REMARK_UNWRAP_IMAGES])
# Special rehype plugins.
_REHYPE_KATEX = Var.create_safe(
"rehypeKatex", _var_is_local=False, _var_is_string=False
)
_REHYPE_RAW = Var.create_safe("rehypeRaw", _var_is_local=False, _var_is_string=False)
_REHYPE_PLUGINS = Var.create_safe([_REHYPE_KATEX, _REHYPE_RAW])
_REHYPE_KATEX = ImmutableVar.create_safe("rehypeKatex")
_REHYPE_RAW = ImmutableVar.create_safe("rehypeRaw")
_REHYPE_PLUGINS = LiteralVar.create([_REHYPE_KATEX, _REHYPE_RAW])
# These tags do NOT get props passed to them
NO_PROPS_TAGS = ("ul", "ol", "li")
@ -209,10 +205,11 @@ class Markdown(Component):
children_prop = props.pop("children", None)
if children_prop is not None:
special_props.add(
Var.create_safe(f"children={str(children_prop)}", _var_is_string=False)
Var.create_safe(
f"children={{{str(children_prop)}}}", _var_is_string=False
)
)
children = []
# Get the component.
component = self.component_map[tag](*children, **props).set(
special_props=special_props
@ -238,7 +235,7 @@ class Markdown(Component):
The formatted component map.
"""
components = {
tag: f"{{({{node, {_CHILDREN._var_name}, {_PROPS._var_name}}}) => {self.format_component(tag)}}}"
tag: f"{{({{node, {_CHILDREN._var_name}, {_PROPS._var_name}}}) => ({self.format_component(tag)})}}"
for tag in self.component_map
}
@ -261,7 +258,7 @@ class Markdown(Component):
return inline ? (
{self.format_component("code")}
) : (
{self.format_component("codeblock", language=Var.create_safe("language", _var_is_local=False, _var_is_string=False))}
{self.format_component("codeblock", language=ImmutableVar.create_safe("language"))}
);
}}}}""".replace("\n", " ")
@ -288,7 +285,7 @@ class Markdown(Component):
function {self._get_component_map_name()} () {{
{formatted_hooks}
return (
{str(LiteralVar.create(self.format_component_map()))}
{str(ImmutableVar.create_safe(self.format_component_map()))}
)
}}
"""
@ -300,14 +297,10 @@ class Markdown(Component):
.add_props(
remark_plugins=_REMARK_PLUGINS,
rehype_plugins=_REHYPE_PLUGINS,
components=ImmutableVar.create_safe(
f"{self._get_component_map_name()}()"
),
)
.remove_props("componentMap", "componentMapHash")
)
tag.special_props.add(
Var.create_safe(
f"components={{{self._get_component_map_name()}()}}",
_var_is_local=True,
_var_is_string=False,
),
)
return tag

View File

@ -8,24 +8,21 @@ from typing import Any, Callable, Dict, Optional, Union, overload
from reflex.components.component import Component
from reflex.event import EventHandler, EventSpec
from reflex.ivars.base import ImmutableVar, LiteralVar
from reflex.style import Style
from reflex.utils.imports import ImportDict
from reflex.vars import BaseVar, Var
_CHILDREN = Var.create_safe("children", _var_is_local=False, _var_is_string=False)
_PROPS = Var.create_safe("...props", _var_is_local=False, _var_is_string=False)
_MOCK_ARG = Var.create_safe("", _var_is_string=False)
_REMARK_MATH = Var.create_safe("remarkMath", _var_is_local=False, _var_is_string=False)
_REMARK_GFM = Var.create_safe("remarkGfm", _var_is_local=False, _var_is_string=False)
_REMARK_UNWRAP_IMAGES = Var.create_safe(
"remarkUnwrapImages", _var_is_local=False, _var_is_string=False
)
_REMARK_PLUGINS = Var.create_safe([_REMARK_MATH, _REMARK_GFM, _REMARK_UNWRAP_IMAGES])
_REHYPE_KATEX = Var.create_safe(
"rehypeKatex", _var_is_local=False, _var_is_string=False
)
_REHYPE_RAW = Var.create_safe("rehypeRaw", _var_is_local=False, _var_is_string=False)
_REHYPE_PLUGINS = Var.create_safe([_REHYPE_KATEX, _REHYPE_RAW])
_CHILDREN = ImmutableVar.create_safe("children")
_PROPS = ImmutableVar.create_safe("...props")
_MOCK_ARG = ImmutableVar.create_safe("")
_REMARK_MATH = ImmutableVar.create_safe("remarkMath")
_REMARK_GFM = ImmutableVar.create_safe("remarkGfm")
_REMARK_UNWRAP_IMAGES = ImmutableVar.create_safe("remarkUnwrapImages")
_REMARK_PLUGINS = LiteralVar.create([_REMARK_MATH, _REMARK_GFM, _REMARK_UNWRAP_IMAGES])
_REHYPE_KATEX = ImmutableVar.create_safe("rehypeKatex")
_REHYPE_RAW = ImmutableVar.create_safe("rehypeRaw")
_REHYPE_PLUGINS = LiteralVar.create([_REHYPE_KATEX, _REHYPE_RAW])
NO_PROPS_TAGS = ("ul", "ol", "li")
@lru_cache

View File

@ -206,5 +206,5 @@ class ColorModeNamespace(ImmutableVar):
color_mode = color_mode_var_and_namespace = ColorModeNamespace(
_var_name=color_mode._var_name,
_var_type=color_mode._var_type,
_var_data=color_mode._var_data,
_var_data=color_mode.get_default_value(),
)

View File

@ -3,7 +3,6 @@
# ------------------- DO NOT EDIT ----------------------
# This file was generated by `reflex/utils/pyi_generator.py`!
# ------------------------------------------------------
import dataclasses
from typing import Any, Callable, Dict, Literal, Optional, Union, get_args, overload
from reflex.components.component import BaseComponent
@ -12,6 +11,7 @@ from reflex.components.core.cond import Cond
from reflex.components.lucide.icon import Icon
from reflex.components.radix.themes.components.switch import Switch
from reflex.event import EventHandler, EventSpec
from reflex.ivars.base import ImmutableVar
from reflex.style import (
Style,
color_mode,
@ -533,11 +533,13 @@ class ColorModeSwitch(Switch):
"""
...
class ColorModeNamespace(BaseVar):
class ColorModeNamespace(ImmutableVar):
icon = staticmethod(ColorModeIcon.create)
button = staticmethod(ColorModeIconButton.create)
switch = staticmethod(ColorModeSwitch.create)
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.get_default_value(),
)

View File

@ -7,6 +7,7 @@ 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 LiteralVar
from reflex.vars import Var
from ..base import (
@ -115,9 +116,7 @@ class HighLevelCheckbox(RadixThemesComponent):
on_change: EventHandler[lambda e0: [e0]]
@classmethod
def create(
cls, text: Var[str] = Var.create_safe("", _var_is_string=True), **props
) -> Component:
def create(cls, text: Var[str] = LiteralVar.create(""), **props) -> Component:
"""Create a checkbox with a label.
Args:

View File

@ -11,7 +11,6 @@ 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.ivars.sequence import StringVar
from reflex.vars import Var
@ -30,14 +29,10 @@ class RadioGroupRoot(RadixThemesComponent):
tag = "RadioGroup.Root"
# The size of the radio group: "1" | "2" | "3"
size: Var[Responsive[Literal["1", "2", "3"]]] = Var.create_safe(
"2", _var_is_string=True
)
size: Var[Responsive[Literal["1", "2", "3"]]] = LiteralVar.create("2")
# The variant of the radio group
variant: Var[Literal["classic", "surface", "soft"]] = Var.create_safe(
"classic", _var_is_string=True
)
variant: Var[Literal["classic", "surface", "soft"]] = LiteralVar.create("classic")
# The color of the radio group
color_scheme: Var[LiteralAccentColor]
@ -89,20 +84,16 @@ class HighLevelRadioGroup(RadixThemesComponent):
items: Var[List[str]]
# The direction of the radio group.
direction: Var[LiteralFlexDirection] = Var.create_safe(
"column", _var_is_string=True
)
direction: Var[LiteralFlexDirection] = LiteralVar.create("column")
# The gap between the items of the radio group.
spacing: Var[LiteralSpacing] = Var.create_safe("2", _var_is_string=True)
spacing: Var[LiteralSpacing] = LiteralVar.create("2")
# The size of the radio group.
size: Var[Literal["1", "2", "3"]] = Var.create_safe("2", _var_is_string=True)
size: Var[Literal["1", "2", "3"]] = LiteralVar.create("2")
# The variant of the radio group
variant: Var[Literal["classic", "surface", "soft"]] = Var.create_safe(
"classic", _var_is_string=True
)
variant: Var[Literal["classic", "surface", "soft"]] = LiteralVar.create("classic")
# The color of the radio group
color_scheme: Var[LiteralAccentColor]
@ -159,13 +150,13 @@ class HighLevelRadioGroup(RadixThemesComponent):
):
default_value = LiteralVar.create(default_value) # type: ignore
else:
default_value = JSON_STRINGIFY.call(ImmutableVar.create(default_value))
default_value = ImmutableVar.create_safe(default_value).to_string()
def radio_group_item(value: Var) -> Component:
item_value = rx.cond(
value._type() == "string",
value,
JSON_STRINGIFY.call(value),
value.to_string(),
).to(StringVar)
return Text.create(

View File

@ -3,6 +3,7 @@
from typing import Literal
from reflex.components.core.breakpoints import Responsive
from reflex.ivars.base import LiteralVar
from reflex.vars import Var
from ..base import (
@ -19,9 +20,7 @@ class Separator(RadixThemesComponent):
tag = "Separator"
# The size of the select: "1" | "2" | "3" | "4"
size: Var[Responsive[LiteralSeperatorSize]] = Var.create_safe(
"4", _var_is_string=True
)
size: Var[Responsive[LiteralSeperatorSize]] = LiteralVar.create("4")
# The color of the select
color_scheme: Var[LiteralAccentColor]

View File

@ -6,6 +6,7 @@ from typing import Literal
from reflex.components.core.breakpoints import Responsive
from reflex.components.el import elements
from reflex.ivars.base import LiteralVar
from reflex.style import STACK_CHILDREN_FULL_WIDTH
from reflex.vars import Var
@ -23,9 +24,7 @@ class Container(elements.Div, RadixThemesComponent):
tag = "Container"
# The size of the container: "1" - "4" (default "3")
size: Var[Responsive[LiteralContainerSize]] = Var.create_safe(
"3", _var_is_string=True
)
size: Var[Responsive[LiteralContainerSize]] = LiteralVar.create("3")
@classmethod
def create(

View File

@ -6,6 +6,7 @@ from typing import Literal
from reflex.components.core.breakpoints import Responsive
from reflex.components.el import elements
from reflex.ivars.base import LiteralVar
from reflex.vars import Var
from ..base import RadixThemesComponent
@ -19,9 +20,7 @@ class Section(elements.Section, RadixThemesComponent):
tag = "Section"
# The size of the section: "1" - "3" (default "2")
size: Var[Responsive[LiteralSectionSize]] = Var.create_safe(
"2", _var_is_string=True
)
size: Var[Responsive[LiteralSectionSize]] = LiteralVar.create("2")
section = Section.create

View File

@ -7,6 +7,7 @@ from typing import Any, Dict, List, Union
from reflex.constants import EventTriggers
from reflex.constants.colors import Color
from reflex.event import EventHandler
from reflex.ivars.base import LiteralVar
from reflex.vars import Var
from .recharts import (
@ -86,7 +87,7 @@ class Axis(Recharts):
tick_count: Var[int]
# If set false, no axis tick lines will be drawn.
tick_line: Var[bool] = Var.create_safe(False)
tick_line: Var[bool] = LiteralVar.create(False)
# The length of tick line.
tick_size: Var[int]
@ -95,7 +96,7 @@ class Axis(Recharts):
min_tick_gap: Var[int]
# The stroke color of axis
stroke: Var[Union[str, Color]] = Var.create_safe(Color("gray", 9))
stroke: Var[Union[str, Color]] = LiteralVar.create(Color("gray", 9))
# The text anchor of axis
text_anchor: Var[str] # 'start', 'middle', 'end'
@ -136,7 +137,7 @@ class XAxis(Axis):
x_axis_id: Var[Union[str, int]]
# Ensures that all datapoints within a chart contribute to its domain calculation, even when they are hidden
include_hidden: Var[bool] = Var.create_safe(False)
include_hidden: Var[bool] = LiteralVar.create(False)
class YAxis(Axis):
@ -187,10 +188,10 @@ class Brush(Recharts):
alias = "RechartsBrush"
# Stroke color
stroke: Var[Union[str, Color]] = Var.create_safe(Color("gray", 9))
stroke: Var[Union[str, Color]] = LiteralVar.create(Color("gray", 9))
# The fill color of brush.
fill: Var[Union[str, Color]] = Var.create_safe(Color("gray", 2))
fill: Var[Union[str, Color]] = LiteralVar.create(Color("gray", 2))
# The key of data displayed in the axis.
data_key: Var[Union[str, int]]
@ -290,22 +291,22 @@ class Area(Cartesian):
alias = "RechartsArea"
# The color of the line stroke.
stroke: Var[Union[str, Color]] = Var.create_safe(Color("accent", 9))
stroke: Var[Union[str, Color]] = LiteralVar.create(Color("accent", 9))
# The width of the line stroke.
stroke_width: Var[int] = Var.create_safe(1)
stroke_width: Var[int] = LiteralVar.create(1)
# The color of the area fill.
fill: Var[Union[str, Color]] = Var.create_safe(Color("accent", 5))
fill: Var[Union[str, Color]] = LiteralVar.create(Color("accent", 5))
# The interpolation type of area. And customized interpolation function can be set to type. 'basis' | 'basisClosed' | 'basisOpen' | 'bumpX' | 'bumpY' | 'bump' | 'linear' | 'linearClosed' | 'natural' | 'monotoneX' | 'monotoneY' | 'monotone' | 'step' | 'stepBefore' | 'stepAfter' |
type_: Var[LiteralAreaType] = Var.create_safe("monotone", _var_is_string=True)
type_: Var[LiteralAreaType] = LiteralVar.create("monotone")
# If false set, dots will not be drawn. If true set, dots will be drawn which have the props calculated internally.
dot: Var[Union[bool, Dict[str, Any]]]
# The dot is shown when user enter an area chart and this chart has tooltip. If false set, no active dot will not be drawn. If true set, active dot will be drawn which have the props calculated internally.
active_dot: Var[Union[bool, Dict[str, Any]]] = Var.create_safe(
active_dot: Var[Union[bool, Dict[str, Any]]] = LiteralVar.create(
{
"stroke": Color("accent", 2),
"fill": Color("accent", 10),
@ -342,7 +343,7 @@ class Bar(Cartesian):
stroke_width: Var[int]
# The width of the line stroke.
fill: Var[Union[str, Color]] = Var.create_safe(Color("accent", 9))
fill: Var[Union[str, Color]] = LiteralVar.create(Color("accent", 9))
# If false set, background of bars will not be drawn. If true set, background of bars will be drawn which have the props calculated internally.
background: Var[bool]
@ -403,13 +404,13 @@ class Line(Cartesian):
type_: Var[LiteralAreaType]
# The color of the line stroke.
stroke: Var[Union[str, Color]] = Var.create_safe(Color("accent", 9))
stroke: Var[Union[str, Color]] = LiteralVar.create(Color("accent", 9))
# The width of the line stroke.
stroke_width: Var[int]
# The dot is shown when mouse enter a line chart and this chart has tooltip. If false set, no active dot will not be drawn. If true set, active dot will be drawn which have the props calculated internally.
dot: Var[Union[bool, Dict[str, Any]]] = Var.create_safe(
dot: Var[Union[bool, Dict[str, Any]]] = LiteralVar.create(
{
"stroke": Color("accent", 10),
"fill": Color("accent", 4),
@ -417,7 +418,7 @@ class Line(Cartesian):
)
# The dot is shown when user enter an area chart and this chart has tooltip. If false set, no active dot will not be drawn. If true set, active dot will be drawn which have the props calculated internally.
active_dot: Var[Union[bool, Dict[str, Any]]] = Var.create_safe(
active_dot: Var[Union[bool, Dict[str, Any]]] = LiteralVar.create(
{
"stroke": Color("accent", 2),
"fill": Color("accent", 10),
@ -475,7 +476,7 @@ class Scatter(Recharts):
line_type: Var[LiteralLineType]
# The fill
fill: Var[Union[str, Color]] = Var.create_safe(Color("accent", 9))
fill: Var[Union[str, Color]] = LiteralVar.create(Color("accent", 9))
# the name
name: Var[Union[str, int]]
@ -552,7 +553,7 @@ class Funnel(Recharts):
animation_easing: Var[LiteralAnimationEasing]
# stroke color
stroke: Var[Union[str, Color]] = Var.create_safe(Color("gray", 3))
stroke: Var[Union[str, Color]] = LiteralVar.create(Color("gray", 3))
# Valid children components
_valid_children: List[str] = ["LabelList", "Cell"]
@ -605,7 +606,7 @@ class ErrorBar(Recharts):
width: Var[int]
# The stroke color of error bar.
stroke: Var[Union[str, Color]] = Var.create_safe(Color("gray", 8))
stroke: Var[Union[str, Color]] = LiteralVar.create(Color("gray", 8))
# The stroke width of error bar.
stroke_width: Var[int]
@ -795,7 +796,7 @@ class CartesianGrid(Grid):
stroke_dasharray: Var[str]
# the stroke color of grid
stroke: Var[Union[str, Color]] = Var.create_safe(Color("gray", 7))
stroke: Var[Union[str, Color]] = LiteralVar.create(Color("gray", 7))
class CartesianAxis(Grid):

View File

@ -9,6 +9,7 @@ from reflex.components.recharts.general import ResponsiveContainer
from reflex.constants import EventTriggers
from reflex.constants.colors import Color
from reflex.event import EventHandler
from reflex.ivars.base import LiteralVar
from reflex.vars import Var
from .recharts import (
@ -156,10 +157,10 @@ class BarChart(CategoricalChartBase):
alias = "RechartsBarChart"
# The gap between two bar categories, which can be a percent value or a fixed value. Percentage | Number
bar_category_gap: Var[Union[str, int]] = Var.create_safe("10%", _var_is_string=True) # type: ignore
bar_category_gap: Var[Union[str, int]] = LiteralVar.create("10%")
# The gap between two bars in the same category, which can be a percent value or a fixed value. Percentage | Number
bar_gap: Var[Union[str, int]] = Var.create_safe(4) # type: ignore
bar_gap: Var[Union[str, int]] = LiteralVar.create(4) # type: ignore
# The width of all the bars in the chart. Number
bar_size: Var[int]

View File

@ -7,6 +7,7 @@ from typing import Any, Dict, List, Union
from reflex.components.component import MemoizationLeaf
from reflex.constants.colors import Color
from reflex.event import EventHandler
from reflex.ivars.base import LiteralVar
from reflex.vars import Var
from .recharts import (
@ -139,7 +140,7 @@ class GraphingTooltip(Recharts):
filter_null: Var[bool]
# If set false, no cursor will be drawn when tooltip is active.
cursor: Var[Union[Dict[str, Any], bool]] = Var.create_safe(
cursor: Var[Union[Dict[str, Any], bool]] = LiteralVar.create(
{
"strokeWidth": 1,
"fill": Color("gray", 3),
@ -150,7 +151,7 @@ class GraphingTooltip(Recharts):
view_box: Var[Dict[str, Any]]
# The style of default tooltip content item which is a li element. DEFAULT: {}
item_style: Var[Dict[str, Any]] = Var.create_safe(
item_style: Var[Dict[str, Any]] = LiteralVar.create(
{
"color": Color("gray", 12),
}
@ -159,7 +160,7 @@ class GraphingTooltip(Recharts):
# The style of tooltip wrapper which is a dom element. DEFAULT: {}
wrapper_style: Var[Dict[str, Any]]
# The style of tooltip content which is a dom element. DEFAULT: {}
content_style: Var[Dict[str, Any]] = Var.create_safe(
content_style: Var[Dict[str, Any]] = LiteralVar.create(
{
"background": Color("gray", 1),
"borderColor": Color("gray", 4),
@ -168,10 +169,10 @@ class GraphingTooltip(Recharts):
)
# The style of default tooltip label which is a p element. DEFAULT: {}
label_style: Var[Dict[str, Any]] = Var.create_safe({"color": Color("gray", 11)})
label_style: Var[Dict[str, Any]] = LiteralVar.create({"color": Color("gray", 11)})
# This option allows the tooltip to extend beyond the viewBox of the chart itself. DEFAULT: { x: false, y: false }
allow_escape_view_box: Var[Dict[str, bool]] = Var.create_safe(
allow_escape_view_box: Var[Dict[str, bool]] = LiteralVar.create(
{"x": False, "y": False}
)
@ -231,10 +232,10 @@ class LabelList(Recharts):
offset: Var[int]
# The fill color of each label
fill: Var[Union[str, Color]] = Var.create_safe(Color("gray", 10))
fill: Var[Union[str, Color]] = LiteralVar.create(Color("gray", 10))
# The stroke color of each label
stroke: Var[Union[str, Color]] = Var.create_safe("none", _var_is_string=True)
stroke: Var[Union[str, Color]] = LiteralVar.create("none")
responsive_container = ResponsiveContainer.create

View File

@ -7,6 +7,7 @@ from typing import Any, Dict, List, Union
from reflex.constants import EventTriggers
from reflex.constants.colors import Color
from reflex.event import EventHandler
from reflex.ivars.base import LiteralVar
from reflex.vars import Var
from .recharts import (
@ -72,10 +73,10 @@ class Pie(Recharts):
_valid_children: List[str] = ["Cell", "LabelList"]
# Stoke color
stroke: Var[Union[str, Color]] = Var.create_safe(Color("accent", 9))
stroke: Var[Union[str, Color]] = LiteralVar.create(Color("accent", 9))
# Fill color
fill: Var[Union[str, Color]] = Var.create_safe(Color("accent", 3))
fill: Var[Union[str, Color]] = LiteralVar.create(Color("accent", 3))
def get_event_triggers(self) -> dict[str, Union[Var, Any]]:
"""Get the event triggers that pass the component's value to the handler.
@ -110,13 +111,13 @@ class Radar(Recharts):
dot: Var[bool]
# Stoke color
stroke: Var[Union[str, Color]] = Var.create_safe(Color("accent", 9))
stroke: Var[Union[str, Color]] = LiteralVar.create(Color("accent", 9))
# Fill color
fill: Var[str] = Var.create_safe(Color("accent", 3))
fill: Var[str] = LiteralVar.create(Color("accent", 3))
# opacity
fill_opacity: Var[float] = Var.create_safe(0.6)
fill_opacity: Var[float] = LiteralVar.create(0.6)
# The type of icon in legend. If set to 'none', no legend item will be rendered.
legend_type: Var[str]
@ -218,7 +219,7 @@ class PolarAngleAxis(Recharts):
axis_line_type: Var[str]
# If false set, tick lines will not be drawn. If true set, tick lines will be drawn which have the props calculated internally. If object set, tick lines will be drawn which have the props mergered by the internal calculated props and the option.
tick_line: Var[Union[bool, Dict[str, Any]]] = Var.create_safe(False)
tick_line: Var[Union[bool, Dict[str, Any]]] = LiteralVar.create(False)
# The width or height of tick.
tick: Var[Union[int, str]]
@ -230,7 +231,7 @@ class PolarAngleAxis(Recharts):
orient: Var[str]
# The stroke color of axis
stroke: Var[Union[str, Color]] = Var.create_safe(Color("gray", 10))
stroke: Var[Union[str, Color]] = LiteralVar.create(Color("gray", 10))
# Allow the axis has duplicated categorys or not when the type of axis is "category".
allow_duplicated_category: Var[bool]
@ -292,7 +293,7 @@ class PolarGrid(Recharts):
grid_type: Var[LiteralGridType]
# The stroke color of grid
stroke: Var[Union[str, Color]] = Var.create_safe(Color("gray", 10))
stroke: Var[Union[str, Color]] = LiteralVar.create(Color("gray", 10))
# Valid children components
_valid_children: List[str] = ["RadarChart", "RadiarBarChart"]
@ -342,10 +343,10 @@ class PolarRadiusAxis(Recharts):
_valid_children: List[str] = ["Label"]
# The domain of the polar radius axis, specifying the minimum and maximum values.
domain: Var[List[int]] = Var.create_safe([0, 250])
domain: Var[List[int]] = LiteralVar.create([0, 250])
# The stroke color of axis
stroke: Var[Union[str, Color]] = Var.create_safe(Color("gray", 10))
stroke: Var[Union[str, Color]] = LiteralVar.create(Color("gray", 10))
def get_event_triggers(self) -> dict[str, Union[Var, Any]]:
"""Get the event triggers that pass the component's value to the handler.

View File

@ -12,6 +12,7 @@ from reflex.event import (
EventSpec,
call_script,
)
from reflex.ivars.base import LiteralVar
from reflex.style import Style, resolved_color_mode
from reflex.utils import format
from reflex.utils.imports import ImportVar
@ -171,21 +172,19 @@ class Toaster(Component):
theme: Var[str] = resolved_color_mode
# whether to show rich colors
rich_colors: Var[bool] = Var.create_safe(True)
rich_colors: Var[bool] = LiteralVar.create(True)
# whether to expand the toast
expand: Var[bool] = Var.create_safe(True)
expand: Var[bool] = LiteralVar.create(True)
# the number of toasts that are currently visible
visible_toasts: Var[int]
# the position of the toast
position: Var[LiteralPosition] = Var.create_safe(
"bottom-right", _var_is_string=True
)
position: Var[LiteralPosition] = LiteralVar.create("bottom-right")
# whether to show the close button
close_button: Var[bool] = Var.create_safe(False)
close_button: Var[bool] = LiteralVar.create(False)
# offset of the toast
offset: Var[str]
@ -330,7 +329,7 @@ class Toaster(Component):
if isinstance(id, Var):
dismiss = f"{toast_ref}.dismiss({id._var_name_unwrapped})"
dismiss_var_data = id._var_data
dismiss_var_data = id._get_all_var_data()
elif isinstance(id, str):
dismiss = f"{toast_ref}.dismiss('{id}')"
else:
@ -339,7 +338,7 @@ class Toaster(Component):
dismiss,
_var_is_string=False,
_var_is_local=True,
_var_data=dismiss_var_data,
_var_data=VarData.merge(dismiss_var_data),
)
return call_script(dismiss_action)

View File

@ -714,15 +714,11 @@ def download(
url = "data:text/plain," + urllib.parse.quote(data)
elif isinstance(data, Var):
# Need to check on the frontend if the Var already looks like a data: URI.
is_data_url = data._replace(
_var_name=(
f"typeof {data._var_full_name} == 'string' && "
f"{data._var_full_name}.startsWith('data:')"
),
_var_type=bool,
_var_is_string=False,
_var_full_name_needs_state_prefix=False,
is_data_url = (data._type() == "string") & (
data.to(str).startswith("data:")
)
# If it's a data: URI, use it as is, otherwise convert the Var to JSON in a data: URI.
url = cond( # type: ignore
is_data_url,

View File

@ -2,10 +2,15 @@
from __future__ import annotations
import contextlib
import dataclasses
import datetime
import dis
import functools
import inspect
import json
import sys
from types import CodeType, FunctionType
from typing import (
TYPE_CHECKING,
Any,
@ -20,17 +25,22 @@ from typing import (
Type,
TypeVar,
Union,
cast,
get_args,
overload,
override,
)
from typing_extensions import ParamSpec, get_origin
from typing_extensions import ParamSpec, get_origin, get_type_hints
from reflex import constants
from reflex.base import Base
from reflex.constants.colors import Color
from reflex.utils import console, imports, serializers, types
from reflex.utils.exceptions import VarTypeError
from reflex.utils.exceptions import VarDependencyError, VarTypeError, VarValueError
from reflex.utils.format import format_state_name
from reflex.vars import (
ComputedVar,
ImmutableVarData,
Var,
VarData,
@ -320,11 +330,15 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
@overload
def to(
self, output: Type[OUTPUT], var_type: types.GenericType | None = None
self,
output: Type[OUTPUT] | types.GenericType,
var_type: types.GenericType | None = None,
) -> OUTPUT: ...
def to(
self, output: Type[OUTPUT], var_type: types.GenericType | None = None
self,
output: Type[OUTPUT] | types.GenericType,
var_type: types.GenericType | None = None,
) -> Var:
"""Convert the var to a different type.
@ -338,12 +352,15 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns:
The converted var.
"""
from .function import FunctionVar, ToFunctionOperation
from .number import (
BooleanVar,
NumberVar,
ToBooleanVarOperation,
ToNumberVarOperation,
)
from .object import ObjectVar, ToObjectOperation
from .sequence import ArrayVar, StringVar, ToArrayOperation, ToStringOperation
fixed_type = (
var_type
@ -351,16 +368,28 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
else get_origin(var_type)
)
fixed_output_type = output if inspect.isclass(output) else get_origin(output)
if fixed_output_type is dict:
return self.to(ObjectVar, output)
if fixed_output_type in (list, tuple, set):
return self.to(ArrayVar, output)
if fixed_output_type in (int, float):
return self.to(NumberVar, output)
if fixed_output_type is str:
return self.to(StringVar, output)
if fixed_output_type is bool:
return self.to(BooleanVar, output)
if issubclass(output, NumberVar):
if fixed_type is not None and not issubclass(fixed_type, (int, float)):
raise TypeError(
f"Unsupported type {var_type} for NumberVar. Must be int or float."
)
return ToNumberVarOperation(self, var_type or float)
if issubclass(output, BooleanVar):
return ToBooleanVarOperation(self)
return ToNumberVarOperation.create(self, var_type or float)
from .sequence import ArrayVar, StringVar, ToArrayOperation, ToStringOperation
if issubclass(output, BooleanVar):
return ToBooleanVarOperation.create(self)
if issubclass(output, ArrayVar):
if fixed_type is not None and not issubclass(
@ -369,28 +398,30 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
raise TypeError(
f"Unsupported type {var_type} for ArrayVar. Must be list, tuple, or set."
)
return ToArrayOperation(self, var_type or list)
return ToArrayOperation.create(self, var_type or list)
if issubclass(output, StringVar):
return ToStringOperation(self)
return ToStringOperation.create(self)
from .object import ObjectVar, ToObjectOperation
if issubclass(output, ObjectVar):
return ToObjectOperation(self, var_type or dict)
from .function import FunctionVar, ToFunctionOperation
if issubclass(output, (ObjectVar, Base)):
return ToObjectOperation.create(self, var_type or dict)
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."
# )
return ToFunctionOperation(self, var_type or Callable)
return ToFunctionOperation.create(self, var_type or Callable)
return output(
_var_name=self._var_name,
_var_type=self._var_type if var_type is None else var_type,
_var_data=self._var_data,
if not issubclass(output, Var) and var_type is None:
return dataclasses.replace(
self,
_var_type=output,
)
return dataclasses.replace(
self,
_var_type=var_type,
)
def guess_type(self) -> ImmutableVar:
@ -413,6 +444,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
if fixed_type is Union:
return self
if not inspect.isclass(fixed_type):
raise TypeError(f"Unsupported type {var_type} for guess_type.")
if issubclass(fixed_type, (int, float)):
return self.to(NumberVar, var_type)
if issubclass(fixed_type, dict):
@ -477,12 +511,12 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns:
The name of the setter function.
"""
setter = constants.SETTER_PREFIX + self._var_name
var_name_parts = self._var_name.split(".")
setter = constants.SETTER_PREFIX + var_name_parts[-1]
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]:
@ -491,6 +525,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns:
A function that that creates a setter for the var.
"""
actual_name = self._var_name.split(".")[-1]
def setter(state: BaseState, value: Any):
"""Get the setter for the var.
@ -502,13 +537,13 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
if self._var_type in [int, float]:
try:
value = self._var_type(value)
setattr(state, self._var_name, value)
setattr(state, actual_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)
setattr(state, actual_name, value)
setter.__qualname__ = self.get_setter_name()
@ -525,7 +560,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
"""
from .number import EqualOperation
return EqualOperation(self, other)
return EqualOperation.create(self, other)
def __ne__(self, other: Var | Any) -> BooleanVar:
"""Check if the current object is not equal to the given object.
@ -538,7 +573,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
"""
from .number import EqualOperation
return ~EqualOperation(self, other)
return ~EqualOperation.create(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.
@ -551,7 +586,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
"""
from .number import GreaterThanOperation
return GreaterThanOperation(self, other)
return GreaterThanOperation.create(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.
@ -564,7 +599,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
"""
from .number import GreaterThanOrEqualOperation
return GreaterThanOrEqualOperation(self, other)
return GreaterThanOrEqualOperation.create(self, other)
def __lt__(self, other: Var | Any) -> BooleanVar:
"""Compare the current instance with another variable using the less than (<) operator.
@ -577,7 +612,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
"""
from .number import LessThanOperation
return LessThanOperation(self, other)
return LessThanOperation.create(self, other)
def __le__(self, other: Var | Any) -> BooleanVar:
"""Compare if the current instance is less than or equal to the given value.
@ -590,7 +625,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
"""
from .number import LessThanOrEqualOperation
return LessThanOrEqualOperation(self, other)
return LessThanOrEqualOperation.create(self, other)
def bool(self) -> BooleanVar:
"""Convert the var to a boolean.
@ -600,7 +635,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
"""
from .number import ToBooleanVarOperation
return ToBooleanVarOperation(self)
return ToBooleanVarOperation.create(self)
def __and__(self, other: Var | Any) -> ImmutableVar:
"""Perform a logical AND operation on the current instance and another variable.
@ -611,7 +646,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns:
A `BooleanVar` object representing the result of the logical AND operation.
"""
return AndOperation(self, other)
return AndOperation.create(self, other)
def __rand__(self, other: Var | Any) -> ImmutableVar:
"""Perform a logical AND operation on the current instance and another variable.
@ -622,7 +657,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns:
A `BooleanVar` object representing the result of the logical AND operation.
"""
return AndOperation(other, self)
return AndOperation.create(other, self)
def __or__(self, other: Var | Any) -> ImmutableVar:
"""Perform a logical OR operation on the current instance and another variable.
@ -633,7 +668,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns:
A `BooleanVar` object representing the result of the logical OR operation.
"""
return OrOperation(self, other)
return OrOperation.create(self, other)
def __ror__(self, other: Var | Any) -> ImmutableVar:
"""Perform a logical OR operation on the current instance and another variable.
@ -644,7 +679,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns:
A `BooleanVar` object representing the result of the logical OR operation.
"""
return OrOperation(other, self)
return OrOperation.create(other, self)
def __invert__(self) -> BooleanVar:
"""Perform a logical NOT operation on the current instance.
@ -654,7 +689,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
"""
from .number import BooleanNotOperation
return BooleanNotOperation(self.bool())
return BooleanNotOperation.create(self.bool())
def to_string(self) -> ImmutableVar:
"""Convert the var to a string.
@ -663,8 +698,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
The string var.
"""
from .function import JSON_STRINGIFY
from .sequence import StringVar
return JSON_STRINGIFY.call(self)
return JSON_STRINGIFY.call(self).to(StringVar)
def as_ref(self) -> ImmutableVar:
"""Get a reference to the var.
@ -732,35 +768,97 @@ class LiteralVar(ImmutableVar):
if value is None:
return ImmutableVar.create_safe("null", _var_data=_var_data)
from reflex.event import EventChain, EventSpec
from reflex.utils.format import get_event_handler_parts
from .function import ArgsFunctionOperation, FunctionStringVar
from .object import LiteralObjectVar
if isinstance(value, EventSpec):
event_name = LiteralVar.create(
".".join(get_event_handler_parts(value.handler))
)
event_args = LiteralVar.create({name: value for name, value in value.args})
event_client_name = LiteralVar.create(value.client_handler_name)
return FunctionStringVar("Event").call(
event_name, event_args, event_client_name
)
if isinstance(value, EventChain):
sig = inspect.signature(value.args_spec) # type: ignore
if sig.parameters:
arg_def = tuple((f"_{p}" for p in sig.parameters))
arg_def_expr = LiteralVar.create(
[ImmutableVar.create_safe(arg) for arg in arg_def]
)
else:
# add a default argument for addEvents if none were specified in value.args_spec
# used to trigger the preventDefault() on the event.
arg_def = ("...args",)
arg_def_expr = ImmutableVar.create_safe("args")
return ArgsFunctionOperation.create(
arg_def,
FunctionStringVar.create("addEvents").call(
LiteralVar.create(
[LiteralVar.create(event) for event in value.events]
),
arg_def_expr,
LiteralVar.create(value.event_actions),
),
)
from plotly.graph_objects import Figure, layout
from plotly.io import to_json
if isinstance(value, Figure):
return LiteralObjectVar.create(
json.loads(to_json(value)), _var_type=Figure, _var_data=_var_data
)
if isinstance(value, layout.Template):
return LiteralObjectVar.create(
{
"data": json.loads(to_json(value.data)),
"layout": json.loads(to_json(value.layout)),
},
_var_type=layout.Template,
_var_data=_var_data,
)
if isinstance(value, Base):
return LiteralObjectVar(
return LiteralObjectVar.create(
value.dict(), _var_type=type(value), _var_data=_var_data
)
if isinstance(value, dict):
return LiteralObjectVar(value, _var_data=_var_data)
return LiteralObjectVar.create(value, _var_data=_var_data)
from .number import LiteralBooleanVar, LiteralNumberVar
from .sequence import LiteralArrayVar, LiteralStringVar
if isinstance(value, str):
return LiteralStringVar.create(value, _var_data=_var_data)
if isinstance(value, Color):
return LiteralStringVar.create(f"{value}", _var_data=_var_data)
from .number import LiteralBooleanVar, LiteralNumberVar
type_mapping = {
int: LiteralNumberVar,
float: LiteralNumberVar,
bool: LiteralBooleanVar,
list: LiteralArrayVar,
tuple: LiteralArrayVar,
set: LiteralArrayVar,
int: LiteralNumberVar.create,
float: LiteralNumberVar.create,
bool: LiteralBooleanVar.create,
list: LiteralArrayVar.create,
tuple: LiteralArrayVar.create,
set: LiteralArrayVar.create,
}
constructor = type_mapping.get(type(value))
if constructor is None:
raise TypeError(f"Unsupported type {type(value)} for LiteralVar.")
raise TypeError(
f"Unsupported type {type(value)} for LiteralVar. Tried to create a LiteralVar from {value}."
)
return constructor(value, _var_data=_var_data)
@ -881,27 +979,8 @@ class AndOperation(ImmutableVar):
# 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(AndOperation, 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)
)
def __post_init__(self):
"""Post-initialize the AndOperation."""
object.__delattr__(self, "_var_name")
@functools.cached_property
@ -955,6 +1034,29 @@ class AndOperation(ImmutableVar):
"""
return hash((self.__class__.__name__, self._var1, self._var2))
@classmethod
def create(
cls, var1: Var | Any, var2: Var | Any, _var_data: VarData | None = None
) -> AndOperation:
"""Create an AndOperation.
Args:
var1: The first var.
var2: The second var.
_var_data: Additional hooks and imports associated with the Var.
Returns:
The AndOperation.
"""
var1, var2 = map(LiteralVar.create, (var1, var2))
return AndOperation(
_var_name="",
_var_type=unionize(var1._var_type, var2._var_type),
_var_data=ImmutableVarData.merge(_var_data),
_var1=var1,
_var2=var2,
)
@dataclasses.dataclass(
eq=False,
@ -970,27 +1072,8 @@ class OrOperation(ImmutableVar):
# 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(OrOperation, 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)
)
def __post_init__(self):
"""Post-initialize the OrOperation."""
object.__delattr__(self, "_var_name")
@functools.cached_property
@ -1044,6 +1127,29 @@ class OrOperation(ImmutableVar):
"""
return hash((self.__class__.__name__, self._var1, self._var2))
@classmethod
def create(
cls, var1: Var | Any, var2: Var | Any, _var_data: VarData | None = None
) -> OrOperation:
"""Create an OrOperation.
Args:
var1: The first var.
var2: The second var.
_var_data: Additional hooks and imports associated with the Var.
Returns:
The OrOperation.
"""
var1, var2 = map(LiteralVar.create, (var1, var2))
return OrOperation(
_var_name="",
_var_type=unionize(var1._var_type, var2._var_type),
_var_data=ImmutableVarData.merge(_var_data),
_var1=var1,
_var2=var2,
)
@dataclasses.dataclass(
eq=False,
@ -1057,14 +1163,14 @@ class ImmutableCallableVar(ImmutableVar):
API with functions that return a family of Var.
"""
fn: Callable[..., ImmutableVar] = dataclasses.field(
default_factory=lambda: lambda: LiteralVar.create(None)
fn: Callable[..., Var] = dataclasses.field(
default_factory=lambda: lambda: ImmutableVar(_var_name="undefined")
)
original_var: ImmutableVar = dataclasses.field(
default_factory=lambda: LiteralVar.create(None)
original_var: Var = dataclasses.field(
default_factory=lambda: ImmutableVar(_var_name="undefined")
)
def __init__(self, fn: Callable[..., ImmutableVar]):
def __init__(self, fn: Callable[..., Var]):
"""Initialize a CallableVar.
Args:
@ -1074,12 +1180,12 @@ class ImmutableCallableVar(ImmutableVar):
super(ImmutableCallableVar, self).__init__(
_var_name=original_var._var_name,
_var_type=original_var._var_type,
_var_data=original_var._var_data,
_var_data=ImmutableVarData.merge(original_var._var_data),
)
object.__setattr__(self, "fn", fn)
object.__setattr__(self, "original_var", original_var)
def __call__(self, *args, **kwargs) -> ImmutableVar:
def __call__(self, *args, **kwargs) -> Var:
"""Call the decorated function.
Args:
@ -1098,3 +1204,433 @@ class ImmutableCallableVar(ImmutableVar):
The hash of the object.
"""
return hash((self.__class__.__name__, self.original_var))
@dataclasses.dataclass(
eq=False,
frozen=True,
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class ImmutableComputedVar(ImmutableVar):
"""A field with computed getters."""
# Whether to track dependencies and cache computed values
_cache: bool = dataclasses.field(default=False)
# Whether the computed var is a backend var
_backend: bool = dataclasses.field(default=False)
# The initial value of the computed var
_initial_value: Any | types.Unset = dataclasses.field(default=types.Unset())
# Explicit var dependencies to track
_static_deps: set[str] = dataclasses.field(default_factory=set)
# Whether var dependencies should be auto-determined
_auto_deps: bool = dataclasses.field(default=True)
# Interval at which the computed var should be updated
_update_interval: Optional[datetime.timedelta] = dataclasses.field(default=None)
_fget: Callable[[BaseState], Any] = dataclasses.field(
default_factory=lambda: lambda _: None
)
def __init__(
self,
fget: Callable[[BaseState], Any],
initial_value: Any | types.Unset = types.Unset(),
cache: bool = False,
deps: Optional[List[Union[str, Var]]] = None,
auto_deps: bool = True,
interval: Optional[Union[int, datetime.timedelta]] = None,
backend: bool | None = None,
**kwargs,
):
"""Initialize a ComputedVar.
Args:
fget: The getter function.
initial_value: The initial value of the computed var.
cache: Whether to cache the computed value.
deps: Explicit var dependencies to track.
auto_deps: Whether var dependencies should be auto-determined.
interval: Interval at which the computed var should be updated.
backend: Whether the computed var is a backend var.
**kwargs: additional attributes to set on the instance
Raises:
TypeError: If the computed var dependencies are not Var instances or var names.
"""
hints = get_type_hints(fget)
hint = hints.get("return", Any)
kwargs["_var_name"] = kwargs.pop("_var_name", fget.__name__)
kwargs["_var_type"] = kwargs.pop("_var_type", hint)
super(ImmutableComputedVar, self).__init__(
_var_name=kwargs.pop("_var_name"),
_var_type=kwargs.pop("_var_type"),
_var_data=ImmutableVarData.merge(kwargs.pop("_var_data", None)),
)
if backend is None:
backend = fget.__name__.startswith("_")
object.__setattr__(self, "_backend", backend)
object.__setattr__(self, "_initial_value", initial_value)
object.__setattr__(self, "_cache", cache)
if isinstance(interval, int):
interval = datetime.timedelta(seconds=interval)
object.__setattr__(self, "_update_interval", interval)
if deps is None:
deps = []
else:
for dep in deps:
if isinstance(dep, Var):
continue
if isinstance(dep, str) and dep != "":
continue
raise TypeError(
"ComputedVar dependencies must be Var instances or var names (non-empty strings)."
)
object.__setattr__(
self,
"_static_deps",
{dep._var_name if isinstance(dep, Var) else dep for dep in deps},
)
object.__setattr__(self, "_auto_deps", auto_deps)
object.__setattr__(self, "_fget", fget)
@override
def _replace(self, merge_var_data=None, **kwargs: Any) -> ImmutableComputedVar:
"""Replace the attributes of the ComputedVar.
Args:
merge_var_data: VarData to merge into the existing VarData.
**kwargs: Var fields to update.
Returns:
The new ComputedVar instance.
Raises:
TypeError: If kwargs contains keys that are not allowed.
"""
field_values = dict(
fget=kwargs.pop("fget", self._fget),
initial_value=kwargs.pop("initial_value", self._initial_value),
cache=kwargs.pop("cache", self._cache),
deps=kwargs.pop("deps", self._static_deps),
auto_deps=kwargs.pop("auto_deps", self._auto_deps),
interval=kwargs.pop("interval", self._update_interval),
backend=kwargs.pop("backend", self._backend),
_var_name=kwargs.pop("_var_name", self._var_name),
_var_type=kwargs.pop("_var_type", self._var_type),
_var_is_local=kwargs.pop("_var_is_local", self._var_is_local),
_var_is_string=kwargs.pop("_var_is_string", self._var_is_string),
_var_full_name_needs_state_prefix=kwargs.pop(
"_var_full_name_needs_state_prefix",
self._var_full_name_needs_state_prefix,
),
_var_data=kwargs.pop(
"_var_data", VarData.merge(self._var_data, merge_var_data)
),
)
if kwargs:
unexpected_kwargs = ", ".join(kwargs.keys())
raise TypeError(f"Unexpected keyword arguments: {unexpected_kwargs}")
return ImmutableComputedVar(**field_values)
@property
def _cache_attr(self) -> str:
"""Get the attribute used to cache the value on the instance.
Returns:
An attribute name.
"""
return f"__cached_{self._var_name}"
@property
def _last_updated_attr(self) -> str:
"""Get the attribute used to store the last updated timestamp.
Returns:
An attribute name.
"""
return f"__last_updated_{self._var_name}"
def needs_update(self, instance: BaseState) -> bool:
"""Check if the computed var needs to be updated.
Args:
instance: The state instance that the computed var is attached to.
Returns:
True if the computed var needs to be updated, False otherwise.
"""
if self._update_interval is None:
return False
last_updated = getattr(instance, self._last_updated_attr, None)
if last_updated is None:
return True
return datetime.datetime.now() - last_updated > self._update_interval
def __get__(self, instance: BaseState | None, owner):
"""Get the ComputedVar value.
If the value is already cached on the instance, return the cached value.
Args:
instance: the instance of the class accessing this computed var.
owner: the class that this descriptor is attached to.
Returns:
The value of the var for the given instance.
"""
if instance is None:
return self._replace(
_var_name=format_state_name(owner.get_full_name())
+ "."
+ self._var_name,
merge_var_data=ImmutableVarData.from_state(owner),
).guess_type()
if not self._cache:
return self.fget(instance)
# handle caching
if not hasattr(instance, self._cache_attr) or self.needs_update(instance):
# Set cache attr on state instance.
setattr(instance, self._cache_attr, self.fget(instance))
# Ensure the computed var gets serialized to redis.
instance._was_touched = True
# Set the last updated timestamp on the state instance.
setattr(instance, self._last_updated_attr, datetime.datetime.now())
return getattr(instance, self._cache_attr)
def _deps(
self,
objclass: Type,
obj: FunctionType | CodeType | None = None,
self_name: Optional[str] = None,
) -> set[str]:
"""Determine var dependencies of this ComputedVar.
Save references to attributes accessed on "self". Recursively called
when the function makes a method call on "self" or define comprehensions
or nested functions that may reference "self".
Args:
objclass: the class obj this ComputedVar is attached to.
obj: the object to disassemble (defaults to the fget function).
self_name: if specified, look for this name in LOAD_FAST and LOAD_DEREF instructions.
Returns:
A set of variable names accessed by the given obj.
Raises:
VarValueError: if the function references the get_state, parent_state, or substates attributes
(cannot track deps in a related state, only implicitly via parent state).
"""
if not self._auto_deps:
return self._static_deps
d = self._static_deps.copy()
if obj is None:
fget = self._fget
if fget is not None:
obj = cast(FunctionType, fget)
else:
return set()
with contextlib.suppress(AttributeError):
# unbox functools.partial
obj = cast(FunctionType, obj.func) # type: ignore
with contextlib.suppress(AttributeError):
# unbox EventHandler
obj = cast(FunctionType, obj.fn) # type: ignore
if self_name is None and isinstance(obj, FunctionType):
try:
# the first argument to the function is the name of "self" arg
self_name = obj.__code__.co_varnames[0]
except (AttributeError, IndexError):
self_name = None
if self_name is None:
# cannot reference attributes on self if method takes no args
return set()
invalid_names = ["get_state", "parent_state", "substates", "get_substate"]
self_is_top_of_stack = False
for instruction in dis.get_instructions(obj):
if (
instruction.opname in ("LOAD_FAST", "LOAD_DEREF")
and instruction.argval == self_name
):
# bytecode loaded the class instance to the top of stack, next load instruction
# is referencing an attribute on self
self_is_top_of_stack = True
continue
if self_is_top_of_stack and instruction.opname in (
"LOAD_ATTR",
"LOAD_METHOD",
):
try:
ref_obj = getattr(objclass, instruction.argval)
except Exception:
ref_obj = None
if instruction.argval in invalid_names:
raise VarValueError(
f"Cached var {self._var_full_name} cannot access arbitrary state via `{instruction.argval}`."
)
if callable(ref_obj):
# recurse into callable attributes
d.update(
self._deps(
objclass=objclass,
obj=ref_obj,
)
)
# recurse into property fget functions
elif isinstance(ref_obj, property) and not isinstance(
ref_obj, ImmutableComputedVar
):
d.update(
self._deps(
objclass=objclass,
obj=ref_obj.fget, # type: ignore
)
)
elif (
instruction.argval in objclass.backend_vars
or instruction.argval in objclass.vars
):
# var access
d.add(instruction.argval)
elif instruction.opname == "LOAD_CONST" and isinstance(
instruction.argval, CodeType
):
# recurse into nested functions / comprehensions, which can reference
# instance attributes from the outer scope
d.update(
self._deps(
objclass=objclass,
obj=instruction.argval,
self_name=self_name,
)
)
self_is_top_of_stack = False
return d
def mark_dirty(self, instance) -> None:
"""Mark this ComputedVar as dirty.
Args:
instance: the state instance that needs to recompute the value.
"""
with contextlib.suppress(AttributeError):
delattr(instance, self._cache_attr)
def _determine_var_type(self) -> Type:
"""Get the type of the var.
Returns:
The type of the var.
"""
hints = get_type_hints(self._fget)
if "return" in hints:
return hints["return"]
return Any
@property
def __class__(self) -> Type:
"""Get the class of the var.
Returns:
The class of the var.
"""
return ComputedVar
@property
def fget(self) -> Callable[[BaseState], Any]:
"""Get the getter function.
Returns:
The getter function.
"""
return self._fget
def immutable_computed_var(
fget: Callable[[BaseState], Any] | None = None,
initial_value: Any | types.Unset = types.Unset(),
cache: bool = False,
deps: Optional[List[Union[str, Var]]] = None,
auto_deps: bool = True,
interval: Optional[Union[datetime.timedelta, int]] = None,
backend: bool | None = None,
_deprecated_cached_var: bool = False,
**kwargs,
) -> (
ImmutableComputedVar | Callable[[Callable[[BaseState], Any]], ImmutableComputedVar]
):
"""A ComputedVar decorator with or without kwargs.
Args:
fget: The getter function.
initial_value: The initial value of the computed var.
cache: Whether to cache the computed value.
deps: Explicit var dependencies to track.
auto_deps: Whether var dependencies should be auto-determined.
interval: Interval at which the computed var should be updated.
backend: Whether the computed var is a backend var.
_deprecated_cached_var: Indicate usage of deprecated cached_var partial function.
**kwargs: additional attributes to set on the instance
Returns:
A ComputedVar instance.
Raises:
ValueError: If caching is disabled and an update interval is set.
VarDependencyError: If user supplies dependencies without caching.
"""
if _deprecated_cached_var:
console.deprecate(
feature_name="cached_var",
reason=("Use @rx.var(cache=True) instead of @rx.cached_var."),
deprecation_version="0.5.6",
removal_version="0.6.0",
)
if cache is False and interval is not None:
raise ValueError("Cannot set update interval without caching.")
if cache is False and (deps is not None or auto_deps is False):
raise VarDependencyError("Cannot track dependencies without caching.")
if fget is not None:
return ImmutableComputedVar(fget, cache=cache)
def wrapper(fget: Callable[[BaseState], Any]) -> ImmutableComputedVar:
return ImmutableComputedVar(
fget,
initial_value=initial_value,
cache=cache,
deps=deps,
auto_deps=auto_deps,
interval=interval,
backend=backend,
**kwargs,
)
return wrapper
# Partial function of computed_var with cache=True
cached_var = functools.partial(
immutable_computed_var, cache=True, _deprecated_cached_var=True
)

View File

@ -7,6 +7,7 @@ import sys
from functools import cached_property
from typing import Any, Callable, Optional, Tuple, Type, Union
from reflex.utils.types import GenericType
from reflex.vars import ImmutableVarData, Var, VarData
from .base import ImmutableVar, LiteralVar
@ -24,9 +25,9 @@ class FunctionVar(ImmutableVar[Callable]):
Returns:
The function call operation.
"""
return ArgsFunctionOperation(
return ArgsFunctionOperation.create(
("...args",),
VarOperationCall(self, *args, ImmutableVar.create_safe("...args")),
VarOperationCall.create(self, *args, ImmutableVar.create_safe("...args")),
)
def call(self, *args: Var | Any) -> VarOperationCall:
@ -38,22 +39,31 @@ class FunctionVar(ImmutableVar[Callable]):
Returns:
The function call operation.
"""
return VarOperationCall(self, *args)
return VarOperationCall.create(self, *args)
class FunctionStringVar(FunctionVar):
"""Base class for immutable function vars from a string."""
def __init__(self, func: str, _var_data: VarData | None = None) -> None:
"""Initialize the function var.
@classmethod
def create(
cls,
func: str,
_var_type: Type[Callable] = Callable,
_var_data: VarData | None = None,
) -> FunctionStringVar:
"""Create a new function var from a string.
Args:
func: The function to call.
_var_data: Additional hooks and imports associated with the Var.
Returns:
The function var.
"""
super(FunctionVar, self).__init__(
return cls(
_var_name=func,
_var_type=Callable,
_var_type=_var_type,
_var_data=ImmutableVarData.merge(_var_data),
)
@ -69,25 +79,6 @@ class VarOperationCall(ImmutableVar):
_func: Optional[FunctionVar] = dataclasses.field(default=None)
_args: Tuple[Union[Var, Any], ...] = dataclasses.field(default_factory=tuple)
def __init__(
self, func: FunctionVar, *args: Var | Any, _var_data: VarData | None = None
):
"""Initialize the function call var.
Args:
func: The function to call.
*args: The arguments to call the function with.
_var_data: Additional hooks and imports associated with the Var.
"""
super(VarOperationCall, self).__init__(
_var_name="",
_var_type=Any,
_var_data=ImmutableVarData.merge(_var_data),
)
object.__setattr__(self, "_func", func)
object.__setattr__(self, "_args", args)
object.__delattr__(self, "_var_name")
def __getattr__(self, name):
"""Get an attribute of the var.
@ -133,7 +124,7 @@ class VarOperationCall(ImmutableVar):
def __post_init__(self):
"""Post-initialize the var."""
pass
object.__delattr__(self, "_var_name")
def __hash__(self):
"""Hash the var.
@ -143,6 +134,32 @@ class VarOperationCall(ImmutableVar):
"""
return hash((self.__class__.__name__, self._func, self._args))
@classmethod
def create(
cls,
func: FunctionVar,
*args: Var | Any,
_var_type: GenericType = Any,
_var_data: VarData | None = None,
) -> VarOperationCall:
"""Create a new function call var.
Args:
func: The function to call.
*args: The arguments to call the function with.
_var_data: Additional hooks and imports associated with the Var.
Returns:
The function call var.
"""
return cls(
_var_name="",
_var_type=_var_type,
_var_data=ImmutableVarData.merge(_var_data),
_func=func,
_args=args,
)
@dataclasses.dataclass(
eq=False,
@ -155,28 +172,6 @@ class ArgsFunctionOperation(FunctionVar):
_args_names: Tuple[str, ...] = dataclasses.field(default_factory=tuple)
_return_expr: Union[Var, Any] = dataclasses.field(default=None)
def __init__(
self,
args_names: Tuple[str, ...],
return_expr: Var | Any,
_var_data: VarData | None = None,
) -> None:
"""Initialize the function with arguments var.
Args:
args_names: The names of the arguments.
return_expr: The return expression of the function.
_var_data: Additional hooks and imports associated with the Var.
"""
super(ArgsFunctionOperation, self).__init__(
_var_name=f"",
_var_type=Callable,
_var_data=ImmutableVarData.merge(_var_data),
)
object.__setattr__(self, "_args_names", args_names)
object.__setattr__(self, "_return_expr", return_expr)
object.__delattr__(self, "_var_name")
def __getattr__(self, name):
"""Get an attribute of the var.
@ -221,6 +216,7 @@ class ArgsFunctionOperation(FunctionVar):
def __post_init__(self):
"""Post-initialize the var."""
object.__delattr__(self, "_var_name")
def __hash__(self):
"""Hash the var.
@ -230,6 +226,32 @@ class ArgsFunctionOperation(FunctionVar):
"""
return hash((self.__class__.__name__, self._args_names, self._return_expr))
@classmethod
def create(
cls,
args_names: Tuple[str, ...],
return_expr: Var | Any,
_var_type: GenericType = Callable,
_var_data: VarData | None = None,
) -> ArgsFunctionOperation:
"""Create a new function var.
Args:
args_names: The names of the arguments.
return_expr: The return expression of the function.
_var_data: Additional hooks and imports associated with the Var.
Returns:
The function var.
"""
return cls(
_var_name="",
_var_type=_var_type,
_var_data=ImmutableVarData.merge(_var_data),
_args_names=args_names,
_return_expr=return_expr,
)
@dataclasses.dataclass(
eq=False,
@ -243,25 +265,8 @@ class ToFunctionOperation(FunctionVar):
default_factory=lambda: LiteralVar.create(None)
)
def __init__(
self,
original_var: Var,
_var_type: Type[Callable] = Callable,
_var_data: VarData | None = None,
) -> None:
"""Initialize the function with arguments var.
Args:
original_var: The original var to convert to a function.
_var_type: The type of the function.
_var_data: Additional hooks and imports associated with the Var.
"""
super(ToFunctionOperation, self).__init__(
_var_name=f"",
_var_type=_var_type,
_var_data=ImmutableVarData.merge(_var_data),
)
object.__setattr__(self, "_original_var", original_var)
def __post_init__(self):
"""Post-initialize the var."""
object.__delattr__(self, "_var_name")
def __getattr__(self, name):
@ -314,5 +319,29 @@ class ToFunctionOperation(FunctionVar):
"""
return hash((self.__class__.__name__, self._original_var))
@classmethod
def create(
cls,
original_var: Var,
_var_type: GenericType = Callable,
_var_data: VarData | None = None,
) -> ToFunctionOperation:
"""Create a new function var.
JSON_STRINGIFY = FunctionStringVar("JSON.stringify")
Args:
original_var: The original var to convert to a function.
_var_type: The type of the function.
_var_data: Additional hooks and imports associated with the Var.
Returns:
The function var.
"""
return cls(
_var_name="",
_var_type=_var_type,
_var_data=ImmutableVarData.merge(_var_data),
_original_var=original_var,
)
JSON_STRINGIFY = FunctionStringVar.create("JSON.stringify")

File diff suppressed because it is too large Load Diff

View File

@ -34,7 +34,7 @@ from .base import (
from .number import BooleanVar, NumberVar
from .sequence import ArrayVar, StringVar
OBJECT_TYPE = TypeVar("OBJECT_TYPE")
OBJECT_TYPE = TypeVar("OBJECT_TYPE", bound=Dict)
KEY_TYPE = TypeVar("KEY_TYPE")
VALUE_TYPE = TypeVar("VALUE_TYPE")
@ -79,7 +79,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
Returns:
The keys of the object.
"""
return ObjectKeysOperation(self)
return ObjectKeysOperation.create(self)
@overload
def values(
@ -95,7 +95,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
Returns:
The values of the object.
"""
return ObjectValuesOperation(self)
return ObjectValuesOperation.create(self)
@overload
def entries(
@ -111,7 +111,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
Returns:
The entries of the object.
"""
return ObjectEntriesOperation(self)
return ObjectEntriesOperation.create(self)
def merge(self, other: ObjectVar) -> ObjectMergeOperation:
"""Merge two objects.
@ -122,7 +122,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
Returns:
The merged object.
"""
return ObjectMergeOperation(self, other)
return ObjectMergeOperation.create(self, other)
# NoReturn is used here to catch when key value is Any
@overload
@ -180,7 +180,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
Returns:
The item from the object.
"""
return ObjectItemOperation(self, key).guess_type()
return ObjectItemOperation.create(self, key).guess_type()
# NoReturn is used here to catch when key value is Any
@overload
@ -253,9 +253,9 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
f"The State var `{self._var_name}` has no attribute '{name}' or may have been annotated "
f"wrongly."
)
return ObjectItemOperation(self, name, attribute_type).guess_type()
return ObjectItemOperation.create(self, name, attribute_type).guess_type()
else:
return ObjectItemOperation(self, name).guess_type()
return ObjectItemOperation.create(self, name).guess_type()
def contains(self, key: Var | Any) -> BooleanVar:
"""Check if the object contains a key.
@ -266,7 +266,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
Returns:
The result of the check.
"""
return ObjectHasOwnProperty(self, key)
return ObjectHasOwnProperty.create(self, key)
@dataclasses.dataclass(
@ -281,29 +281,8 @@ class LiteralObjectVar(LiteralVar, ObjectVar[OBJECT_TYPE]):
default_factory=dict
)
def __init__(
self: LiteralObjectVar[OBJECT_TYPE],
_var_value: OBJECT_TYPE,
_var_type: Type[OBJECT_TYPE] | None = None,
_var_data: VarData | None = None,
):
"""Initialize the object var.
Args:
_var_value: The value of the var.
_var_type: The type of the var.
_var_data: Additional hooks and imports associated with the Var.
"""
super(LiteralObjectVar, self).__init__(
_var_name="",
_var_type=(figure_out_type(_var_value) if _var_type is None else _var_type),
_var_data=ImmutableVarData.merge(_var_data),
)
object.__setattr__(
self,
"_var_value",
_var_value,
)
def __post_init__(self):
"""Post initialization."""
object.__delattr__(self, "_var_name")
def _key_type(self) -> Type:
@ -409,6 +388,30 @@ class LiteralObjectVar(LiteralVar, ObjectVar[OBJECT_TYPE]):
"""
return hash((self.__class__.__name__, self._var_name))
@classmethod
def create(
cls,
_var_value: OBJECT_TYPE,
_var_type: GenericType | None = None,
_var_data: VarData | None = None,
) -> LiteralObjectVar[OBJECT_TYPE]:
"""Create the literal object var.
Args:
_var_value: The value of the var.
_var_type: The type of the var.
_var_data: Additional hooks and imports associated with the Var.
Returns:
The literal object var.
"""
return LiteralObjectVar(
_var_name="",
_var_type=(figure_out_type(_var_value) if _var_type is None else _var_type),
_var_data=ImmutableVarData.merge(_var_data),
_var_value=_var_value,
)
@dataclasses.dataclass(
eq=False,
@ -418,26 +421,12 @@ class LiteralObjectVar(LiteralVar, ObjectVar[OBJECT_TYPE]):
class ObjectToArrayOperation(ArrayVar):
"""Base class for object to array operations."""
value: ObjectVar = dataclasses.field(default_factory=lambda: LiteralObjectVar({}))
_value: ObjectVar = dataclasses.field(
default_factory=lambda: LiteralObjectVar.create({})
)
def __init__(
self,
_var_value: ObjectVar,
_var_type: Type = list,
_var_data: VarData | None = None,
):
"""Initialize the object to array operation.
Args:
_var_value: The value of the operation.
_var_data: Additional hooks and imports associated with the operation.
"""
super(ObjectToArrayOperation, self).__init__(
_var_name="",
_var_type=_var_type,
_var_data=ImmutableVarData.merge(_var_data),
)
object.__setattr__(self, "value", _var_value)
def __post_init__(self):
"""Post initialization."""
object.__delattr__(self, "_var_name")
@cached_property
@ -472,7 +461,7 @@ class ObjectToArrayOperation(ArrayVar):
The VarData of the components and all of its children.
"""
return ImmutableVarData.merge(
self.value._get_all_var_data(),
self._value._get_all_var_data(),
self._var_data,
)
@ -490,26 +479,37 @@ class ObjectToArrayOperation(ArrayVar):
Returns:
The hash of the operation.
"""
return hash((self.__class__.__name__, self.value))
return hash((self.__class__.__name__, self._value))
@classmethod
def create(
cls,
value: ObjectVar,
_var_type: GenericType | None = None,
_var_data: VarData | None = None,
) -> ObjectToArrayOperation:
"""Create the object to array operation.
Args:
value: The value of the operation.
_var_data: Additional hooks and imports associated with the operation.
Returns:
The object to array operation.
"""
return cls(
_var_name="",
_var_type=list if _var_type is None else _var_type,
_var_data=ImmutableVarData.merge(_var_data),
_value=value,
)
class ObjectKeysOperation(ObjectToArrayOperation):
"""Operation to get the keys of an object."""
def __init__(
self,
value: ObjectVar,
_var_data: VarData | None = None,
):
"""Initialize the object keys operation.
Args:
value: The value of the operation.
_var_data: Additional hooks and imports associated with the operation.
"""
super(ObjectKeysOperation, self).__init__(
value, List[value._key_type()], _var_data
)
# value, List[value._key_type()], _var_data
# )
@cached_property
def _cached_var_name(self) -> str:
@ -518,27 +518,34 @@ class ObjectKeysOperation(ObjectToArrayOperation):
Returns:
The name of the operation.
"""
return f"Object.keys({self.value._var_name})"
return f"Object.keys({str(self._value)})"
@classmethod
def create(
cls,
value: ObjectVar,
_var_data: VarData | None = None,
) -> ObjectKeysOperation:
"""Create the object keys operation.
Args:
value: The value of the operation.
_var_data: Additional hooks and imports associated with the operation.
Returns:
The object keys operation.
"""
return cls(
_var_name="",
_var_type=List[str],
_var_data=ImmutableVarData.merge(_var_data),
_value=value,
)
class ObjectValuesOperation(ObjectToArrayOperation):
"""Operation to get the values of an object."""
def __init__(
self,
value: ObjectVar,
_var_data: VarData | None = None,
):
"""Initialize the object values operation.
Args:
value: The value of the operation.
_var_data: Additional hooks and imports associated with the operation.
"""
super(ObjectValuesOperation, self).__init__(
value, List[value._value_type()], _var_data
)
@cached_property
def _cached_var_name(self) -> str:
"""The name of the operation.
@ -546,27 +553,34 @@ class ObjectValuesOperation(ObjectToArrayOperation):
Returns:
The name of the operation.
"""
return f"Object.values({self.value._var_name})"
return f"Object.values({self._value._var_name})"
@classmethod
def create(
cls,
value: ObjectVar,
_var_data: VarData | None = None,
) -> ObjectValuesOperation:
"""Create the object values operation.
Args:
value: The value of the operation.
_var_data: Additional hooks and imports associated with the operation.
Returns:
The object values operation.
"""
return cls(
_var_name="",
_var_type=List[value._value_type()],
_var_data=ImmutableVarData.merge(_var_data),
_value=value,
)
class ObjectEntriesOperation(ObjectToArrayOperation):
"""Operation to get the entries of an object."""
def __init__(
self,
value: ObjectVar,
_var_data: VarData | None = None,
):
"""Initialize the object entries operation.
Args:
value: The value of the operation.
_var_data: Additional hooks and imports associated with the operation.
"""
super(ObjectEntriesOperation, self).__init__(
value, List[Tuple[value._key_type(), value._value_type()]], _var_data
)
@cached_property
def _cached_var_name(self) -> str:
"""The name of the operation.
@ -574,7 +588,29 @@ class ObjectEntriesOperation(ObjectToArrayOperation):
Returns:
The name of the operation.
"""
return f"Object.entries({self.value._var_name})"
return f"Object.entries({self._value._var_name})"
@classmethod
def create(
cls,
value: ObjectVar,
_var_data: VarData | None = None,
) -> ObjectEntriesOperation:
"""Create the object entries operation.
Args:
value: The value of the operation.
_var_data: Additional hooks and imports associated with the operation.
Returns:
The object entries operation.
"""
return cls(
_var_name="",
_var_type=List[Tuple[str, value._value_type()]],
_var_data=ImmutableVarData.merge(_var_data),
_value=value,
)
@dataclasses.dataclass(
@ -585,30 +621,12 @@ class ObjectEntriesOperation(ObjectToArrayOperation):
class ObjectMergeOperation(ObjectVar):
"""Operation to merge two objects."""
left: ObjectVar = dataclasses.field(default_factory=lambda: LiteralObjectVar({}))
right: ObjectVar = dataclasses.field(default_factory=lambda: LiteralObjectVar({}))
def __init__(
self,
left: ObjectVar,
right: ObjectVar,
_var_data: VarData | None = None,
):
"""Initialize the object merge operation.
Args:
left: The left object to merge.
right: The right object to merge.
_var_data: Additional hooks and imports associated with the operation.
"""
super(ObjectMergeOperation, self).__init__(
_var_name="",
_var_type=left._var_type,
_var_data=ImmutableVarData.merge(_var_data),
)
object.__setattr__(self, "left", left)
object.__setattr__(self, "right", right)
object.__delattr__(self, "_var_name")
_lhs: ObjectVar = dataclasses.field(
default_factory=lambda: LiteralObjectVar.create({})
)
_rhs: ObjectVar = dataclasses.field(
default_factory=lambda: LiteralObjectVar.create({})
)
@cached_property
def _cached_var_name(self) -> str:
@ -617,7 +635,7 @@ class ObjectMergeOperation(ObjectVar):
Returns:
The name of the operation.
"""
return f"Object.assign({self.left._var_name}, {self.right._var_name})"
return f"Object.assign({self._lhs._var_name}, {self._rhs._var_name})"
def __getattr__(self, name):
"""Get an attribute of the operation.
@ -640,8 +658,8 @@ class ObjectMergeOperation(ObjectVar):
The VarData of the components and all of its children.
"""
return ImmutableVarData.merge(
self.left._get_all_var_data(),
self.right._get_all_var_data(),
self._lhs._get_all_var_data(),
self._rhs._get_all_var_data(),
self._var_data,
)
@ -659,7 +677,33 @@ class ObjectMergeOperation(ObjectVar):
Returns:
The hash of the operation.
"""
return hash((self.__class__.__name__, self.left, self.right))
return hash((self.__class__.__name__, self._lhs, self._rhs))
@classmethod
def create(
cls,
lhs: ObjectVar,
rhs: ObjectVar,
_var_data: VarData | None = None,
) -> ObjectMergeOperation:
"""Create the object merge operation.
Args:
lhs: The left object to merge.
rhs: The right object to merge.
_var_data: Additional hooks and imports associated with the operation.
Returns:
The object merge operation.
"""
# TODO: Figure out how to merge the types
return cls(
_var_name="",
_var_type=lhs._var_type,
_var_data=ImmutableVarData.merge(_var_data),
_lhs=lhs,
_rhs=rhs,
)
@dataclasses.dataclass(
@ -670,33 +714,10 @@ class ObjectMergeOperation(ObjectVar):
class ObjectItemOperation(ImmutableVar):
"""Operation to get an item from an object."""
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_type: GenericType | None = None,
_var_data: VarData | None = None,
):
"""Initialize the object item operation.
Args:
value: The value of the operation.
key: The key to get from the object.
_var_data: Additional hooks and imports associated with the operation.
"""
super(ObjectItemOperation, self).__init__(
_var_name="",
_var_type=value._value_type() if _var_type is None else _var_type,
_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")
_object: ObjectVar = dataclasses.field(
default_factory=lambda: LiteralObjectVar.create({})
)
_key: Var | Any = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
@cached_property
def _cached_var_name(self) -> str:
@ -705,7 +726,7 @@ class ObjectItemOperation(ImmutableVar):
Returns:
The name of the operation.
"""
return f"{str(self.value)}[{str(self.key)}]"
return f"{str(self._object)}[{str(self._key)}]"
def __getattr__(self, name):
"""Get an attribute of the operation.
@ -728,8 +749,8 @@ class ObjectItemOperation(ImmutableVar):
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._object._get_all_var_data(),
self._key._get_all_var_data(),
self._var_data,
)
@ -747,7 +768,38 @@ class ObjectItemOperation(ImmutableVar):
Returns:
The hash of the operation.
"""
return hash((self.__class__.__name__, self.value, self.key))
return hash((self.__class__.__name__, self._object, self._key))
def __post_init__(self):
"""Post initialization."""
object.__delattr__(self, "_var_name")
@classmethod
def create(
cls,
object: ObjectVar,
key: Var | Any,
_var_type: GenericType | None = None,
_var_data: VarData | None = None,
) -> ObjectItemOperation:
"""Create the object item operation.
Args:
object: The object to get the item from.
key: The key to get from the object.
_var_type: The type of the item.
_var_data: Additional hooks and imports associated with the operation.
Returns:
The object item operation.
"""
return cls(
_var_name="",
_var_type=object._value_type() if _var_type is None else _var_type,
_var_data=ImmutableVarData.merge(_var_data),
_object=object,
_key=key if isinstance(key, Var) else LiteralVar.create(key),
)
@dataclasses.dataclass(
@ -758,28 +810,9 @@ class ObjectItemOperation(ImmutableVar):
class ToObjectOperation(ObjectVar):
"""Operation to convert a var to an object."""
_original_var: Var = dataclasses.field(default_factory=lambda: LiteralObjectVar({}))
def __init__(
self,
_original_var: Var,
_var_type: Type = dict,
_var_data: VarData | None = None,
):
"""Initialize the to object operation.
Args:
_original_var: The original var to convert.
_var_type: The type of the var.
_var_data: Additional hooks and imports associated with the operation.
"""
super(ToObjectOperation, self).__init__(
_var_name="",
_var_type=_var_type,
_var_data=ImmutableVarData.merge(_var_data),
)
object.__setattr__(self, "_original_var", _original_var)
object.__delattr__(self, "_var_name")
_original_var: Var = dataclasses.field(
default_factory=lambda: LiteralObjectVar.create({})
)
@cached_property
def _cached_var_name(self) -> str:
@ -831,6 +864,34 @@ class ToObjectOperation(ObjectVar):
"""
return hash((self.__class__.__name__, self._original_var))
def __post_init__(self):
"""Post initialization."""
object.__delattr__(self, "_var_name")
@classmethod
def create(
cls,
original_var: Var,
_var_type: GenericType | None = None,
_var_data: VarData | None = None,
) -> ToObjectOperation:
"""Create the to object operation.
Args:
original_var: The original var to convert.
_var_type: The type of the var.
_var_data: Additional hooks and imports associated with the operation.
Returns:
The to object operation.
"""
return cls(
_var_name="",
_var_type=dict if _var_type is None else _var_type,
_var_data=ImmutableVarData.merge(_var_data),
_original_var=original_var,
)
@dataclasses.dataclass(
eq=False,
@ -840,30 +901,13 @@ class ToObjectOperation(ObjectVar):
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))
_object: ObjectVar = dataclasses.field(
default_factory=lambda: LiteralObjectVar.create({})
)
_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)
)
def __post_init__(self):
"""Post initialization."""
object.__delattr__(self, "_var_name")
@cached_property
@ -873,7 +917,7 @@ class ObjectHasOwnProperty(BooleanVar):
Returns:
The name of the operation.
"""
return f"{str(self.value)}.hasOwnProperty({str(self.key)})"
return f"{str(self._object)}.hasOwnProperty({str(self._key)})"
def __getattr__(self, name):
"""Get an attribute of the operation.
@ -896,8 +940,8 @@ class ObjectHasOwnProperty(BooleanVar):
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._object._get_all_var_data(),
self._key._get_all_var_data(),
self._var_data,
)
@ -915,4 +959,29 @@ class ObjectHasOwnProperty(BooleanVar):
Returns:
The hash of the operation.
"""
return hash((self.__class__.__name__, self.value, self.key))
return hash((self.__class__.__name__, self._object, self._key))
@classmethod
def create(
cls,
object: ObjectVar,
key: Var | Any,
_var_data: VarData | None = None,
) -> ObjectHasOwnProperty:
"""Create the object has own property operation.
Args:
object: The object to check.
key: The key to check.
_var_data: Additional hooks and imports associated with the operation.
Returns:
The object has own property operation.
"""
return cls(
_var_name="",
_var_type=bool,
_var_data=ImmutableVarData.merge(_var_data),
_object=object,
_key=key if isinstance(key, Var) else LiteralVar.create(key),
)

File diff suppressed because it is too large Load Diff

View File

@ -32,7 +32,7 @@ import dill
from sqlalchemy.orm import DeclarativeBase
from reflex.config import get_config
from reflex.ivars.base import ImmutableVar
from reflex.ivars.base import ImmutableComputedVar, ImmutableVar, immutable_computed_var
try:
import pydantic.v1 as pydantic
@ -60,7 +60,6 @@ from reflex.vars import (
ComputedVar,
ImmutableVarData,
Var,
computed_var,
)
if TYPE_CHECKING:
@ -68,7 +67,7 @@ if TYPE_CHECKING:
Delta = Dict[str, Any]
var = computed_var
var = immutable_computed_var
# If the state is this large, it's considered a performance issue.
@ -307,7 +306,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
base_vars: ClassVar[Dict[str, ImmutableVar]] = {}
# The computed vars of the class.
computed_vars: ClassVar[Dict[str, ComputedVar]] = {}
computed_vars: ClassVar[Dict[str, Union[ComputedVar, ImmutableComputedVar]]] = {}
# Vars inherited by the parent state.
inherited_vars: ClassVar[Dict[str, Var]] = {}
@ -420,7 +419,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
return f"{self.__class__.__name__}({self.dict()})"
@classmethod
def _get_computed_vars(cls) -> list[ComputedVar]:
def _get_computed_vars(cls) -> list[Union[ComputedVar, ImmutableComputedVar]]:
"""Helper function to get all computed vars of a instance.
Returns:
@ -430,7 +429,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
v
for mixin in cls._mixins() + [cls]
for v in mixin.__dict__.values()
if isinstance(v, ComputedVar)
if isinstance(v, (ComputedVar, ImmutableComputedVar))
]
@classmethod
@ -534,7 +533,10 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
for f in cls.get_fields().values()
if f.name not in cls.get_skip_vars()
}
cls.computed_vars = {v._var_name: v._var_set_state(cls) for v in computed_vars}
cls.computed_vars = {
v._var_name: v._replace(merge_var_data=ImmutableVarData.from_state(cls))
for v in computed_vars
}
cls.vars = {
**cls.inherited_vars,
**cls.base_vars,
@ -555,12 +557,12 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
for mixin in cls._mixins():
for name, value in mixin.__dict__.items():
if isinstance(value, ComputedVar):
if isinstance(value, (ComputedVar, ImmutableComputedVar)):
fget = cls._copy_fn(value.fget)
newcv = value._replace(fget=fget)
newcv = value._replace(
fget=fget, _var_data=ImmutableVarData.from_state(cls)
)
# cleanup refs to mixin cls in var_data
newcv._var_data = None
newcv._var_set_state(cls)
setattr(cls, name, newcv)
cls.computed_vars[newcv._var_name] = newcv
cls.vars[newcv._var_name] = newcv
@ -897,8 +899,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
)
# create the variable based on name and type
var = ImmutableVar(_var_name=name, _var_type=type_).guess_type()
var._var_set_state(cls)
var = ImmutableVar(
_var_name=name, _var_type=type_, _var_data=ImmutableVarData.from_state(cls)
).guess_type()
# add the pydantic field dynamically (must be done before _init_var)
cls.add_field(var, default_value)
@ -983,7 +986,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
and not types.is_optional(prop._var_type)
):
# Ensure frontend uses null coalescing when accessing.
prop._var_type = Optional[prop._var_type]
object.__setattr__(prop, "_var_type", Optional[prop._var_type])
@staticmethod
def _get_base_functions() -> dict[str, FunctionType]:
@ -1783,7 +1786,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
# Include initial computed vars.
prop_name: (
cv._initial_value
if isinstance(cv, ComputedVar)
if isinstance(cv, (ComputedVar, ImmutableComputedVar))
and not isinstance(cv._initial_value, types.Unset)
else self.get_value(getattr(self, prop_name))
)

View File

@ -9,8 +9,6 @@ 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
@ -274,8 +272,10 @@ def format_f_string_prop(prop: BaseVar) -> str:
Returns:
The formatted string.
"""
from reflex.ivars.base import VarData
s = prop._var_full_name
var_data = prop._var_data
var_data = VarData.merge(prop._get_all_var_data())
interps = var_data.interpolations if var_data else []
parts: List[str] = []
@ -423,6 +423,7 @@ def format_prop(
# import here to avoid circular import.
from reflex.event import EventChain
from reflex.utils import serializers
from reflex.vars import VarData
try:
# Handle var props.
@ -430,7 +431,8 @@ def format_prop(
if not prop._var_is_local or prop._var_is_string:
return str(prop)
if isinstance(prop, BaseVar) and types._issubclass(prop._var_type, str):
if prop._var_data and prop._var_data.interpolations:
var_data = VarData.merge(prop._get_all_var_data())
if var_data and var_data.interpolations:
return format_f_string_prop(prop)
return format_string(prop._var_full_name)
prop = prop._var_full_name
@ -485,17 +487,38 @@ def format_props(*single_props, **key_value_props) -> list[str]:
The formatted props list.
"""
# Format all the props.
from reflex.ivars.base import ImmutableVar
from reflex.ivars.base import ImmutableVar, LiteralVar
# print(
# *[
# f"{name}={{{format_prop(prop if isinstance(prop, Var) else LiteralVar.create(prop))}}}"
# for name, prop in sorted(key_value_props.items())
# if prop is not None
# ],
# sep="\n",
# )
# if single_props:
# print("single_props", single_props)
return [
(
f"{name}={{{format_prop(prop)}}}"
if isinstance(prop, ImmutableVar)
else f"{name}={format_prop(prop)}"
f"{name}={format_prop(prop)}"
if isinstance(prop, Var) and not isinstance(prop, ImmutableVar)
else (
f"{name}={{{format_prop(prop if isinstance(prop, Var) else LiteralVar.create(prop))}}}"
)
)
for name, prop in sorted(key_value_props.items())
if prop is not None
] + [str(prop) for prop in single_props]
] + [
(
str(prop)
if isinstance(prop, Var) and not isinstance(prop, ImmutableVar)
else f"{{{str(LiteralVar.create(prop))}}}"
)
for prop in single_props
]
def get_event_handler_parts(handler: EventHandler) -> tuple[str, str]:
@ -510,13 +533,13 @@ def get_event_handler_parts(handler: EventHandler) -> tuple[str, str]:
# Get the class that defines the event handler.
parts = handler.fn.__qualname__.split(".")
# If there's no enclosing class, just return the function name.
if len(parts) == 1:
return ("", parts[-1])
# Get the state full name
state_full_name = handler.state_full_name
# If there's no enclosing class, just return the function name.
if not state_full_name:
return ("", parts[-1])
# Get the function name
name = parts[-1]
@ -655,6 +678,7 @@ def format_queue_events(
call_event_fn,
call_event_handler,
)
from reflex.ivars.base import FunctionVar, ImmutableVar
if not events:
return ImmutableVar("(() => null)").to(FunctionVar, EventChain)
@ -944,6 +968,8 @@ def format_data_editor_cell(cell: Any):
Returns:
The formatted cell.
"""
from reflex.ivars.base import ImmutableVar
return {
"kind": ImmutableVar.create("GridCellKind.Text"),
"data": cell,

View File

@ -491,10 +491,18 @@ def is_backend_base_variable(name: str, cls: Type) -> bool:
return False
if callable(value):
return False
from reflex.ivars.base import ImmutableComputedVar
from reflex.vars import ComputedVar
if isinstance(
value, (types.FunctionType, property, cached_property, ComputedVar)
value,
(
types.FunctionType,
property,
cached_property,
ComputedVar,
ImmutableComputedVar,
),
):
return False

View File

@ -892,6 +892,7 @@ class Var:
Raises:
VarTypeError: If the var is not indexable.
"""
print(repr(self))
from reflex.utils import format
# Indexing is only supported for strings, lists, tuples, dicts, and dataframes.

View File

@ -154,6 +154,7 @@ class Var:
def _var_set_state(self, state: Type[BaseState] | str) -> Any: ...
def _get_all_var_data(self) -> VarData | ImmutableVarData: ...
def json(self) -> str: ...
def _type(self) -> Var: ...
@dataclass(eq=False)
class BaseVar(Var):