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"], "style": ["Style", "toggle_color_mode"],
"utils.imports": ["ImportVar"], "utils.imports": ["ImportVar"],
"utils.serializers": ["serializer"], "utils.serializers": ["serializer"],
"vars": ["cached_var", "Var"], "vars": ["Var"],
"ivars.base": ["cached_var"],
} }
_SUBMODULES: set[str] = { _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 upload_files as upload_files
from .event import window_alert as window_alert from .event import window_alert as window_alert
from .experimental import _x as _x 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 .middleware import middleware as middleware from .middleware import middleware as middleware
from .model import Model as Model 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.imports import ImportVar as ImportVar
from .utils.serializers import serializer as serializer from .utils.serializers import serializer as serializer
from .vars import Var as Var from .vars import Var as Var
from .vars import cached_var as cached_var
del compat del compat
RADIX_THEMES_MAPPING: dict RADIX_THEMES_MAPPING: dict

View File

@ -442,7 +442,11 @@ class App(MiddlewareMixin, LifespanMixin, Base):
raise raise
except TypeError as e: except TypeError as e:
message = str(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( raise VarOperationTypeError(
"You may be trying to use an invalid Python function on a state var. " "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. " "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): if isinstance(contents, ImmutableVar):
return cls(contents=contents) 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) contents = contents.to(str)
else: else:
contents = str(contents) if contents is not None 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: for child in self.children:
if not isinstance(child, Component): if not isinstance(child, Component):
continue 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 return vars
@ -1326,13 +1327,13 @@ class Component(BaseComponent, ABC):
other_imports = [] other_imports = []
user_hooks = self._get_hooks() user_hooks = self._get_hooks()
if ( user_hooks_data = (
user_hooks is not None VarData.merge(user_hooks._get_all_var_data())
and isinstance(user_hooks, Var) if user_hooks is not None and isinstance(user_hooks, Var)
and user_hooks._var_data is not None else None
and user_hooks._var_data.imports )
): if user_hooks_data is not None:
other_imports.append(user_hooks._var_data.imports) other_imports.append(user_hooks_data.imports)
other_imports.extend( other_imports.extend(
hook_imports for hook_imports in self._get_added_hooks().values() hook_imports for hook_imports in self._get_added_hooks().values()
) )
@ -1830,9 +1831,11 @@ class CustomComponent(Component):
Returns: Returns:
Each var referenced by the component (props, styles, event handlers). Each var referenced by the component (props, styles, event handlers).
""" """
return super()._get_vars(include_children=include_children) + [ return (
prop for prop in self.props.values() if isinstance(prop, Var) 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 @lru_cache(maxsize=None) # noqa
def get_component(self) -> Component: 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 def create( # type: ignore
cls, cls,
*children, *children,
contents: Optional[Union[Var[str], str]] = None, contents: Optional[Union[Var[Any], Any]] = None,
style: Optional[Style] = None, style: Optional[Style] = None,
key: Optional[Any] = None, key: Optional[Any] = None,
id: Optional[Any] = None, id: Optional[Any] = None,

View File

@ -104,7 +104,7 @@ class Cond(MemoizationLeaf):
The import dict for the component. The import dict for the component.
""" """
cond_imports: dict[str, str | ImportVar | list[str | ImportVar]] = getattr( 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} return {**cond_imports, **_IS_TRUE_IMPORT}
@ -135,6 +135,8 @@ def cond(condition: Any, c1: Any, c2: Any = None) -> Component | ImmutableVar:
Raises: Raises:
ValueError: If the arguments are invalid. 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. # Convert the condition to a Var.
cond_var = LiteralVar.create(condition) cond_var = LiteralVar.create(condition)
assert cond_var is not None, "The condition must be set." 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) c2 = create_var(c2)
# Create the conditional var. # Create the conditional var.
return TernaryOperator( return TernaryOperator.create(
condition=cond_var, condition=cond_var.to(bool),
if_true=c1, if_true=c1,
if_false=c2, if_false=c2,
_var_data=VarData(imports=_IS_TRUE_IMPORT), _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 import format, types
from reflex.utils.exceptions import MatchTypeError from reflex.utils.exceptions import MatchTypeError
from reflex.utils.imports import ImportDict from reflex.utils.imports import ImportDict
from reflex.vars import ImmutableVarData, Var from reflex.vars import ImmutableVarData, Var, VarData
class Match(MemoizationLeaf): class Match(MemoizationLeaf):
@ -264,7 +264,7 @@ class Match(MemoizationLeaf):
Returns: Returns:
The import dict. The import dict.
""" """
return getattr(self.cond._var_data, "imports", {}) return getattr(VarData.merge(self.cond._get_all_var_data()), "imports", {})
match = Match.create match = Match.create

View File

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

View File

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

View File

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

View File

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

View File

@ -9,13 +9,14 @@ from jinja2 import Environment
from reflex.components.el.element import Element from reflex.components.el.element import Element
from reflex.event import EventHandler, EventSpec from reflex.event import EventHandler, EventSpec
from reflex.ivars.base import ImmutableVar
from reflex.style import Style from reflex.style import Style
from reflex.utils.imports import ImportDict from reflex.utils.imports import ImportDict
from reflex.vars import BaseVar, Var from reflex.vars import BaseVar, Var
from .base import BaseHTML 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( 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 " "\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" tag = "link"
# Specifies the CORS settings for the linked resource
cross_origin: Var[Union[str, int, bool]] cross_origin: Var[Union[str, int, bool]]
# Specifies the URL of the linked document/resource
href: Var[Union[str, int, bool]] href: Var[Union[str, int, bool]]
# Specifies the language of the text in the linked document
href_lang: Var[Union[str, int, bool]] href_lang: Var[Union[str, int, bool]]
# Allows a browser to check the fetched link for integrity
integrity: Var[Union[str, int, bool]] integrity: Var[Union[str, int, bool]]
# Specifies on what device the linked document will be displayed
media: Var[Union[str, int, bool]] media: Var[Union[str, int, bool]]
# Specifies the referrer policy of the linked document
referrer_policy: Var[Union[str, int, bool]] referrer_policy: Var[Union[str, int, bool]]
# Specifies the relationship between the current document and the linked one
rel: Var[Union[str, int, bool]] rel: Var[Union[str, int, bool]]
# Specifies the sizes of icons for visual media
sizes: Var[Union[str, int, bool]] sizes: Var[Union[str, int, bool]]
# Specifies the MIME type of the linked document
type: Var[Union[str, int, bool]] type: Var[Union[str, int, bool]]
class Meta(BaseHTML): # Inherits common attributes from BaseHTML class Meta(BaseHTML): # Inherits common attributes from BaseHTML
"""Display the meta element.""" """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]] char_set: Var[Union[str, int, bool]]
# Defines the content of the metadata
content: Var[Union[str, int, bool]] 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]] http_equiv: Var[Union[str, int, bool]]
# Specifies a name for the metadata
name: Var[Union[str, int, bool]] name: Var[Union[str, int, bool]]

View File

@ -346,6 +346,15 @@ class Link(BaseHTML):
Args: Args:
*children: The children of the component. *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. 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. 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. content_editable: Indicates whether the element's content is editable.
@ -466,6 +475,10 @@ class Meta(BaseHTML):
Args: Args:
*children: The children of the component. *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. 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. 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. 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.component import Component
from reflex.components.tags import Tag from reflex.components.tags import Tag
from reflex.ivars.base import ImmutableComputedVar
from reflex.utils import types from reflex.utils import types
from reflex.utils.imports import ImportDict from reflex.utils.imports import ImportDict
from reflex.utils.serializers import serialize 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 # The annotation should be provided if data is a computed var. We need this to know how to
# render pandas dataframes. # render pandas dataframes.
if isinstance(data, ComputedVar) and data._var_type == Any: if (
isinstance(data, (ComputedVar, ImmutableComputedVar))
and data._var_type == Any
):
raise ValueError( raise ValueError(
"Annotation of the computed var assigned to the data field should be provided." "Annotation of the computed var assigned to the data field should be provided."
) )
if ( if (
columns is not None columns is not None
and isinstance(columns, ComputedVar) and isinstance(columns, (ComputedVar, ImmutableComputedVar))
and columns._var_type == Any and columns._var_type == Any
): ):
raise ValueError( 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.link import Link
from reflex.components.radix.themes.typography.text import Text from reflex.components.radix.themes.typography.text import Text
from reflex.components.tags.tag import Tag 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 import types
from reflex.utils.imports import ImportDict, ImportVar from reflex.utils.imports import ImportDict, ImportVar
from reflex.vars import Var from reflex.vars import Var
# Special vars used in the component map. # Special vars used in the component map.
_CHILDREN = Var.create_safe("children", _var_is_local=False, _var_is_string=False) _CHILDREN = ImmutableVar.create_safe("children")
_PROPS = Var.create_safe("...props", _var_is_local=False, _var_is_string=False) _PROPS = ImmutableVar.create_safe("...props")
_MOCK_ARG = Var.create_safe("", _var_is_string=False) _MOCK_ARG = ImmutableVar.create_safe("")
# Special remark plugins. # Special remark plugins.
_REMARK_MATH = Var.create_safe("remarkMath", _var_is_local=False, _var_is_string=False) _REMARK_MATH = ImmutableVar.create_safe("remarkMath")
_REMARK_GFM = Var.create_safe("remarkGfm", _var_is_local=False, _var_is_string=False) _REMARK_GFM = ImmutableVar.create_safe("remarkGfm")
_REMARK_UNWRAP_IMAGES = Var.create_safe( _REMARK_UNWRAP_IMAGES = ImmutableVar.create_safe("remarkUnwrapImages")
"remarkUnwrapImages", _var_is_local=False, _var_is_string=False _REMARK_PLUGINS = LiteralVar.create([_REMARK_MATH, _REMARK_GFM, _REMARK_UNWRAP_IMAGES])
)
_REMARK_PLUGINS = Var.create_safe([_REMARK_MATH, _REMARK_GFM, _REMARK_UNWRAP_IMAGES])
# Special rehype plugins. # Special rehype plugins.
_REHYPE_KATEX = Var.create_safe( _REHYPE_KATEX = ImmutableVar.create_safe("rehypeKatex")
"rehypeKatex", _var_is_local=False, _var_is_string=False _REHYPE_RAW = ImmutableVar.create_safe("rehypeRaw")
) _REHYPE_PLUGINS = LiteralVar.create([_REHYPE_KATEX, _REHYPE_RAW])
_REHYPE_RAW = Var.create_safe("rehypeRaw", _var_is_local=False, _var_is_string=False)
_REHYPE_PLUGINS = Var.create_safe([_REHYPE_KATEX, _REHYPE_RAW])
# These tags do NOT get props passed to them # These tags do NOT get props passed to them
NO_PROPS_TAGS = ("ul", "ol", "li") NO_PROPS_TAGS = ("ul", "ol", "li")
@ -209,10 +205,11 @@ class Markdown(Component):
children_prop = props.pop("children", None) children_prop = props.pop("children", None)
if children_prop is not None: if children_prop is not None:
special_props.add( 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 = [] children = []
# Get the component. # Get the component.
component = self.component_map[tag](*children, **props).set( component = self.component_map[tag](*children, **props).set(
special_props=special_props special_props=special_props
@ -238,7 +235,7 @@ class Markdown(Component):
The formatted component map. The formatted component map.
""" """
components = { 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 for tag in self.component_map
} }
@ -261,7 +258,7 @@ class Markdown(Component):
return inline ? ( return inline ? (
{self.format_component("code")} {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", " ") }}}}""".replace("\n", " ")
@ -288,7 +285,7 @@ class Markdown(Component):
function {self._get_component_map_name()} () {{ function {self._get_component_map_name()} () {{
{formatted_hooks} {formatted_hooks}
return ( 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( .add_props(
remark_plugins=_REMARK_PLUGINS, remark_plugins=_REMARK_PLUGINS,
rehype_plugins=_REHYPE_PLUGINS, rehype_plugins=_REHYPE_PLUGINS,
components=ImmutableVar.create_safe(
f"{self._get_component_map_name()}()"
),
) )
.remove_props("componentMap", "componentMapHash") .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 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.components.component import Component
from reflex.event import EventHandler, EventSpec from reflex.event import EventHandler, EventSpec
from reflex.ivars.base import ImmutableVar, LiteralVar
from reflex.style import Style from reflex.style import Style
from reflex.utils.imports import ImportDict from reflex.utils.imports import ImportDict
from reflex.vars import BaseVar, Var from reflex.vars import BaseVar, Var
_CHILDREN = Var.create_safe("children", _var_is_local=False, _var_is_string=False) _CHILDREN = ImmutableVar.create_safe("children")
_PROPS = Var.create_safe("...props", _var_is_local=False, _var_is_string=False) _PROPS = ImmutableVar.create_safe("...props")
_MOCK_ARG = Var.create_safe("", _var_is_string=False) _MOCK_ARG = ImmutableVar.create_safe("")
_REMARK_MATH = Var.create_safe("remarkMath", _var_is_local=False, _var_is_string=False) _REMARK_MATH = ImmutableVar.create_safe("remarkMath")
_REMARK_GFM = Var.create_safe("remarkGfm", _var_is_local=False, _var_is_string=False) _REMARK_GFM = ImmutableVar.create_safe("remarkGfm")
_REMARK_UNWRAP_IMAGES = Var.create_safe( _REMARK_UNWRAP_IMAGES = ImmutableVar.create_safe("remarkUnwrapImages")
"remarkUnwrapImages", _var_is_local=False, _var_is_string=False _REMARK_PLUGINS = LiteralVar.create([_REMARK_MATH, _REMARK_GFM, _REMARK_UNWRAP_IMAGES])
) _REHYPE_KATEX = ImmutableVar.create_safe("rehypeKatex")
_REMARK_PLUGINS = Var.create_safe([_REMARK_MATH, _REMARK_GFM, _REMARK_UNWRAP_IMAGES]) _REHYPE_RAW = ImmutableVar.create_safe("rehypeRaw")
_REHYPE_KATEX = Var.create_safe( _REHYPE_PLUGINS = LiteralVar.create([_REHYPE_KATEX, _REHYPE_RAW])
"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])
NO_PROPS_TAGS = ("ul", "ol", "li") NO_PROPS_TAGS = ("ul", "ol", "li")
@lru_cache @lru_cache

View File

@ -206,5 +206,5 @@ class ColorModeNamespace(ImmutableVar):
color_mode = color_mode_var_and_namespace = ColorModeNamespace( color_mode = color_mode_var_and_namespace = ColorModeNamespace(
_var_name=color_mode._var_name, _var_name=color_mode._var_name,
_var_type=color_mode._var_type, _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 ---------------------- # ------------------- DO NOT EDIT ----------------------
# This file was generated by `reflex/utils/pyi_generator.py`! # 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 typing import Any, Callable, Dict, Literal, Optional, Union, get_args, overload
from reflex.components.component import BaseComponent 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.lucide.icon import Icon
from reflex.components.radix.themes.components.switch import Switch from reflex.components.radix.themes.components.switch import Switch
from reflex.event import EventHandler, EventSpec from reflex.event import EventHandler, EventSpec
from reflex.ivars.base import ImmutableVar
from reflex.style import ( from reflex.style import (
Style, Style,
color_mode, color_mode,
@ -533,11 +533,13 @@ class ColorModeSwitch(Switch):
""" """
... ...
class ColorModeNamespace(BaseVar): class ColorModeNamespace(ImmutableVar):
icon = staticmethod(ColorModeIcon.create) icon = staticmethod(ColorModeIcon.create)
button = staticmethod(ColorModeIconButton.create) button = staticmethod(ColorModeIconButton.create)
switch = staticmethod(ColorModeSwitch.create) switch = staticmethod(ColorModeSwitch.create)
color_mode = color_mode_var_and_namespace = ColorModeNamespace( 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.layout.flex import Flex
from reflex.components.radix.themes.typography.text import Text from reflex.components.radix.themes.typography.text import Text
from reflex.event import EventHandler from reflex.event import EventHandler
from reflex.ivars.base import LiteralVar
from reflex.vars import Var from reflex.vars import Var
from ..base import ( from ..base import (
@ -115,9 +116,7 @@ class HighLevelCheckbox(RadixThemesComponent):
on_change: EventHandler[lambda e0: [e0]] on_change: EventHandler[lambda e0: [e0]]
@classmethod @classmethod
def create( def create(cls, text: Var[str] = LiteralVar.create(""), **props) -> Component:
cls, text: Var[str] = Var.create_safe("", _var_is_string=True), **props
) -> Component:
"""Create a checkbox with a label. """Create a checkbox with a label.
Args: 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.components.radix.themes.typography.text import Text
from reflex.event import EventHandler from reflex.event import EventHandler
from reflex.ivars.base import ImmutableVar, LiteralVar from reflex.ivars.base import ImmutableVar, LiteralVar
from reflex.ivars.function import JSON_STRINGIFY
from reflex.ivars.sequence import StringVar from reflex.ivars.sequence import StringVar
from reflex.vars import Var from reflex.vars import Var
@ -30,14 +29,10 @@ class RadioGroupRoot(RadixThemesComponent):
tag = "RadioGroup.Root" tag = "RadioGroup.Root"
# The size of the radio group: "1" | "2" | "3" # The size of the radio group: "1" | "2" | "3"
size: Var[Responsive[Literal["1", "2", "3"]]] = Var.create_safe( size: Var[Responsive[Literal["1", "2", "3"]]] = LiteralVar.create("2")
"2", _var_is_string=True
)
# The variant of the radio group # The variant of the radio group
variant: Var[Literal["classic", "surface", "soft"]] = Var.create_safe( variant: Var[Literal["classic", "surface", "soft"]] = LiteralVar.create("classic")
"classic", _var_is_string=True
)
# The color of the radio group # The color of the radio group
color_scheme: Var[LiteralAccentColor] color_scheme: Var[LiteralAccentColor]
@ -89,20 +84,16 @@ class HighLevelRadioGroup(RadixThemesComponent):
items: Var[List[str]] items: Var[List[str]]
# The direction of the radio group. # The direction of the radio group.
direction: Var[LiteralFlexDirection] = Var.create_safe( direction: Var[LiteralFlexDirection] = LiteralVar.create("column")
"column", _var_is_string=True
)
# The gap between the items of the radio group. # 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. # 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 # The variant of the radio group
variant: Var[Literal["classic", "surface", "soft"]] = Var.create_safe( variant: Var[Literal["classic", "surface", "soft"]] = LiteralVar.create("classic")
"classic", _var_is_string=True
)
# The color of the radio group # The color of the radio group
color_scheme: Var[LiteralAccentColor] color_scheme: Var[LiteralAccentColor]
@ -159,13 +150,13 @@ class HighLevelRadioGroup(RadixThemesComponent):
): ):
default_value = LiteralVar.create(default_value) # type: ignore default_value = LiteralVar.create(default_value) # type: ignore
else: 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: def radio_group_item(value: Var) -> Component:
item_value = rx.cond( item_value = rx.cond(
value._type() == "string", value._type() == "string",
value, value,
JSON_STRINGIFY.call(value), value.to_string(),
).to(StringVar) ).to(StringVar)
return Text.create( return Text.create(

View File

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

View File

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

View File

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

View File

@ -7,6 +7,7 @@ from typing import Any, Dict, List, Union
from reflex.constants import EventTriggers from reflex.constants import EventTriggers
from reflex.constants.colors import Color from reflex.constants.colors import Color
from reflex.event import EventHandler from reflex.event import EventHandler
from reflex.ivars.base import LiteralVar
from reflex.vars import Var from reflex.vars import Var
from .recharts import ( from .recharts import (
@ -86,7 +87,7 @@ class Axis(Recharts):
tick_count: Var[int] tick_count: Var[int]
# If set false, no axis tick lines will be drawn. # 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. # The length of tick line.
tick_size: Var[int] tick_size: Var[int]
@ -95,7 +96,7 @@ class Axis(Recharts):
min_tick_gap: Var[int] min_tick_gap: Var[int]
# The stroke color of axis # 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 # The text anchor of axis
text_anchor: Var[str] # 'start', 'middle', 'end' text_anchor: Var[str] # 'start', 'middle', 'end'
@ -136,7 +137,7 @@ class XAxis(Axis):
x_axis_id: Var[Union[str, int]] x_axis_id: Var[Union[str, int]]
# Ensures that all datapoints within a chart contribute to its domain calculation, even when they are hidden # 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): class YAxis(Axis):
@ -187,10 +188,10 @@ class Brush(Recharts):
alias = "RechartsBrush" alias = "RechartsBrush"
# Stroke color # 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. # 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. # The key of data displayed in the axis.
data_key: Var[Union[str, int]] data_key: Var[Union[str, int]]
@ -290,22 +291,22 @@ class Area(Cartesian):
alias = "RechartsArea" alias = "RechartsArea"
# The color of the line stroke. # 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. # 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. # 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' | # 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. # 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]]] 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. # 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), "stroke": Color("accent", 2),
"fill": Color("accent", 10), "fill": Color("accent", 10),
@ -342,7 +343,7 @@ class Bar(Cartesian):
stroke_width: Var[int] stroke_width: Var[int]
# The width of the line stroke. # 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. # 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] background: Var[bool]
@ -403,13 +404,13 @@ class Line(Cartesian):
type_: Var[LiteralAreaType] type_: Var[LiteralAreaType]
# The color of the line stroke. # 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. # The width of the line stroke.
stroke_width: Var[int] 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. # 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), "stroke": Color("accent", 10),
"fill": Color("accent", 4), "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. # 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), "stroke": Color("accent", 2),
"fill": Color("accent", 10), "fill": Color("accent", 10),
@ -475,7 +476,7 @@ class Scatter(Recharts):
line_type: Var[LiteralLineType] line_type: Var[LiteralLineType]
# The fill # 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 # the name
name: Var[Union[str, int]] name: Var[Union[str, int]]
@ -552,7 +553,7 @@ class Funnel(Recharts):
animation_easing: Var[LiteralAnimationEasing] animation_easing: Var[LiteralAnimationEasing]
# stroke color # 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 components
_valid_children: List[str] = ["LabelList", "Cell"] _valid_children: List[str] = ["LabelList", "Cell"]
@ -605,7 +606,7 @@ class ErrorBar(Recharts):
width: Var[int] width: Var[int]
# The stroke color of error bar. # 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. # The stroke width of error bar.
stroke_width: Var[int] stroke_width: Var[int]
@ -795,7 +796,7 @@ class CartesianGrid(Grid):
stroke_dasharray: Var[str] stroke_dasharray: Var[str]
# the stroke color of grid # 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): class CartesianAxis(Grid):

View File

@ -9,6 +9,7 @@ from reflex.components.recharts.general import ResponsiveContainer
from reflex.constants import EventTriggers from reflex.constants import EventTriggers
from reflex.constants.colors import Color from reflex.constants.colors import Color
from reflex.event import EventHandler from reflex.event import EventHandler
from reflex.ivars.base import LiteralVar
from reflex.vars import Var from reflex.vars import Var
from .recharts import ( from .recharts import (
@ -156,10 +157,10 @@ class BarChart(CategoricalChartBase):
alias = "RechartsBarChart" alias = "RechartsBarChart"
# The gap between two bar categories, which can be a percent value or a fixed value. Percentage | Number # 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 # 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 # The width of all the bars in the chart. Number
bar_size: Var[int] 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.components.component import MemoizationLeaf
from reflex.constants.colors import Color from reflex.constants.colors import Color
from reflex.event import EventHandler from reflex.event import EventHandler
from reflex.ivars.base import LiteralVar
from reflex.vars import Var from reflex.vars import Var
from .recharts import ( from .recharts import (
@ -139,7 +140,7 @@ class GraphingTooltip(Recharts):
filter_null: Var[bool] filter_null: Var[bool]
# If set false, no cursor will be drawn when tooltip is active. # 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, "strokeWidth": 1,
"fill": Color("gray", 3), "fill": Color("gray", 3),
@ -150,7 +151,7 @@ class GraphingTooltip(Recharts):
view_box: Var[Dict[str, Any]] view_box: Var[Dict[str, Any]]
# The style of default tooltip content item which is a li element. DEFAULT: {} # 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), "color": Color("gray", 12),
} }
@ -159,7 +160,7 @@ class GraphingTooltip(Recharts):
# The style of tooltip wrapper which is a dom element. DEFAULT: {} # The style of tooltip wrapper which is a dom element. DEFAULT: {}
wrapper_style: Var[Dict[str, Any]] wrapper_style: Var[Dict[str, Any]]
# The style of tooltip content which is a dom element. DEFAULT: {} # 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), "background": Color("gray", 1),
"borderColor": Color("gray", 4), "borderColor": Color("gray", 4),
@ -168,10 +169,10 @@ class GraphingTooltip(Recharts):
) )
# The style of default tooltip label which is a p element. DEFAULT: {} # 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 } # 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} {"x": False, "y": False}
) )
@ -231,10 +232,10 @@ class LabelList(Recharts):
offset: Var[int] offset: Var[int]
# The fill color of each label # 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 # 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 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 import EventTriggers
from reflex.constants.colors import Color from reflex.constants.colors import Color
from reflex.event import EventHandler from reflex.event import EventHandler
from reflex.ivars.base import LiteralVar
from reflex.vars import Var from reflex.vars import Var
from .recharts import ( from .recharts import (
@ -72,10 +73,10 @@ class Pie(Recharts):
_valid_children: List[str] = ["Cell", "LabelList"] _valid_children: List[str] = ["Cell", "LabelList"]
# Stoke color # 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 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]]: def get_event_triggers(self) -> dict[str, Union[Var, Any]]:
"""Get the event triggers that pass the component's value to the handler. """Get the event triggers that pass the component's value to the handler.
@ -110,13 +111,13 @@ class Radar(Recharts):
dot: Var[bool] dot: Var[bool]
# Stoke color # 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 color
fill: Var[str] = Var.create_safe(Color("accent", 3)) fill: Var[str] = LiteralVar.create(Color("accent", 3))
# opacity # 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. # The type of icon in legend. If set to 'none', no legend item will be rendered.
legend_type: Var[str] legend_type: Var[str]
@ -218,7 +219,7 @@ class PolarAngleAxis(Recharts):
axis_line_type: Var[str] 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. # 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. # The width or height of tick.
tick: Var[Union[int, str]] tick: Var[Union[int, str]]
@ -230,7 +231,7 @@ class PolarAngleAxis(Recharts):
orient: Var[str] orient: Var[str]
# The stroke color of axis # 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 the axis has duplicated categorys or not when the type of axis is "category".
allow_duplicated_category: Var[bool] allow_duplicated_category: Var[bool]
@ -292,7 +293,7 @@ class PolarGrid(Recharts):
grid_type: Var[LiteralGridType] grid_type: Var[LiteralGridType]
# The stroke color of grid # 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 components
_valid_children: List[str] = ["RadarChart", "RadiarBarChart"] _valid_children: List[str] = ["RadarChart", "RadiarBarChart"]
@ -342,10 +343,10 @@ class PolarRadiusAxis(Recharts):
_valid_children: List[str] = ["Label"] _valid_children: List[str] = ["Label"]
# The domain of the polar radius axis, specifying the minimum and maximum values. # 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 # 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]]: def get_event_triggers(self) -> dict[str, Union[Var, Any]]:
"""Get the event triggers that pass the component's value to the handler. """Get the event triggers that pass the component's value to the handler.

View File

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

View File

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

View File

@ -2,10 +2,15 @@
from __future__ import annotations from __future__ import annotations
import contextlib
import dataclasses import dataclasses
import datetime
import dis
import functools import functools
import inspect import inspect
import json
import sys import sys
from types import CodeType, FunctionType
from typing import ( from typing import (
TYPE_CHECKING, TYPE_CHECKING,
Any, Any,
@ -20,17 +25,22 @@ from typing import (
Type, Type,
TypeVar, TypeVar,
Union, Union,
cast,
get_args, get_args,
overload, 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 import constants
from reflex.base import Base from reflex.base import Base
from reflex.constants.colors import Color
from reflex.utils import console, imports, serializers, types 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 ( from reflex.vars import (
ComputedVar,
ImmutableVarData, ImmutableVarData,
Var, Var,
VarData, VarData,
@ -320,11 +330,15 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
@overload @overload
def to( 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: ... ) -> OUTPUT: ...
def to( 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: ) -> Var:
"""Convert the var to a different type. """Convert the var to a different type.
@ -338,12 +352,15 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns: Returns:
The converted var. The converted var.
""" """
from .function import FunctionVar, ToFunctionOperation
from .number import ( from .number import (
BooleanVar, BooleanVar,
NumberVar, NumberVar,
ToBooleanVarOperation, ToBooleanVarOperation,
ToNumberVarOperation, ToNumberVarOperation,
) )
from .object import ObjectVar, ToObjectOperation
from .sequence import ArrayVar, StringVar, ToArrayOperation, ToStringOperation
fixed_type = ( fixed_type = (
var_type var_type
@ -351,16 +368,28 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
else get_origin(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 issubclass(output, NumberVar):
if fixed_type is not None and not issubclass(fixed_type, (int, float)): if fixed_type is not None and not issubclass(fixed_type, (int, float)):
raise TypeError( raise TypeError(
f"Unsupported type {var_type} for NumberVar. Must be int or float." f"Unsupported type {var_type} for NumberVar. Must be int or float."
) )
return ToNumberVarOperation(self, var_type or float) return ToNumberVarOperation.create(self, var_type or float)
if issubclass(output, BooleanVar):
return ToBooleanVarOperation(self)
from .sequence import ArrayVar, StringVar, ToArrayOperation, ToStringOperation if issubclass(output, BooleanVar):
return ToBooleanVarOperation.create(self)
if issubclass(output, ArrayVar): if issubclass(output, ArrayVar):
if fixed_type is not None and not issubclass( if fixed_type is not None and not issubclass(
@ -369,28 +398,30 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
raise TypeError( raise TypeError(
f"Unsupported type {var_type} for ArrayVar. Must be list, tuple, or set." 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): if issubclass(output, StringVar):
return ToStringOperation(self) return ToStringOperation.create(self)
from .object import ObjectVar, ToObjectOperation if issubclass(output, (ObjectVar, Base)):
return ToObjectOperation.create(self, var_type or dict)
if issubclass(output, ObjectVar):
return ToObjectOperation(self, var_type or dict)
from .function import FunctionVar, ToFunctionOperation
if issubclass(output, FunctionVar): if issubclass(output, FunctionVar):
# if fixed_type is not None and not issubclass(fixed_type, Callable): # if fixed_type is not None and not issubclass(fixed_type, Callable):
# raise TypeError( # raise TypeError(
# f"Unsupported type {var_type} for FunctionVar. Must be Callable." # 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( if not issubclass(output, Var) and var_type is None:
_var_name=self._var_name, return dataclasses.replace(
_var_type=self._var_type if var_type is None else var_type, self,
_var_data=self._var_data, _var_type=output,
)
return dataclasses.replace(
self,
_var_type=var_type,
) )
def guess_type(self) -> ImmutableVar: def guess_type(self) -> ImmutableVar:
@ -413,6 +444,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
if fixed_type is Union: if fixed_type is Union:
return self return self
if not inspect.isclass(fixed_type):
raise TypeError(f"Unsupported type {var_type} for guess_type.")
if issubclass(fixed_type, (int, float)): if issubclass(fixed_type, (int, float)):
return self.to(NumberVar, var_type) return self.to(NumberVar, var_type)
if issubclass(fixed_type, dict): if issubclass(fixed_type, dict):
@ -477,12 +511,12 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns: Returns:
The name of the setter function. 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: if self._var_data is None:
return setter return setter
if not include_state or self._var_data.state == "": if not include_state or self._var_data.state == "":
return setter return setter
print("get_setter_name", self._var_data.state, setter)
return ".".join((self._var_data.state, setter)) return ".".join((self._var_data.state, setter))
def get_setter(self) -> Callable[[BaseState, Any], None]: def get_setter(self) -> Callable[[BaseState, Any], None]:
@ -491,6 +525,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns: Returns:
A function that that creates a setter for the var. A function that that creates a setter for the var.
""" """
actual_name = self._var_name.split(".")[-1]
def setter(state: BaseState, value: Any): def setter(state: BaseState, value: Any):
"""Get the setter for the var. """Get the setter for the var.
@ -502,13 +537,13 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
if self._var_type in [int, float]: if self._var_type in [int, float]:
try: try:
value = self._var_type(value) value = self._var_type(value)
setattr(state, self._var_name, value) setattr(state, actual_name, value)
except ValueError: except ValueError:
console.debug( console.debug(
f"{type(state).__name__}.{self._var_name}: Failed conversion of {value} to '{self._var_type.__name__}'. Value not set.", f"{type(state).__name__}.{self._var_name}: Failed conversion of {value} to '{self._var_type.__name__}'. Value not set.",
) )
else: else:
setattr(state, self._var_name, value) setattr(state, actual_name, value)
setter.__qualname__ = self.get_setter_name() setter.__qualname__ = self.get_setter_name()
@ -525,7 +560,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
""" """
from .number import EqualOperation from .number import EqualOperation
return EqualOperation(self, other) return EqualOperation.create(self, other)
def __ne__(self, other: Var | Any) -> BooleanVar: def __ne__(self, other: Var | Any) -> BooleanVar:
"""Check if the current object is not equal to the given object. """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 from .number import EqualOperation
return ~EqualOperation(self, other) return ~EqualOperation.create(self, other)
def __gt__(self, other: Var | Any) -> BooleanVar: 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. """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 from .number import GreaterThanOperation
return GreaterThanOperation(self, other) return GreaterThanOperation.create(self, other)
def __ge__(self, other: Var | Any) -> BooleanVar: 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. """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 from .number import GreaterThanOrEqualOperation
return GreaterThanOrEqualOperation(self, other) return GreaterThanOrEqualOperation.create(self, other)
def __lt__(self, other: Var | Any) -> BooleanVar: def __lt__(self, other: Var | Any) -> BooleanVar:
"""Compare the current instance with another variable using the less than (<) operator. """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 from .number import LessThanOperation
return LessThanOperation(self, other) return LessThanOperation.create(self, other)
def __le__(self, other: Var | Any) -> BooleanVar: def __le__(self, other: Var | Any) -> BooleanVar:
"""Compare if the current instance is less than or equal to the given value. """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 from .number import LessThanOrEqualOperation
return LessThanOrEqualOperation(self, other) return LessThanOrEqualOperation.create(self, other)
def bool(self) -> BooleanVar: def bool(self) -> BooleanVar:
"""Convert the var to a boolean. """Convert the var to a boolean.
@ -600,7 +635,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
""" """
from .number import ToBooleanVarOperation from .number import ToBooleanVarOperation
return ToBooleanVarOperation(self) return ToBooleanVarOperation.create(self)
def __and__(self, other: Var | Any) -> ImmutableVar: def __and__(self, other: Var | Any) -> ImmutableVar:
"""Perform a logical AND operation on the current instance and another variable. """Perform a logical AND operation on the current instance and another variable.
@ -611,7 +646,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns: Returns:
A `BooleanVar` object representing the result of the logical AND operation. 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: def __rand__(self, other: Var | Any) -> ImmutableVar:
"""Perform a logical AND operation on the current instance and another variable. """Perform a logical AND operation on the current instance and another variable.
@ -622,7 +657,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns: Returns:
A `BooleanVar` object representing the result of the logical AND operation. 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: def __or__(self, other: Var | Any) -> ImmutableVar:
"""Perform a logical OR operation on the current instance and another variable. """Perform a logical OR operation on the current instance and another variable.
@ -633,7 +668,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns: Returns:
A `BooleanVar` object representing the result of the logical OR operation. 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: def __ror__(self, other: Var | Any) -> ImmutableVar:
"""Perform a logical OR operation on the current instance and another variable. """Perform a logical OR operation on the current instance and another variable.
@ -644,7 +679,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns: Returns:
A `BooleanVar` object representing the result of the logical OR operation. A `BooleanVar` object representing the result of the logical OR operation.
""" """
return OrOperation(other, self) return OrOperation.create(other, self)
def __invert__(self) -> BooleanVar: def __invert__(self) -> BooleanVar:
"""Perform a logical NOT operation on the current instance. """Perform a logical NOT operation on the current instance.
@ -654,7 +689,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
""" """
from .number import BooleanNotOperation from .number import BooleanNotOperation
return BooleanNotOperation(self.bool()) return BooleanNotOperation.create(self.bool())
def to_string(self) -> ImmutableVar: def to_string(self) -> ImmutableVar:
"""Convert the var to a string. """Convert the var to a string.
@ -663,8 +698,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
The string var. The string var.
""" """
from .function import JSON_STRINGIFY 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: def as_ref(self) -> ImmutableVar:
"""Get a reference to the var. """Get a reference to the var.
@ -732,35 +768,97 @@ class LiteralVar(ImmutableVar):
if value is None: if value is None:
return ImmutableVar.create_safe("null", _var_data=_var_data) 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 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): if isinstance(value, Base):
return LiteralObjectVar( return LiteralObjectVar.create(
value.dict(), _var_type=type(value), _var_data=_var_data value.dict(), _var_type=type(value), _var_data=_var_data
) )
if isinstance(value, dict): 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 from .sequence import LiteralArrayVar, LiteralStringVar
if isinstance(value, str): if isinstance(value, str):
return LiteralStringVar.create(value, _var_data=_var_data) 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 = { type_mapping = {
int: LiteralNumberVar, int: LiteralNumberVar.create,
float: LiteralNumberVar, float: LiteralNumberVar.create,
bool: LiteralBooleanVar, bool: LiteralBooleanVar.create,
list: LiteralArrayVar, list: LiteralArrayVar.create,
tuple: LiteralArrayVar, tuple: LiteralArrayVar.create,
set: LiteralArrayVar, set: LiteralArrayVar.create,
} }
constructor = type_mapping.get(type(value)) constructor = type_mapping.get(type(value))
if constructor is None: 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) return constructor(value, _var_data=_var_data)
@ -881,27 +979,8 @@ class AndOperation(ImmutableVar):
# The second var. # The second var.
_var2: Var = dataclasses.field(default_factory=lambda: LiteralVar.create(None)) _var2: Var = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
def __init__( def __post_init__(self):
self, var1: Var | Any, var2: Var | Any, _var_data: VarData | None = None """Post-initialize the AndOperation."""
):
"""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)
)
object.__delattr__(self, "_var_name") object.__delattr__(self, "_var_name")
@functools.cached_property @functools.cached_property
@ -955,6 +1034,29 @@ class AndOperation(ImmutableVar):
""" """
return hash((self.__class__.__name__, self._var1, self._var2)) 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( @dataclasses.dataclass(
eq=False, eq=False,
@ -970,27 +1072,8 @@ class OrOperation(ImmutableVar):
# The second var. # The second var.
_var2: Var = dataclasses.field(default_factory=lambda: LiteralVar.create(None)) _var2: Var = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
def __init__( def __post_init__(self):
self, var1: Var | Any, var2: Var | Any, _var_data: VarData | None = None """Post-initialize the OrOperation."""
):
"""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)
)
object.__delattr__(self, "_var_name") object.__delattr__(self, "_var_name")
@functools.cached_property @functools.cached_property
@ -1044,6 +1127,29 @@ class OrOperation(ImmutableVar):
""" """
return hash((self.__class__.__name__, self._var1, self._var2)) 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( @dataclasses.dataclass(
eq=False, eq=False,
@ -1057,14 +1163,14 @@ class ImmutableCallableVar(ImmutableVar):
API with functions that return a family of Var. API with functions that return a family of Var.
""" """
fn: Callable[..., ImmutableVar] = dataclasses.field( fn: Callable[..., Var] = dataclasses.field(
default_factory=lambda: lambda: LiteralVar.create(None) default_factory=lambda: lambda: ImmutableVar(_var_name="undefined")
) )
original_var: ImmutableVar = dataclasses.field( original_var: Var = dataclasses.field(
default_factory=lambda: LiteralVar.create(None) default_factory=lambda: ImmutableVar(_var_name="undefined")
) )
def __init__(self, fn: Callable[..., ImmutableVar]): def __init__(self, fn: Callable[..., Var]):
"""Initialize a CallableVar. """Initialize a CallableVar.
Args: Args:
@ -1074,12 +1180,12 @@ class ImmutableCallableVar(ImmutableVar):
super(ImmutableCallableVar, self).__init__( super(ImmutableCallableVar, self).__init__(
_var_name=original_var._var_name, _var_name=original_var._var_name,
_var_type=original_var._var_type, _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, "fn", fn)
object.__setattr__(self, "original_var", original_var) object.__setattr__(self, "original_var", original_var)
def __call__(self, *args, **kwargs) -> ImmutableVar: def __call__(self, *args, **kwargs) -> Var:
"""Call the decorated function. """Call the decorated function.
Args: Args:
@ -1098,3 +1204,433 @@ class ImmutableCallableVar(ImmutableVar):
The hash of the object. The hash of the object.
""" """
return hash((self.__class__.__name__, self.original_var)) 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 functools import cached_property
from typing import Any, Callable, Optional, Tuple, Type, Union from typing import Any, Callable, Optional, Tuple, Type, Union
from reflex.utils.types import GenericType
from reflex.vars import ImmutableVarData, Var, VarData from reflex.vars import ImmutableVarData, Var, VarData
from .base import ImmutableVar, LiteralVar from .base import ImmutableVar, LiteralVar
@ -24,9 +25,9 @@ class FunctionVar(ImmutableVar[Callable]):
Returns: Returns:
The function call operation. The function call operation.
""" """
return ArgsFunctionOperation( return ArgsFunctionOperation.create(
("...args",), ("...args",),
VarOperationCall(self, *args, ImmutableVar.create_safe("...args")), VarOperationCall.create(self, *args, ImmutableVar.create_safe("...args")),
) )
def call(self, *args: Var | Any) -> VarOperationCall: def call(self, *args: Var | Any) -> VarOperationCall:
@ -38,22 +39,31 @@ class FunctionVar(ImmutableVar[Callable]):
Returns: Returns:
The function call operation. The function call operation.
""" """
return VarOperationCall(self, *args) return VarOperationCall.create(self, *args)
class FunctionStringVar(FunctionVar): class FunctionStringVar(FunctionVar):
"""Base class for immutable function vars from a string.""" """Base class for immutable function vars from a string."""
def __init__(self, func: str, _var_data: VarData | None = None) -> None: @classmethod
"""Initialize the function var. 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: Args:
func: The function to call. func: The function to call.
_var_data: Additional hooks and imports associated with the Var. _var_data: Additional hooks and imports associated with the Var.
Returns:
The function var.
""" """
super(FunctionVar, self).__init__( return cls(
_var_name=func, _var_name=func,
_var_type=Callable, _var_type=_var_type,
_var_data=ImmutableVarData.merge(_var_data), _var_data=ImmutableVarData.merge(_var_data),
) )
@ -69,25 +79,6 @@ class VarOperationCall(ImmutableVar):
_func: Optional[FunctionVar] = dataclasses.field(default=None) _func: Optional[FunctionVar] = dataclasses.field(default=None)
_args: Tuple[Union[Var, Any], ...] = dataclasses.field(default_factory=tuple) _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): def __getattr__(self, name):
"""Get an attribute of the var. """Get an attribute of the var.
@ -133,7 +124,7 @@ class VarOperationCall(ImmutableVar):
def __post_init__(self): def __post_init__(self):
"""Post-initialize the var.""" """Post-initialize the var."""
pass object.__delattr__(self, "_var_name")
def __hash__(self): def __hash__(self):
"""Hash the var. """Hash the var.
@ -143,6 +134,32 @@ class VarOperationCall(ImmutableVar):
""" """
return hash((self.__class__.__name__, self._func, self._args)) 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( @dataclasses.dataclass(
eq=False, eq=False,
@ -155,28 +172,6 @@ class ArgsFunctionOperation(FunctionVar):
_args_names: Tuple[str, ...] = dataclasses.field(default_factory=tuple) _args_names: Tuple[str, ...] = dataclasses.field(default_factory=tuple)
_return_expr: Union[Var, Any] = dataclasses.field(default=None) _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): def __getattr__(self, name):
"""Get an attribute of the var. """Get an attribute of the var.
@ -221,6 +216,7 @@ class ArgsFunctionOperation(FunctionVar):
def __post_init__(self): def __post_init__(self):
"""Post-initialize the var.""" """Post-initialize the var."""
object.__delattr__(self, "_var_name")
def __hash__(self): def __hash__(self):
"""Hash the var. """Hash the var.
@ -230,6 +226,32 @@ class ArgsFunctionOperation(FunctionVar):
""" """
return hash((self.__class__.__name__, self._args_names, self._return_expr)) 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( @dataclasses.dataclass(
eq=False, eq=False,
@ -243,25 +265,8 @@ class ToFunctionOperation(FunctionVar):
default_factory=lambda: LiteralVar.create(None) default_factory=lambda: LiteralVar.create(None)
) )
def __init__( def __post_init__(self):
self, """Post-initialize the var."""
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)
object.__delattr__(self, "_var_name") object.__delattr__(self, "_var_name")
def __getattr__(self, name): def __getattr__(self, name):
@ -314,5 +319,29 @@ class ToFunctionOperation(FunctionVar):
""" """
return hash((self.__class__.__name__, self._original_var)) 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 .number import BooleanVar, NumberVar
from .sequence import ArrayVar, StringVar from .sequence import ArrayVar, StringVar
OBJECT_TYPE = TypeVar("OBJECT_TYPE") OBJECT_TYPE = TypeVar("OBJECT_TYPE", bound=Dict)
KEY_TYPE = TypeVar("KEY_TYPE") KEY_TYPE = TypeVar("KEY_TYPE")
VALUE_TYPE = TypeVar("VALUE_TYPE") VALUE_TYPE = TypeVar("VALUE_TYPE")
@ -79,7 +79,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
Returns: Returns:
The keys of the object. The keys of the object.
""" """
return ObjectKeysOperation(self) return ObjectKeysOperation.create(self)
@overload @overload
def values( def values(
@ -95,7 +95,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
Returns: Returns:
The values of the object. The values of the object.
""" """
return ObjectValuesOperation(self) return ObjectValuesOperation.create(self)
@overload @overload
def entries( def entries(
@ -111,7 +111,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
Returns: Returns:
The entries of the object. The entries of the object.
""" """
return ObjectEntriesOperation(self) return ObjectEntriesOperation.create(self)
def merge(self, other: ObjectVar) -> ObjectMergeOperation: def merge(self, other: ObjectVar) -> ObjectMergeOperation:
"""Merge two objects. """Merge two objects.
@ -122,7 +122,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
Returns: Returns:
The merged object. The merged object.
""" """
return ObjectMergeOperation(self, other) return ObjectMergeOperation.create(self, other)
# NoReturn is used here to catch when key value is Any # NoReturn is used here to catch when key value is Any
@overload @overload
@ -180,7 +180,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
Returns: Returns:
The item from the object. 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 # NoReturn is used here to catch when key value is Any
@overload @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"The State var `{self._var_name}` has no attribute '{name}' or may have been annotated "
f"wrongly." f"wrongly."
) )
return ObjectItemOperation(self, name, attribute_type).guess_type() return ObjectItemOperation.create(self, name, attribute_type).guess_type()
else: else:
return ObjectItemOperation(self, name).guess_type() return ObjectItemOperation.create(self, name).guess_type()
def contains(self, key: Var | Any) -> BooleanVar: def contains(self, key: Var | Any) -> BooleanVar:
"""Check if the object contains a key. """Check if the object contains a key.
@ -266,7 +266,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
Returns: Returns:
The result of the check. The result of the check.
""" """
return ObjectHasOwnProperty(self, key) return ObjectHasOwnProperty.create(self, key)
@dataclasses.dataclass( @dataclasses.dataclass(
@ -281,29 +281,8 @@ class LiteralObjectVar(LiteralVar, ObjectVar[OBJECT_TYPE]):
default_factory=dict default_factory=dict
) )
def __init__( def __post_init__(self):
self: LiteralObjectVar[OBJECT_TYPE], """Post initialization."""
_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,
)
object.__delattr__(self, "_var_name") object.__delattr__(self, "_var_name")
def _key_type(self) -> Type: def _key_type(self) -> Type:
@ -409,6 +388,30 @@ class LiteralObjectVar(LiteralVar, ObjectVar[OBJECT_TYPE]):
""" """
return hash((self.__class__.__name__, self._var_name)) 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( @dataclasses.dataclass(
eq=False, eq=False,
@ -418,26 +421,12 @@ class LiteralObjectVar(LiteralVar, ObjectVar[OBJECT_TYPE]):
class ObjectToArrayOperation(ArrayVar): class ObjectToArrayOperation(ArrayVar):
"""Base class for object to array operations.""" """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__( def __post_init__(self):
self, """Post initialization."""
_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)
object.__delattr__(self, "_var_name") object.__delattr__(self, "_var_name")
@cached_property @cached_property
@ -472,7 +461,7 @@ class ObjectToArrayOperation(ArrayVar):
The VarData of the components and all of its children. The VarData of the components and all of its children.
""" """
return ImmutableVarData.merge( return ImmutableVarData.merge(
self.value._get_all_var_data(), self._value._get_all_var_data(),
self._var_data, self._var_data,
) )
@ -490,26 +479,37 @@ class ObjectToArrayOperation(ArrayVar):
Returns: Returns:
The hash of the operation. 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): class ObjectKeysOperation(ObjectToArrayOperation):
"""Operation to get the keys of an object.""" """Operation to get the keys of an object."""
def __init__( # value, List[value._key_type()], _var_data
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
)
@cached_property @cached_property
def _cached_var_name(self) -> str: def _cached_var_name(self) -> str:
@ -518,27 +518,34 @@ class ObjectKeysOperation(ObjectToArrayOperation):
Returns: Returns:
The name of the operation. 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): class ObjectValuesOperation(ObjectToArrayOperation):
"""Operation to get the values of an object.""" """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 @cached_property
def _cached_var_name(self) -> str: def _cached_var_name(self) -> str:
"""The name of the operation. """The name of the operation.
@ -546,27 +553,34 @@ class ObjectValuesOperation(ObjectToArrayOperation):
Returns: Returns:
The name of the operation. 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): class ObjectEntriesOperation(ObjectToArrayOperation):
"""Operation to get the entries of an object.""" """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 @cached_property
def _cached_var_name(self) -> str: def _cached_var_name(self) -> str:
"""The name of the operation. """The name of the operation.
@ -574,7 +588,29 @@ class ObjectEntriesOperation(ObjectToArrayOperation):
Returns: Returns:
The name of the operation. 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( @dataclasses.dataclass(
@ -585,30 +621,12 @@ class ObjectEntriesOperation(ObjectToArrayOperation):
class ObjectMergeOperation(ObjectVar): class ObjectMergeOperation(ObjectVar):
"""Operation to merge two objects.""" """Operation to merge two objects."""
left: ObjectVar = dataclasses.field(default_factory=lambda: LiteralObjectVar({})) _lhs: ObjectVar = dataclasses.field(
right: ObjectVar = dataclasses.field(default_factory=lambda: LiteralObjectVar({})) default_factory=lambda: LiteralObjectVar.create({})
)
def __init__( _rhs: ObjectVar = dataclasses.field(
self, default_factory=lambda: LiteralObjectVar.create({})
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")
@cached_property @cached_property
def _cached_var_name(self) -> str: def _cached_var_name(self) -> str:
@ -617,7 +635,7 @@ class ObjectMergeOperation(ObjectVar):
Returns: Returns:
The name of the operation. 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): def __getattr__(self, name):
"""Get an attribute of the operation. """Get an attribute of the operation.
@ -640,8 +658,8 @@ class ObjectMergeOperation(ObjectVar):
The VarData of the components and all of its children. The VarData of the components and all of its children.
""" """
return ImmutableVarData.merge( return ImmutableVarData.merge(
self.left._get_all_var_data(), self._lhs._get_all_var_data(),
self.right._get_all_var_data(), self._rhs._get_all_var_data(),
self._var_data, self._var_data,
) )
@ -659,7 +677,33 @@ class ObjectMergeOperation(ObjectVar):
Returns: Returns:
The hash of the operation. 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( @dataclasses.dataclass(
@ -670,33 +714,10 @@ class ObjectMergeOperation(ObjectVar):
class ObjectItemOperation(ImmutableVar): class ObjectItemOperation(ImmutableVar):
"""Operation to get an item from an object.""" """Operation to get an item from an object."""
value: ObjectVar = dataclasses.field(default_factory=lambda: LiteralObjectVar({})) _object: ObjectVar = dataclasses.field(
key: Var | Any = dataclasses.field(default_factory=lambda: LiteralVar.create(None)) default_factory=lambda: LiteralObjectVar.create({})
)
def __init__( _key: Var | Any = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
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")
@cached_property @cached_property
def _cached_var_name(self) -> str: def _cached_var_name(self) -> str:
@ -705,7 +726,7 @@ class ObjectItemOperation(ImmutableVar):
Returns: Returns:
The name of the operation. 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): def __getattr__(self, name):
"""Get an attribute of the operation. """Get an attribute of the operation.
@ -728,8 +749,8 @@ class ObjectItemOperation(ImmutableVar):
The VarData of the components and all of its children. The VarData of the components and all of its children.
""" """
return ImmutableVarData.merge( return ImmutableVarData.merge(
self.value._get_all_var_data(), self._object._get_all_var_data(),
self.key._get_all_var_data(), self._key._get_all_var_data(),
self._var_data, self._var_data,
) )
@ -747,7 +768,38 @@ class ObjectItemOperation(ImmutableVar):
Returns: Returns:
The hash of the operation. 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( @dataclasses.dataclass(
@ -758,28 +810,9 @@ class ObjectItemOperation(ImmutableVar):
class ToObjectOperation(ObjectVar): class ToObjectOperation(ObjectVar):
"""Operation to convert a var to an object.""" """Operation to convert a var to an object."""
_original_var: Var = dataclasses.field(default_factory=lambda: LiteralObjectVar({})) _original_var: Var = dataclasses.field(
default_factory=lambda: LiteralObjectVar.create({})
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")
@cached_property @cached_property
def _cached_var_name(self) -> str: def _cached_var_name(self) -> str:
@ -831,6 +864,34 @@ class ToObjectOperation(ObjectVar):
""" """
return hash((self.__class__.__name__, self._original_var)) 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( @dataclasses.dataclass(
eq=False, eq=False,
@ -840,30 +901,13 @@ class ToObjectOperation(ObjectVar):
class ObjectHasOwnProperty(BooleanVar): class ObjectHasOwnProperty(BooleanVar):
"""Operation to check if an object has a property.""" """Operation to check if an object has a property."""
value: ObjectVar = dataclasses.field(default_factory=lambda: LiteralObjectVar({})) _object: ObjectVar = dataclasses.field(
key: Var | Any = dataclasses.field(default_factory=lambda: LiteralVar.create(None)) default_factory=lambda: LiteralObjectVar.create({})
)
_key: Var | Any = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
def __init__( def __post_init__(self):
self, """Post initialization."""
value: ObjectVar,
key: Var | Any,
_var_data: VarData | None = None,
):
"""Initialize the object has own property operation.
Args:
value: The value of the operation.
key: The key to check.
_var_data: Additional hooks and imports associated with the operation.
"""
super(ObjectHasOwnProperty, self).__init__(
_var_name="",
_var_data=ImmutableVarData.merge(_var_data),
)
object.__setattr__(self, "value", value)
object.__setattr__(
self, "key", key if isinstance(key, Var) else LiteralVar.create(key)
)
object.__delattr__(self, "_var_name") object.__delattr__(self, "_var_name")
@cached_property @cached_property
@ -873,7 +917,7 @@ class ObjectHasOwnProperty(BooleanVar):
Returns: Returns:
The name of the operation. 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): def __getattr__(self, name):
"""Get an attribute of the operation. """Get an attribute of the operation.
@ -896,8 +940,8 @@ class ObjectHasOwnProperty(BooleanVar):
The VarData of the components and all of its children. The VarData of the components and all of its children.
""" """
return ImmutableVarData.merge( return ImmutableVarData.merge(
self.value._get_all_var_data(), self._object._get_all_var_data(),
self.key._get_all_var_data(), self._key._get_all_var_data(),
self._var_data, self._var_data,
) )
@ -915,4 +959,29 @@ class ObjectHasOwnProperty(BooleanVar):
Returns: Returns:
The hash of the operation. 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 sqlalchemy.orm import DeclarativeBase
from reflex.config import get_config from reflex.config import get_config
from reflex.ivars.base import ImmutableVar from reflex.ivars.base import ImmutableComputedVar, ImmutableVar, immutable_computed_var
try: try:
import pydantic.v1 as pydantic import pydantic.v1 as pydantic
@ -60,7 +60,6 @@ from reflex.vars import (
ComputedVar, ComputedVar,
ImmutableVarData, ImmutableVarData,
Var, Var,
computed_var,
) )
if TYPE_CHECKING: if TYPE_CHECKING:
@ -68,7 +67,7 @@ if TYPE_CHECKING:
Delta = Dict[str, Any] Delta = Dict[str, Any]
var = computed_var var = immutable_computed_var
# If the state is this large, it's considered a performance issue. # 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]] = {} base_vars: ClassVar[Dict[str, ImmutableVar]] = {}
# The computed vars of the class. # 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. # Vars inherited by the parent state.
inherited_vars: ClassVar[Dict[str, Var]] = {} 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()})" return f"{self.__class__.__name__}({self.dict()})"
@classmethod @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. """Helper function to get all computed vars of a instance.
Returns: Returns:
@ -430,7 +429,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
v v
for mixin in cls._mixins() + [cls] for mixin in cls._mixins() + [cls]
for v in mixin.__dict__.values() for v in mixin.__dict__.values()
if isinstance(v, ComputedVar) if isinstance(v, (ComputedVar, ImmutableComputedVar))
] ]
@classmethod @classmethod
@ -534,7 +533,10 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
for f in cls.get_fields().values() for f in cls.get_fields().values()
if f.name not in cls.get_skip_vars() 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.vars = {
**cls.inherited_vars, **cls.inherited_vars,
**cls.base_vars, **cls.base_vars,
@ -555,12 +557,12 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
for mixin in cls._mixins(): for mixin in cls._mixins():
for name, value in mixin.__dict__.items(): for name, value in mixin.__dict__.items():
if isinstance(value, ComputedVar): if isinstance(value, (ComputedVar, ImmutableComputedVar)):
fget = cls._copy_fn(value.fget) 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 # cleanup refs to mixin cls in var_data
newcv._var_data = None
newcv._var_set_state(cls)
setattr(cls, name, newcv) setattr(cls, name, newcv)
cls.computed_vars[newcv._var_name] = newcv cls.computed_vars[newcv._var_name] = newcv
cls.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 # create the variable based on name and type
var = ImmutableVar(_var_name=name, _var_type=type_).guess_type() var = ImmutableVar(
var._var_set_state(cls) _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) # add the pydantic field dynamically (must be done before _init_var)
cls.add_field(var, default_value) 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) and not types.is_optional(prop._var_type)
): ):
# Ensure frontend uses null coalescing when accessing. # Ensure frontend uses null coalescing when accessing.
prop._var_type = Optional[prop._var_type] object.__setattr__(prop, "_var_type", Optional[prop._var_type])
@staticmethod @staticmethod
def _get_base_functions() -> dict[str, FunctionType]: def _get_base_functions() -> dict[str, FunctionType]:
@ -1783,7 +1786,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
# Include initial computed vars. # Include initial computed vars.
prop_name: ( prop_name: (
cv._initial_value cv._initial_value
if isinstance(cv, ComputedVar) if isinstance(cv, (ComputedVar, ImmutableComputedVar))
and not isinstance(cv._initial_value, types.Unset) and not isinstance(cv._initial_value, types.Unset)
else self.get_value(getattr(self, prop_name)) 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 typing import TYPE_CHECKING, Any, Callable, List, Optional, Union
from reflex import constants from reflex import constants
from reflex.ivars.base import ImmutableVar
from reflex.ivars.function import FunctionVar
from reflex.utils import exceptions, types from reflex.utils import exceptions, types
from reflex.vars import BaseVar, Var from reflex.vars import BaseVar, Var
@ -274,8 +272,10 @@ def format_f_string_prop(prop: BaseVar) -> str:
Returns: Returns:
The formatted string. The formatted string.
""" """
from reflex.ivars.base import VarData
s = prop._var_full_name 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 [] interps = var_data.interpolations if var_data else []
parts: List[str] = [] parts: List[str] = []
@ -423,6 +423,7 @@ def format_prop(
# import here to avoid circular import. # import here to avoid circular import.
from reflex.event import EventChain from reflex.event import EventChain
from reflex.utils import serializers from reflex.utils import serializers
from reflex.vars import VarData
try: try:
# Handle var props. # Handle var props.
@ -430,7 +431,8 @@ def format_prop(
if not prop._var_is_local or prop._var_is_string: if not prop._var_is_local or prop._var_is_string:
return str(prop) return str(prop)
if isinstance(prop, BaseVar) and types._issubclass(prop._var_type, str): 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_f_string_prop(prop)
return format_string(prop._var_full_name) return format_string(prop._var_full_name)
prop = 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. The formatted props list.
""" """
# Format all the props. # 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 [ return [
( (
f"{name}={{{format_prop(prop)}}}" f"{name}={format_prop(prop)}"
if isinstance(prop, ImmutableVar) if isinstance(prop, Var) and not isinstance(prop, ImmutableVar)
else f"{name}={format_prop(prop)}" else (
f"{name}={{{format_prop(prop if isinstance(prop, Var) else LiteralVar.create(prop))}}}"
)
) )
for name, prop in sorted(key_value_props.items()) for name, prop in sorted(key_value_props.items())
if prop is not None 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]: 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. # Get the class that defines the event handler.
parts = handler.fn.__qualname__.split(".") 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 # Get the state full name
state_full_name = handler.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 # Get the function name
name = parts[-1] name = parts[-1]
@ -655,6 +678,7 @@ def format_queue_events(
call_event_fn, call_event_fn,
call_event_handler, call_event_handler,
) )
from reflex.ivars.base import FunctionVar, ImmutableVar
if not events: if not events:
return ImmutableVar("(() => null)").to(FunctionVar, EventChain) return ImmutableVar("(() => null)").to(FunctionVar, EventChain)
@ -944,6 +968,8 @@ def format_data_editor_cell(cell: Any):
Returns: Returns:
The formatted cell. The formatted cell.
""" """
from reflex.ivars.base import ImmutableVar
return { return {
"kind": ImmutableVar.create("GridCellKind.Text"), "kind": ImmutableVar.create("GridCellKind.Text"),
"data": cell, "data": cell,

View File

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

View File

@ -892,6 +892,7 @@ class Var:
Raises: Raises:
VarTypeError: If the var is not indexable. VarTypeError: If the var is not indexable.
""" """
print(repr(self))
from reflex.utils import format from reflex.utils import format
# Indexing is only supported for strings, lists, tuples, dicts, and dataframes. # 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 _var_set_state(self, state: Type[BaseState] | str) -> Any: ...
def _get_all_var_data(self) -> VarData | ImmutableVarData: ... def _get_all_var_data(self) -> VarData | ImmutableVarData: ...
def json(self) -> str: ... def json(self) -> str: ...
def _type(self) -> Var: ...
@dataclass(eq=False) @dataclass(eq=False)
class BaseVar(Var): class BaseVar(Var):