Var field cleanup (#1943)

This commit is contained in:
Masen Furer 2023-10-13 14:53:55 -07:00 committed by GitHub
parent 684912e33b
commit 024cb5fa9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 691 additions and 530 deletions

View File

@ -131,8 +131,8 @@ def EventChain():
@app.add_page @app.add_page
def index(): def index():
return rx.fragment( return rx.fragment(
rx.input(value=State.token, readonly=True, id="token"), rx.input(value=State.token, is_read_only=True, id="token"),
rx.input(value=State.interim_value, readonly=True, id="interim_value"), rx.input(value=State.interim_value, is_read_only=True, id="interim_value"),
rx.button( rx.button(
"Return Event", "Return Event",
id="return_event", id="return_event",

View File

@ -65,13 +65,13 @@ class Base(pydantic.BaseModel):
default_value: The default value of the field default_value: The default value of the field
""" """
new_field = ModelField.infer( new_field = ModelField.infer(
name=var.name, name=var._var_name,
value=default_value, value=default_value,
annotation=var.type_, annotation=var._var_type,
class_validators=None, class_validators=None,
config=cls.__config__, config=cls.__config__,
) )
cls.__fields__.update({var.name: new_field}) cls.__fields__.update({var._var_name: new_field})
def get_value(self, key: str) -> Any: def get_value(self, key: str) -> Any:
"""Get the value of a field. """Get the value of a field.

View File

@ -243,7 +243,7 @@ def compile_custom_component(
} }
# Concatenate the props. # Concatenate the props.
props = [prop.name for prop in component.get_prop_vars()] props = [prop._var_name for prop in component.get_prop_vars()]
# Compile the component. # Compile the component.
return ( return (

View File

@ -94,4 +94,4 @@ def client_side(javascript_code) -> Var[EventChain]:
deprecation_version="0.2.9", deprecation_version="0.2.9",
removal_version="0.3.0", removal_version="0.3.0",
) )
return BaseVar(name=f"...args => {{{javascript_code}}}", type_=EventChain) return BaseVar(_var_name=f"...args => {{{javascript_code}}}", _var_type=EventChain)

View File

@ -155,7 +155,7 @@ class Component(Base, ABC):
raise TypeError raise TypeError
# Get the passed type and the var type. # Get the passed type and the var type.
passed_type = kwargs[key].type_ passed_type = kwargs[key]._var_type
expected_type = fields[key].outer_type_.__args__[0] expected_type = fields[key].outer_type_.__args__[0]
except TypeError: except TypeError:
# If it is not a valid var, check the base types. # If it is not a valid var, check the base types.
@ -222,7 +222,7 @@ class Component(Base, ABC):
# If it's an event chain var, return it. # If it's an event chain var, return it.
if isinstance(value, Var): if isinstance(value, Var):
if value.type_ is not EventChain: if value._var_type is not EventChain:
raise ValueError(f"Invalid event chain: {value}") raise ValueError(f"Invalid event chain: {value}")
return value return value
@ -388,7 +388,7 @@ class Component(Base, ABC):
# Add ref to element if `id` is not None. # Add ref to element if `id` is not None.
ref = self.get_ref() ref = self.get_ref()
if ref is not None: if ref is not None:
props["ref"] = Var.create(ref, is_local=False) props["ref"] = Var.create(ref, _var_is_local=False)
return tag.add_props(**props) return tag.add_props(**props)
@ -440,7 +440,7 @@ class Component(Base, ABC):
children = [ children = [
child child
if isinstance(child, Component) if isinstance(child, Component)
else Bare.create(contents=Var.create(child, is_string=True)) else Bare.create(contents=Var.create(child, _var_is_string=True))
for child in children for child in children
] ]
@ -808,11 +808,13 @@ class CustomComponent(Component):
# Handle subclasses of Base. # Handle subclasses of Base.
if types._issubclass(type_, Base): if types._issubclass(type_, Base):
try: try:
value = BaseVar(name=value.json(), type_=type_, is_local=True) value = BaseVar(
_var_name=value.json(), _var_type=type_, _var_is_local=True
)
except Exception: except Exception:
value = Var.create(value) value = Var.create(value)
else: else:
value = Var.create(value, is_string=type(value) is str) value = Var.create(value, _var_is_string=type(value) is str)
# Set the prop. # Set the prop.
self.props[format.to_camel_case(key)] = value self.props[format.to_camel_case(key)] = value
@ -888,8 +890,10 @@ class CustomComponent(Component):
""" """
return [ return [
BaseVar( BaseVar(
name=name, _var_name=name,
type_=prop.type_ if types._isinstance(prop, Var) else type(prop), _var_type=prop._var_type
if types._isinstance(prop, Var)
else type(prop),
) )
for name, prop in self.props.items() for name, prop in self.props.items()
] ]

View File

@ -48,7 +48,8 @@ class CodeBlock(Component):
merged_imports = super()._get_imports() merged_imports = super()._get_imports()
if self.theme is not None: if self.theme is not None:
merged_imports = imports.merge_imports( merged_imports = imports.merge_imports(
merged_imports, {PRISM_STYLES_PATH: {ImportVar(tag=self.theme.name)}} merged_imports,
{PRISM_STYLES_PATH: {ImportVar(tag=self.theme._var_name)}},
) )
return merged_imports return merged_imports
@ -113,7 +114,7 @@ class CodeBlock(Component):
out = super()._render() out = super()._render()
if self.theme is not None: if self.theme is not None:
out.add_props( out.add_props(
style=Var.create(self.theme.name, is_local=False) style=Var.create(self.theme._var_name, _var_is_local=False)
).remove_props("theme") ).remove_props("theme")
return out return out

View File

@ -64,7 +64,7 @@ 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.type_ == Any: if isinstance(data, ComputedVar) 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."
) )
@ -72,7 +72,7 @@ class DataTable(Gridjs):
if ( if (
columns is not None columns is not None
and isinstance(columns, ComputedVar) and isinstance(columns, ComputedVar)
and columns.type_ == Any and columns._var_type == Any
): ):
raise ValueError( raise ValueError(
"Annotation of the computed var assigned to the column field should be provided." "Annotation of the computed var assigned to the column field should be provided."
@ -81,7 +81,7 @@ class DataTable(Gridjs):
# If data is a pandas dataframe and columns are provided throw an error. # If data is a pandas dataframe and columns are provided throw an error.
if ( if (
types.is_dataframe(type(data)) types.is_dataframe(type(data))
or (isinstance(data, Var) and types.is_dataframe(data.type_)) or (isinstance(data, Var) and types.is_dataframe(data._var_type))
) and columns is not None: ) and columns is not None:
raise ValueError( raise ValueError(
"Cannot pass in both a pandas dataframe and columns to the data_table component." "Cannot pass in both a pandas dataframe and columns to the data_table component."
@ -89,7 +89,7 @@ class DataTable(Gridjs):
# If data is a list and columns are not provided, throw an error # If data is a list and columns are not provided, throw an error
if ( if (
(isinstance(data, Var) and types._issubclass(data.type_, List)) (isinstance(data, Var) and types._issubclass(data._var_type, List))
or issubclass(type(data), List) or issubclass(type(data), List)
) and columns is None: ) and columns is None:
raise ValueError( raise ValueError(
@ -109,16 +109,16 @@ class DataTable(Gridjs):
) )
def _render(self) -> Tag: def _render(self) -> Tag:
if isinstance(self.data, Var) and types.is_dataframe(self.data.type_): if isinstance(self.data, Var) and types.is_dataframe(self.data._var_type):
self.columns = BaseVar( self.columns = BaseVar(
name=f"{self.data.name}.columns", _var_name=f"{self.data._var_name}.columns",
type_=List[Any], _var_type=List[Any],
state=self.data.state, _var_state=self.data._var_state,
) )
self.data = BaseVar( self.data = BaseVar(
name=f"{self.data.name}.data", _var_name=f"{self.data._var_name}.data",
type_=List[List[Any]], _var_type=List[List[Any]],
state=self.data.state, _var_state=self.data._var_state,
) )
if types.is_dataframe(type(self.data)): if types.is_dataframe(type(self.data)):
# If given a pandas df break up the data and columns # If given a pandas df break up the data and columns

View File

@ -101,7 +101,9 @@ class Thead(ChakraComponent):
if ( if (
( (
isinstance(headers, Var) isinstance(headers, Var)
and not types.check_type_in_allowed_types(headers.type_, allowed_types) and not types.check_type_in_allowed_types(
headers._var_type, allowed_types
)
) )
or not isinstance(headers, Var) or not isinstance(headers, Var)
and not types.check_type_in_allowed_types(type(headers), allowed_types) and not types.check_type_in_allowed_types(type(headers), allowed_types)
@ -156,7 +158,7 @@ class Tbody(ChakraComponent):
""" """
allowed_subclasses = (List, Tuple) allowed_subclasses = (List, Tuple)
if isinstance(rows, Var): if isinstance(rows, Var):
outer_type = rows.type_ outer_type = rows._var_type
inner_type = ( inner_type = (
outer_type.__args__[0] if hasattr(outer_type, "__args__") else None outer_type.__args__[0] if hasattr(outer_type, "__args__") else None
) )
@ -222,7 +224,9 @@ class Tfoot(ChakraComponent):
if ( if (
( (
isinstance(footers, Var) isinstance(footers, Var)
and not types.check_type_in_allowed_types(footers.type_, allowed_types) and not types.check_type_in_allowed_types(
footers._var_type, allowed_types
)
) )
or not isinstance(footers, Var) or not isinstance(footers, Var)
and not types.check_type_in_allowed_types(type(footers), allowed_types) and not types.check_type_in_allowed_types(type(footers), allowed_types)

View File

@ -58,7 +58,7 @@ class DebounceInput(Component):
raise ValueError("DebounceInput child requires an on_change handler") raise ValueError("DebounceInput child requires an on_change handler")
child_ref = child.get_ref() child_ref = child.get_ref()
if child_ref and not props.get("ref"): if child_ref and not props.get("ref"):
props["input_ref"] = Var.create(child_ref, is_local=False) props["input_ref"] = Var.create(child_ref, _var_is_local=False)
self.children = [] self.children = []
tag = super()._render() tag = super()._render()
tag.add_props( tag.add_props(
@ -67,7 +67,9 @@ class DebounceInput(Component):
sx=child.style, sx=child.style,
id=child.id, id=child.id,
class_name=child.class_name, class_name=child.class_name,
element=Var.create("{%s}" % child.tag, is_local=False, is_string=False), element=Var.create(
"{%s}" % child.tag, _var_is_local=False, _var_is_string=False
),
) )
# do NOT render the child, DebounceInput will create it # do NOT render the child, DebounceInput will create it
object.__setattr__(child, "render", lambda: "") object.__setattr__(child, "render", lambda: "")

View File

@ -29,10 +29,12 @@ class Form(ChakraComponent):
# to collect data # to collect data
if ref.startswith("refs_"): if ref.startswith("refs_"):
form_refs[ref[5:-3]] = Var.create( form_refs[ref[5:-3]] = Var.create(
f"getRefValues({ref[:-3]})", is_local=False f"getRefValues({ref[:-3]})", _var_is_local=False
) )
else: else:
form_refs[ref[4:]] = Var.create(f"getRefValue({ref})", is_local=False) form_refs[ref[4:]] = Var.create(
f"getRefValue({ref})", _var_is_local=False
)
return { return {
**super().get_event_triggers(), **super().get_event_triggers(),

View File

@ -308,7 +308,9 @@ class Select(Component):
return { return {
**super().get_event_triggers(), **super().get_event_triggers(),
EventTriggers.ON_CHANGE: ( EventTriggers.ON_CHANGE: (
lambda e0: [Var.create_safe(f"{e0}.map(e => e.value)", is_local=True)] lambda e0: [
Var.create_safe(f"{e0}.map(e => e.value)", _var_is_local=True)
]
if self.is_multi if self.is_multi
else lambda e0: [e0] else lambda e0: [e0]
), ),

View File

@ -11,13 +11,17 @@ from reflex.event import EventChain
from reflex.vars import BaseVar, Var from reflex.vars import BaseVar, Var
files_state: str = "const [files, setFiles] = useState([]);" files_state: str = "const [files, setFiles] = useState([]);"
upload_file: BaseVar = BaseVar(name="e => setFiles((files) => e)", type_=EventChain) upload_file: BaseVar = BaseVar(
_var_name="e => setFiles((files) => e)", _var_type=EventChain
)
# Use this var along with the Upload component to render the list of selected files. # Use this var along with the Upload component to render the list of selected files.
selected_files: BaseVar = BaseVar(name="files.map((f) => f.name)", type_=List[str]) selected_files: BaseVar = BaseVar(
_var_name="files.map((f) => f.name)", _var_type=List[str]
)
clear_selected_files: BaseVar = BaseVar( clear_selected_files: BaseVar = BaseVar(
name="_e => setFiles((files) => [])", type_=EventChain _var_name="_e => setFiles((files) => [])", _var_type=EventChain
) )
@ -77,7 +81,9 @@ class Upload(Component):
} }
# The file input to use. # The file input to use.
upload = Input.create(type_="file") upload = Input.create(type_="file")
upload.special_props = {BaseVar(name="{...getInputProps()}", type_=None)} upload.special_props = {
BaseVar(_var_name="{...getInputProps()}", _var_type=None)
}
# The dropzone to use. # The dropzone to use.
zone = Box.create( zone = Box.create(
@ -85,7 +91,7 @@ class Upload(Component):
*children, *children,
**{k: v for k, v in props.items() if k not in supported_props}, **{k: v for k, v in props.items() if k not in supported_props},
) )
zone.special_props = {BaseVar(name="{...getRootProps()}", type_=None)} zone.special_props = {BaseVar(_var_name="{...getRootProps()}", _var_type=None)}
# Create the component. # Create the component.
return super().create(zone, on_drop=upload_file, **upload_props) return super().create(zone, on_drop=upload_file, **upload_props)

View File

@ -77,7 +77,7 @@ class Cond(Component):
).set( ).set(
props=tag.format_props(), props=tag.format_props(),
), ),
cond_state=f"isTrue({self.cond.full_name})", cond_state=f"isTrue({self.cond._var_full_name})",
) )
@ -118,11 +118,11 @@ def cond(condition: Any, c1: Any, c2: Any = None):
# Create the conditional var. # Create the conditional var.
return BaseVar( return BaseVar(
name=format.format_cond( _var_name=format.format_cond(
cond=cond_var.full_name, cond=cond_var._var_full_name,
true_value=c1, true_value=c1,
false_value=c2, false_value=c2,
is_prop=True, is_prop=True,
), ),
type_=c1.type_ if isinstance(c1, BaseVar) else type(c1), _var_type=c1._var_type if isinstance(c1, BaseVar) else type(c1),
) )

View File

@ -35,18 +35,18 @@ class Foreach(Component):
""" """
try: try:
type_ = ( type_ = (
iterable.type_ iterable._var_type
if iterable.type_.mro()[0] == dict if iterable._var_type.mro()[0] == dict
else iterable.type_.__args__[0] else iterable._var_type.__args__[0]
) )
except Exception: except Exception:
type_ = Any type_ = Any
iterable = Var.create(iterable) # type: ignore iterable = Var.create(iterable) # type: ignore
if iterable.type_ == Any: if iterable._var_type == Any:
raise TypeError( raise TypeError(
f"Could not foreach over var of type Any. (If you are trying to foreach over a state var, add a type annotation to the var.)" f"Could not foreach over var of type Any. (If you are trying to foreach over a state var, add a type annotation to the var.)"
) )
arg = BaseVar(name="_", type_=type_, is_local=True) arg = BaseVar(_var_name="_", _var_type=type_, _var_is_local=True)
return cls( return cls(
iterable=iterable, iterable=iterable,
render_fn=render_fn, render_fn=render_fn,
@ -66,15 +66,15 @@ class Foreach(Component):
tag = self._render() tag = self._render()
try: try:
type_ = ( type_ = (
self.iterable.type_ self.iterable._var_type
if self.iterable.type_.mro()[0] == dict if self.iterable._var_type.mro()[0] == dict
else self.iterable.type_.__args__[0] else self.iterable._var_type.__args__[0]
) )
except Exception: except Exception:
type_ = Any type_ = Any
arg = BaseVar( arg = BaseVar(
name=get_unique_variable_name(), _var_name=get_unique_variable_name(),
type_=type_, _var_type=type_,
) )
index_arg = tag.get_index_var_arg() index_arg = tag.get_index_var_arg()
component = tag.render_component(self.render_fn, arg) component = tag.render_component(self.render_fn, arg)
@ -89,8 +89,8 @@ class Foreach(Component):
children=[component.render()], children=[component.render()],
props=tag.format_props(), props=tag.format_props(),
), ),
iterable_state=tag.iterable.full_name, iterable_state=tag.iterable._var_full_name,
arg_name=arg.name, arg_name=arg._var_name,
arg_index=index_arg, arg_index=index_arg,
iterable_type=tag.iterable.type_.mro()[0].__name__, iterable_type=tag.iterable._var_type.mro()[0].__name__,
) )

View File

@ -77,7 +77,7 @@ class Image(ChakraComponent):
""" """
src = props.get("src", None) src = props.get("src", None)
if src is not None and not isinstance(src, (Var)): if src is not None and not isinstance(src, (Var)):
props["src"] = Var.create(value=src, is_string=True) props["src"] = Var.create(value=src, _var_is_string=True)
return super().create(*children, **props) return super().create(*children, **props)

View File

@ -23,7 +23,7 @@ class Link(ChakraComponent):
text: Var[str] text: Var[str]
# What the link renders to. # What the link renders to.
as_: Var[str] = BaseVar.create("{NextLink}", is_local=False) # type: ignore as_: Var[str] = BaseVar.create(value="{NextLink}", _var_is_local=False) # type: ignore
# If true, the link will open in new tab. # If true, the link will open in new tab.
is_external: Var[bool] is_external: Var[bool]

View File

@ -13,14 +13,14 @@ from reflex.vars import ImportVar, Var
connection_error: Var = Var.create_safe( connection_error: Var = Var.create_safe(
value="(connectError !== null) ? connectError.message : ''", value="(connectError !== null) ? connectError.message : ''",
is_local=False, _var_is_local=False,
is_string=False, _var_is_string=False,
) )
has_connection_error: Var = Var.create_safe( has_connection_error: Var = Var.create_safe(
value="connectError !== null", value="connectError !== null",
is_string=False, _var_is_string=False,
) )
has_connection_error.type_ = bool has_connection_error._var_type = bool
class WebsocketTargetURL(Bare): class WebsocketTargetURL(Bare):

View File

@ -31,8 +31,8 @@ class IterTag(Tag):
The index var. The index var.
""" """
return BaseVar( return BaseVar(
name=INDEX_VAR, _var_name=INDEX_VAR,
type_=int, _var_type=int,
) )
@staticmethod @staticmethod
@ -43,9 +43,9 @@ class IterTag(Tag):
The index var. The index var.
""" """
return BaseVar( return BaseVar(
name=INDEX_VAR, _var_name=INDEX_VAR,
type_=int, _var_type=int,
is_local=True, _var_is_local=True,
) )
@staticmethod @staticmethod

View File

@ -17,18 +17,18 @@ from reflex.utils import console, imports, types
from reflex.vars import ImportVar, Var from reflex.vars import ImportVar, Var
# Special vars used in the component map. # Special vars used in the component map.
_CHILDREN = Var.create_safe("children", is_local=False) _CHILDREN = Var.create_safe("children", _var_is_local=False)
_PROPS = Var.create_safe("...props", is_local=False) _PROPS = Var.create_safe("...props", _var_is_local=False)
_MOCK_ARG = Var.create_safe("") _MOCK_ARG = Var.create_safe("")
# Special remark plugins. # Special remark plugins.
_REMARK_MATH = Var.create_safe("remarkMath", is_local=False) _REMARK_MATH = Var.create_safe("remarkMath", _var_is_local=False)
_REMARK_GFM = Var.create_safe("remarkGfm", is_local=False) _REMARK_GFM = Var.create_safe("remarkGfm", _var_is_local=False)
_REMARK_PLUGINS = Var.create_safe([_REMARK_MATH, _REMARK_GFM]) _REMARK_PLUGINS = Var.create_safe([_REMARK_MATH, _REMARK_GFM])
# Special rehype plugins. # Special rehype plugins.
_REHYPE_KATEX = Var.create_safe("rehypeKatex", is_local=False) _REHYPE_KATEX = Var.create_safe("rehypeKatex", _var_is_local=False)
_REHYPE_RAW = Var.create_safe("rehypeRaw", is_local=False) _REHYPE_RAW = Var.create_safe("rehypeRaw", _var_is_local=False)
_REHYPE_PLUGINS = Var.create_safe([_REHYPE_KATEX, _REHYPE_RAW]) _REHYPE_PLUGINS = Var.create_safe([_REHYPE_KATEX, _REHYPE_RAW])
# Component Mapping # Component Mapping
@ -153,13 +153,17 @@ class Markdown(Component):
{ {
"": {ImportVar(tag="katex/dist/katex.min.css")}, "": {ImportVar(tag="katex/dist/katex.min.css")},
"remark-math@5.1.1": { "remark-math@5.1.1": {
ImportVar(tag=_REMARK_MATH.name, is_default=True) ImportVar(tag=_REMARK_MATH._var_name, is_default=True)
},
"remark-gfm@3.0.1": {
ImportVar(tag=_REMARK_GFM._var_name, is_default=True)
}, },
"remark-gfm@3.0.1": {ImportVar(tag=_REMARK_GFM.name, is_default=True)},
"rehype-katex@6.0.3": { "rehype-katex@6.0.3": {
ImportVar(tag=_REHYPE_KATEX.name, is_default=True) ImportVar(tag=_REHYPE_KATEX._var_name, is_default=True)
},
"rehype-raw@6.1.1": {
ImportVar(tag=_REHYPE_RAW._var_name, is_default=True)
}, },
"rehype-raw@6.1.1": {ImportVar(tag=_REHYPE_RAW.name, is_default=True)},
} }
) )
@ -226,20 +230,20 @@ class Markdown(Component):
The formatted component map. The formatted component map.
""" """
components = { components = {
tag: f"{{({{{_CHILDREN.name}, {_PROPS.name}}}) => {self.format_component(tag)}}}" tag: f"{{({{{_CHILDREN._var_name}, {_PROPS._var_name}}}) => {self.format_component(tag)}}}"
for tag in self.component_map for tag in self.component_map
} }
# Separate out inline code and code blocks. # Separate out inline code and code blocks.
components[ components[
"code" "code"
] = f"""{{({{inline, className, {_CHILDREN.name}, {_PROPS.name}}}) => {{ ] = f"""{{({{inline, className, {_CHILDREN._var_name}, {_PROPS._var_name}}}) => {{
const match = (className || '').match(/language-(?<lang>.*)/); const match = (className || '').match(/language-(?<lang>.*)/);
const language = match ? match[1] : ''; const language = match ? match[1] : '';
return inline ? ( return inline ? (
{self.format_component("code")} {self.format_component("code")}
) : ( ) : (
{self.format_component("codeblock", language=Var.create_safe("language", is_local=False), children=Var.create_safe("String(children)", is_local=False))} {self.format_component("codeblock", language=Var.create_safe("language", _var_is_local=False), children=Var.create_safe("String(children)", _var_is_local=False))}
); );
}}}}""".replace( }}}}""".replace(
"\n", " " "\n", " "

View File

@ -153,7 +153,7 @@ class EventHandler(Base):
# Otherwise, convert to JSON. # Otherwise, convert to JSON.
try: try:
values.append(Var.create(arg, is_string=type(arg) is str)) values.append(Var.create(arg, _var_is_string=type(arg) is str))
except TypeError as e: except TypeError as e:
raise TypeError( raise TypeError(
f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}." f"Arguments to event handlers must be Vars or JSON-serializable. Got {arg} of type {type(arg)}."
@ -211,7 +211,7 @@ class FrontendEvent(Base):
# The default event argument. # The default event argument.
EVENT_ARG = BaseVar(name="_e", type_=FrontendEvent, is_local=True) EVENT_ARG = BaseVar(_var_name="_e", _var_type=FrontendEvent, _var_is_local=True)
class FileUpload(Base): class FileUpload(Base):
@ -241,7 +241,7 @@ def server_side(name: str, sig: inspect.Signature, **kwargs) -> EventSpec:
return EventSpec( return EventSpec(
handler=EventHandler(fn=fn), handler=EventHandler(fn=fn),
args=tuple( args=tuple(
(Var.create_safe(k), Var.create_safe(v, is_string=type(v) is str)) (Var.create_safe(k), Var.create_safe(v, _var_is_string=type(v) is str))
for k, v in kwargs.items() for k, v in kwargs.items()
), ),
) )
@ -560,9 +560,9 @@ def parse_args_spec(arg_spec: ArgsSpec):
return arg_spec( return arg_spec(
*[ *[
BaseVar( BaseVar(
name=f"_{l_arg}", _var_name=f"_{l_arg}",
type_=spec.annotations.get(l_arg, FrontendEvent), _var_type=spec.annotations.get(l_arg, FrontendEvent),
is_local=True, _var_is_local=True,
) )
for l_arg in spec.args for l_arg in spec.args
] ]
@ -675,7 +675,7 @@ def fix_events(
e = e() e = e()
assert isinstance(e, EventSpec), f"Unexpected event type, {type(e)}." assert isinstance(e, EventSpec), f"Unexpected event type, {type(e)}."
name = format.format_event_handler(e.handler) name = format.format_event_handler(e.handler)
payload = {k.name: v._decode() for k, v in e.args} # type: ignore payload = {k._var_name: v._decode() for k, v in e.args} # type: ignore
# Create an event and append it to the list. # Create an event and append it to the list.
out.append( out.append(

View File

@ -132,7 +132,7 @@ class State(Base, ABC, extra=pydantic.Extra.allow):
) )
for cvar_name, cvar in self.computed_vars.items(): for cvar_name, cvar in self.computed_vars.items():
# Add the dependencies. # Add the dependencies.
for var in cvar.deps(objclass=type(self)): for var in cvar._deps(objclass=type(self)):
self.computed_var_dependencies[var].add(cvar_name) self.computed_var_dependencies[var].add(cvar_name)
if var in inherited_vars: if var in inherited_vars:
# track that this substate depends on its parent for this var # track that this substate depends on its parent for this var
@ -211,12 +211,14 @@ class State(Base, ABC, extra=pydantic.Extra.allow):
# Set the base and computed vars. # Set the base and computed vars.
cls.base_vars = { cls.base_vars = {
f.name: BaseVar(name=f.name, type_=f.outer_type_).set_state(cls) f.name: BaseVar(_var_name=f.name, _var_type=f.outer_type_)._var_set_state(
cls
)
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 = { cls.computed_vars = {
v.name: v.set_state(cls) v._var_name: v._var_set_state(cls)
for v in cls.__dict__.values() for v in cls.__dict__.values()
if isinstance(v, ComputedVar) if isinstance(v, ComputedVar)
} }
@ -389,12 +391,12 @@ class State(Base, ABC, extra=pydantic.Extra.allow):
Raises: Raises:
TypeError: if the variable has an incorrect type TypeError: if the variable has an incorrect type
""" """
if not types.is_valid_var_type(prop.type_): if not types.is_valid_var_type(prop._var_type):
raise TypeError( raise TypeError(
"State vars must be primitive Python types, " "State vars must be primitive Python types, "
"Plotly figures, Pandas dataframes, " "Plotly figures, Pandas dataframes, "
"or subclasses of rx.Base. " "or subclasses of rx.Base. "
f'Found var "{prop.name}" with type {prop.type_}.' f'Found var "{prop._var_name}" with type {prop._var_type}.'
) )
cls._set_var(prop) cls._set_var(prop)
cls._create_setter(prop) cls._create_setter(prop)
@ -421,8 +423,8 @@ class State(Base, ABC, extra=pydantic.Extra.allow):
) )
# create the variable based on name and type # create the variable based on name and type
var = BaseVar(name=name, type_=type_) var = BaseVar(_var_name=name, _var_type=type_)
var.set_state(cls) var._var_set_state(cls)
# 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)
@ -444,7 +446,7 @@ class State(Base, ABC, extra=pydantic.Extra.allow):
Args: Args:
prop: The var instance to set. prop: The var instance to set.
""" """
setattr(cls, prop.name, prop) setattr(cls, prop._var_name, prop)
@classmethod @classmethod
def _create_setter(cls, prop: BaseVar): def _create_setter(cls, prop: BaseVar):
@ -467,7 +469,7 @@ class State(Base, ABC, extra=pydantic.Extra.allow):
prop: The var to set the default value for. prop: The var to set the default value for.
""" """
# Get the pydantic field for the var. # Get the pydantic field for the var.
field = cls.get_fields()[prop.name] field = cls.get_fields()[prop._var_name]
default_value = prop.get_default_value() default_value = prop.get_default_value()
if field.required and default_value is not None: if field.required and default_value is not None:
field.required = False field.required = False
@ -599,8 +601,9 @@ class State(Base, ABC, extra=pydantic.Extra.allow):
func = arglist_factory(param) func = arglist_factory(param)
else: else:
continue continue
func.fget.__name__ = param # to allow passing as a prop # type: ignore # to allow passing as a prop
cls.vars[param] = cls.computed_vars[param] = func.set_state(cls) # type: ignore func._var_name = param
cls.vars[param] = cls.computed_vars[param] = func._var_set_state(cls) # type: ignore
setattr(cls, param, func) setattr(cls, param, func)
def __getattribute__(self, name: str) -> Any: def __getattribute__(self, name: str) -> Any:
@ -912,7 +915,7 @@ class State(Base, ABC, extra=pydantic.Extra.allow):
return set( return set(
cvar_name cvar_name
for cvar_name, cvar in self.computed_vars.items() for cvar_name, cvar in self.computed_vars.items()
if not cvar.cache if not cvar._cache
) )
def _mark_dirty_computed_vars(self) -> None: def _mark_dirty_computed_vars(self) -> None:

View File

@ -7,8 +7,8 @@ from reflex.event import EventChain
from reflex.utils import format from reflex.utils import format
from reflex.vars import BaseVar, Var from reflex.vars import BaseVar, Var
color_mode = BaseVar(name=constants.ColorMode.NAME, type_="str") color_mode = BaseVar(_var_name=constants.ColorMode.NAME, _var_type="str")
toggle_color_mode = BaseVar(name=constants.ColorMode.TOGGLE, type_=EventChain) toggle_color_mode = BaseVar(_var_name=constants.ColorMode.TOGGLE, _var_type=EventChain)
def convert(style_dict): def convert(style_dict):

View File

@ -106,6 +106,7 @@ class AppHarness:
app_instance: Optional[reflex.App] = None app_instance: Optional[reflex.App] = None
frontend_process: Optional[subprocess.Popen] = None frontend_process: Optional[subprocess.Popen] = None
frontend_url: Optional[str] = None frontend_url: Optional[str] = None
frontend_output_thread: Optional[threading.Thread] = None
backend_thread: Optional[threading.Thread] = None backend_thread: Optional[threading.Thread] = None
backend: Optional[uvicorn.Server] = None backend: Optional[uvicorn.Server] = None
state_manager: Optional[StateManagerMemory | StateManagerRedis] = None state_manager: Optional[StateManagerMemory | StateManagerRedis] = None
@ -230,6 +231,18 @@ class AppHarness:
if self.frontend_url is None: if self.frontend_url is None:
raise RuntimeError("Frontend did not start") raise RuntimeError("Frontend did not start")
def consume_frontend_output():
while True:
line = (
self.frontend_process.stdout.readline() # pyright: ignore [reportOptionalMemberAccess]
)
if not line:
break
print(line)
self.frontend_output_thread = threading.Thread(target=consume_frontend_output)
self.frontend_output_thread.start()
def start(self) -> "AppHarness": def start(self) -> "AppHarness":
"""Start the backend in a new thread and dev frontend as a separate process. """Start the backend in a new thread and dev frontend as a separate process.
@ -278,6 +291,8 @@ class AppHarness:
self.frontend_process.communicate() self.frontend_process.communicate()
if self.backend_thread is not None: if self.backend_thread is not None:
self.backend_thread.join() self.backend_thread.join()
if self.frontend_output_thread is not None:
self.frontend_output_thread.join()
for driver in self._frontends: for driver in self._frontends:
driver.quit() driver.quit()

View File

@ -203,13 +203,13 @@ def format_var(var: Var) -> str:
Returns: Returns:
The formatted Var. The formatted Var.
""" """
if not var.is_local or var.is_string: if not var._var_is_local or var._var_is_string:
return str(var) return str(var)
if types._issubclass(var.type_, str): if types._issubclass(var._var_type, str):
return format_string(var.full_name) return format_string(var._var_full_name)
if is_wrapped(var.full_name, "{"): if is_wrapped(var._var_full_name, "{"):
return var.full_name return var._var_full_name
return json_dumps(var.full_name) return json_dumps(var._var_full_name)
def format_route(route: str, format_case=True) -> str: def format_route(route: str, format_case=True) -> str:
@ -259,12 +259,16 @@ def format_cond(
# Format prop conds. # Format prop conds.
if is_prop: if is_prop:
prop1 = Var.create_safe(true_value, is_string=type(true_value) is str).set( prop1 = Var.create_safe(
is_local=True true_value,
) # type: ignore _var_is_string=type(true_value) is str,
prop2 = Var.create_safe(false_value, is_string=type(false_value) is str).set( )
is_local=True prop1._var_is_local = True
) # type: ignore prop2 = Var.create_safe(
false_value,
_var_is_string=type(false_value) is str,
)
prop2._var_is_local = True
return f"{cond} ? {prop1} : {prop2}".replace("{", "").replace("}", "") return f"{cond} ? {prop1} : {prop2}".replace("{", "").replace("}", "")
# Format component conds. # Format component conds.
@ -292,11 +296,11 @@ def format_prop(
try: try:
# Handle var props. # Handle var props.
if isinstance(prop, Var): if isinstance(prop, Var):
if not prop.is_local or prop.is_string: if not prop._var_is_local or prop._var_is_string:
return str(prop) return str(prop)
if types._issubclass(prop.type_, str): if types._issubclass(prop._var_type, str):
return format_string(prop.full_name) return format_string(prop._var_full_name)
prop = prop.full_name prop = prop._var_full_name
# Handle event props. # Handle event props.
elif isinstance(prop, EventChain): elif isinstance(prop, EventChain):
@ -414,7 +418,12 @@ def format_event(event_spec: EventSpec) -> str:
args = ",".join( args = ",".join(
[ [
":".join( ":".join(
(name.name, json.dumps(val.name) if val.is_string else val.full_name) (
name._var_name,
json.dumps(val._var_name)
if val._var_is_string
else val._var_full_name,
)
) )
for name, val in event_spec.args for name, val in event_spec.args
] ]
@ -448,7 +457,7 @@ def format_event_chain(
if isinstance(event_chain, Var): if isinstance(event_chain, Var):
from reflex.event import EventChain from reflex.event import EventChain
if event_chain.type_ is not EventChain: if event_chain._var_type is not EventChain:
raise ValueError(f"Invalid event chain: {event_chain}") raise ValueError(f"Invalid event chain: {event_chain}")
return "".join( return "".join(
[ [
@ -540,7 +549,7 @@ def format_array_ref(refs: str, idx: Var | None) -> str:
""" """
clean_ref = re.sub(r"[^\w]+", "_", refs) clean_ref = re.sub(r"[^\w]+", "_", refs)
if idx is not None: if idx is not None:
idx.is_local = True idx._var_is_local = True
return f"refs_{clean_ref}[{idx}]" return f"refs_{clean_ref}[{idx}]"
return f"refs_{clean_ref}" return f"refs_{clean_ref}"

View File

@ -2,11 +2,12 @@
from __future__ import annotations from __future__ import annotations
import contextlib import contextlib
import dataclasses
import dis import dis
import json import json
import random import random
import string import string
from abc import ABC import sys
from types import CodeType, FunctionType from types import CodeType, FunctionType
from typing import ( from typing import (
TYPE_CHECKING, TYPE_CHECKING,
@ -64,6 +65,18 @@ OPERATION_MAPPING = {
(list, list): {"+", ">", "<", "<=", ">="}, (list, list): {"+", ">", "<", "<=", ">="},
} }
# These names were changed in reflex 0.3.0
REPLACED_NAMES = {
"full_name": "_var_full_name",
"name": "_var_name",
"state": "_var_state",
"type_": "_var_type",
"is_local": "_var_is_local",
"is_string": "_var_is_string",
"set_state": "_var_set_state",
"deps": "_deps",
}
def get_unique_variable_name() -> str: def get_unique_variable_name() -> str:
"""Get a unique variable name. """Get a unique variable name.
@ -78,34 +91,34 @@ def get_unique_variable_name() -> str:
return get_unique_variable_name() return get_unique_variable_name()
class Var(ABC): class Var:
"""An abstract var.""" """An abstract var."""
# The name of the var. # The name of the var.
name: str _var_name: str
# The type of the var. # The type of the var.
type_: Type _var_type: Type
# The name of the enclosing state. # The name of the enclosing state.
state: str = "" _var_state: str
# Whether this is a local javascript variable. # Whether this is a local javascript variable.
is_local: bool = False _var_is_local: bool
# Whether the var is a string literal. # Whether the var is a string literal.
is_string: bool = False _var_is_string: bool
@classmethod @classmethod
def create( def create(
cls, value: Any, is_local: bool = True, is_string: bool = False cls, value: Any, _var_is_local: bool = True, _var_is_string: bool = False
) -> Var | None: ) -> Var | None:
"""Create a var from a value. """Create a var from a value.
Args: Args:
value: The value to create the var from. value: The value to create the var from.
is_local: Whether the var is local. _var_is_local: Whether the var is local.
is_string: Whether the var is a string literal. _var_is_string: Whether the var is a string literal.
Returns: Returns:
The var. The var.
@ -130,23 +143,32 @@ class Var(ABC):
) )
name = name if isinstance(name, str) else format.json_dumps(name) name = name if isinstance(name, str) else format.json_dumps(name)
return BaseVar(name=name, type_=type_, is_local=is_local, is_string=is_string) return BaseVar(
_var_name=name,
_var_type=type_,
_var_is_local=_var_is_local,
_var_is_string=_var_is_string,
)
@classmethod @classmethod
def create_safe( def create_safe(
cls, value: Any, is_local: bool = True, is_string: bool = False cls, value: Any, _var_is_local: bool = True, _var_is_string: bool = False
) -> Var: ) -> Var:
"""Create a var from a value, guaranteeing that it is not None. """Create a var from a value, asserting that it is not None.
Args: Args:
value: The value to create the var from. value: The value to create the var from.
is_local: Whether the var is local. _var_is_local: Whether the var is local.
is_string: Whether the var is a string literal. _var_is_string: Whether the var is a string literal.
Returns: Returns:
The var. The var.
""" """
var = cls.create(value, is_local=is_local, is_string=is_string) var = cls.create(
value,
_var_is_local=_var_is_local,
_var_is_string=_var_is_string,
)
assert var is not None assert var is not None
return var return var
@ -171,14 +193,14 @@ class Var(ABC):
Returns: Returns:
The decoded value or the Var name. The decoded value or the Var name.
""" """
if self.state: if self._var_state:
return self.full_name return self._var_full_name
if self.is_string: if self._var_is_string:
return self.name return self._var_name
try: try:
return json.loads(self.name) return json.loads(self._var_name)
except ValueError: except ValueError:
return self.name return self._var_name
def equals(self, other: Var) -> bool: def equals(self, other: Var) -> bool:
"""Check if two vars are equal. """Check if two vars are equal.
@ -190,10 +212,10 @@ class Var(ABC):
Whether the vars are equal. Whether the vars are equal.
""" """
return ( return (
self.name == other.name self._var_name == other._var_name
and self.type_ == other.type_ and self._var_type == other._var_type
and self.state == other.state and self._var_state == other._var_state
and self.is_local == other.is_local and self._var_is_local == other._var_is_local
) )
def to_string(self, json: bool = True) -> Var: def to_string(self, json: bool = True) -> Var:
@ -214,7 +236,7 @@ class Var(ABC):
Returns: Returns:
The hash of the var. The hash of the var.
""" """
return hash((self.name, str(self.type_))) return hash((self._var_name, str(self._var_type)))
def __str__(self) -> str: def __str__(self) -> str:
"""Wrap the var so it can be used in templates. """Wrap the var so it can be used in templates.
@ -222,8 +244,12 @@ class Var(ABC):
Returns: Returns:
The wrapped var, i.e. {state.var}. The wrapped var, i.e. {state.var}.
""" """
out = self.full_name if self.is_local else format.wrap(self.full_name, "{") out = (
if self.is_string: self._var_full_name
if self._var_is_local
else format.wrap(self._var_full_name, "{")
)
if self._var_is_string:
out = format.format_string(out) out = format.format_string(out)
return out return out
@ -234,7 +260,7 @@ class Var(ABC):
TypeError: when attempting to bool-ify the Var. TypeError: when attempting to bool-ify the Var.
""" """
raise TypeError( raise TypeError(
f"Cannot convert Var {self.full_name!r} to bool for use with `if`, `and`, `or`, and `not`. " f"Cannot convert Var {self._var_full_name!r} to bool for use with `if`, `and`, `or`, and `not`. "
"Instead use `rx.cond` and bitwise operators `&` (and), `|` (or), `~` (invert)." "Instead use `rx.cond` and bitwise operators `&` (and), `|` (or), `~` (invert)."
) )
@ -245,7 +271,7 @@ class Var(ABC):
TypeError: when attempting to iterate over the Var. TypeError: when attempting to iterate over the Var.
""" """
raise TypeError( raise TypeError(
f"Cannot iterate over Var {self.full_name!r}. Instead use `rx.foreach`." f"Cannot iterate over Var {self._var_full_name!r}. Instead use `rx.foreach`."
) )
def __format__(self, format_spec: str) -> str: def __format__(self, format_spec: str) -> str:
@ -257,7 +283,7 @@ class Var(ABC):
Returns: Returns:
The formatted var. The formatted var.
""" """
if self.is_local: if self._var_is_local:
return str(self) return str(self)
return f"${str(self)}" return f"${str(self)}"
@ -275,16 +301,16 @@ class Var(ABC):
""" """
# Indexing is only supported for strings, lists, tuples, dicts, and dataframes. # Indexing is only supported for strings, lists, tuples, dicts, and dataframes.
if not ( if not (
types._issubclass(self.type_, Union[List, Dict, Tuple, str]) types._issubclass(self._var_type, Union[List, Dict, Tuple, str])
or types.is_dataframe(self.type_) or types.is_dataframe(self._var_type)
): ):
if self.type_ == Any: if self._var_type == Any:
raise TypeError( raise TypeError(
"Could not index into var of type Any. (If you are trying to index into a state var, " "Could not index into var of type Any. (If you are trying to index into a state var, "
"add the correct type annotation to the var.)" "add the correct type annotation to the var.)"
) )
raise TypeError( raise TypeError(
f"Var {self.name} of type {self.type_} does not support indexing." f"Var {self._var_name} of type {self._var_type} does not support indexing."
) )
# The type of the indexed var. # The type of the indexed var.
@ -292,15 +318,20 @@ class Var(ABC):
# Convert any vars to local vars. # Convert any vars to local vars.
if isinstance(i, Var): if isinstance(i, Var):
i = BaseVar(name=i.name, type_=i.type_, state=i.state, is_local=True) i = BaseVar(
_var_name=i._var_name,
_var_type=i._var_type,
_var_state=i._var_state,
_var_is_local=True,
)
# Handle list/tuple/str indexing. # Handle list/tuple/str indexing.
if types._issubclass(self.type_, Union[List, Tuple, str]): if types._issubclass(self._var_type, Union[List, Tuple, str]):
# List/Tuple/String indices must be ints, slices, or vars. # List/Tuple/String indices must be ints, slices, or vars.
if ( if (
not isinstance(i, types.get_args(Union[int, slice, Var])) not isinstance(i, types.get_args(Union[int, slice, Var]))
or isinstance(i, Var) or isinstance(i, Var)
and not i.type_ == int and not i._var_type == int
): ):
raise TypeError("Index must be an integer or an integer var.") raise TypeError("Index must be an integer or an integer var.")
@ -312,35 +343,40 @@ class Var(ABC):
# Use the slice function. # Use the slice function.
return BaseVar( return BaseVar(
name=f"{self.name}.slice({start}, {stop})", _var_name=f"{self._var_name}.slice({start}, {stop})",
type_=self.type_, _var_type=self._var_type,
state=self.state, _var_state=self._var_state,
is_local=self.is_local, _var_is_local=self._var_is_local,
) )
# Get the type of the indexed var. # Get the type of the indexed var.
type_ = ( type_ = (
types.get_args(self.type_)[0] types.get_args(self._var_type)[0]
if types.is_generic_alias(self.type_) if types.is_generic_alias(self._var_type)
else Any else Any
) )
# Use `at` to support negative indices. # Use `at` to support negative indices.
return BaseVar( return BaseVar(
name=f"{self.name}.at({i})", _var_name=f"{self._var_name}.at({i})",
type_=type_, _var_type=type_,
state=self.state, _var_state=self._var_state,
is_local=self.is_local, _var_is_local=self._var_is_local,
) )
# Dictionary / dataframe indexing. # Dictionary / dataframe indexing.
# Tuples are currently not supported as indexes. # Tuples are currently not supported as indexes.
if ( if (
(types._issubclass(self.type_, Dict) or types.is_dataframe(self.type_)) (
types._issubclass(self._var_type, Dict)
or types.is_dataframe(self._var_type)
)
and not isinstance(i, types.get_args(Union[int, str, float, Var])) and not isinstance(i, types.get_args(Union[int, str, float, Var]))
) or ( ) or (
isinstance(i, Var) isinstance(i, Var)
and not types._issubclass(i.type_, types.get_args(Union[int, str, float])) and not types._issubclass(
i._var_type, types.get_args(Union[int, str, float])
)
): ):
raise TypeError( raise TypeError(
"Index must be one of the following types: int, str, int or str Var" "Index must be one of the following types: int, str, int or str Var"
@ -349,18 +385,20 @@ class Var(ABC):
if isinstance(i, str): if isinstance(i, str):
i = format.wrap(i, '"') i = format.wrap(i, '"')
type_ = ( type_ = (
types.get_args(self.type_)[1] if types.is_generic_alias(self.type_) else Any types.get_args(self._var_type)[1]
if types.is_generic_alias(self._var_type)
else Any
) )
# Use normal indexing here. # Use normal indexing here.
return BaseVar( return BaseVar(
name=f"{self.name}[{i}]", _var_name=f"{self._var_name}[{i}]",
type_=type_, _var_type=type_,
state=self.state, _var_state=self._var_state,
is_local=self.is_local, _var_is_local=self._var_is_local,
) )
def __getattribute__(self, name: str) -> Var: def __getattr__(self, name: str) -> Var:
"""Get a var attribute. """Get a var attribute.
Args: Args:
@ -373,30 +411,39 @@ class Var(ABC):
AttributeError: If the var is wrongly annotated or can't find attribute. AttributeError: If the var is wrongly annotated or can't find attribute.
TypeError: If an annotation to the var isn't provided. TypeError: If an annotation to the var isn't provided.
""" """
try: # Check if the attribute is one of the class fields.
return super().__getattribute__(name) if not name.startswith("_"):
except Exception as e: if self._var_type == Any:
# Check if the attribute is one of the class fields. raise TypeError(
if not name.startswith("_"): f"You must provide an annotation for the state var `{self._var_full_name}`. Annotation cannot be `{self._var_type}`"
if self.type_ == Any: ) from None
raise TypeError( if (
f"You must provide an annotation for the state var `{self.full_name}`. Annotation cannot be `{self.type_}`" hasattr(self._var_type, "__fields__")
) from None and name in self._var_type.__fields__
if hasattr(self.type_, "__fields__") and name in self.type_.__fields__: ):
type_ = self.type_.__fields__[name].outer_type_ type_ = self._var_type.__fields__[name].outer_type_
if isinstance(type_, ModelField): if isinstance(type_, ModelField):
type_ = type_.type_ type_ = type_.type_
return BaseVar( return BaseVar(
name=f"{self.name}.{name}", _var_name=f"{self._var_name}.{name}",
type_=type_, _var_type=type_,
state=self.state, _var_state=self._var_state,
is_local=self.is_local, _var_is_local=self._var_is_local,
) )
if name in REPLACED_NAMES:
raise AttributeError(
f"Field {name!r} was renamed to {REPLACED_NAMES[name]!r}"
)
raise AttributeError( raise AttributeError(
f"The State var `{self.full_name}` has no attribute '{name}' or may have been annotated " f"The State var `{self._var_full_name}` has no attribute '{name}' or may have been annotated "
f"wrongly.\n" f"wrongly."
f"original message: {e.args[0]}" )
) from e
raise AttributeError(
f"The State var has no attribute '{name}' or may have been annotated wrongly.",
)
def operation( def operation(
self, self,
@ -429,7 +476,7 @@ class Var(ABC):
else: else:
other = Var.create(other) other = Var.create(other)
type_ = type_ or self.type_ type_ = type_ or self._var_type
if other is None and flip: if other is None and flip:
raise ValueError( raise ValueError(
@ -441,42 +488,42 @@ class Var(ABC):
if other is not None: if other is not None:
# check if the operation between operands is valid. # check if the operation between operands is valid.
if op and not self.is_valid_operation( if op and not self.is_valid_operation(
types.get_base_class(left_operand.type_), # type: ignore types.get_base_class(left_operand._var_type), # type: ignore
types.get_base_class(right_operand.type_), # type: ignore types.get_base_class(right_operand._var_type), # type: ignore
op, op,
): ):
raise TypeError( raise TypeError(
f"Unsupported Operand type(s) for {op}: `{left_operand.full_name}` of type {left_operand.type_.__name__} and `{right_operand.full_name}` of type {right_operand.type_.__name__}" # type: ignore f"Unsupported Operand type(s) for {op}: `{left_operand._var_full_name}` of type {left_operand._var_type.__name__} and `{right_operand._var_full_name}` of type {right_operand._var_type.__name__}" # type: ignore
) )
# apply function to operands # apply function to operands
if fn is not None: if fn is not None:
if invoke_fn: if invoke_fn:
# invoke the function on left operand. # invoke the function on left operand.
operation_name = f"{left_operand.full_name}.{fn}({right_operand.full_name})" # type: ignore operation_name = f"{left_operand._var_full_name}.{fn}({right_operand._var_full_name})" # type: ignore
else: else:
# pass the operands as arguments to the function. # pass the operands as arguments to the function.
operation_name = f"{left_operand.full_name} {op} {right_operand.full_name}" # type: ignore operation_name = f"{left_operand._var_full_name} {op} {right_operand._var_full_name}" # type: ignore
operation_name = f"{fn}({operation_name})" operation_name = f"{fn}({operation_name})"
else: else:
# apply operator to operands (left operand <operator> right_operand) # apply operator to operands (left operand <operator> right_operand)
operation_name = f"{left_operand.full_name} {op} {right_operand.full_name}" # type: ignore operation_name = f"{left_operand._var_full_name} {op} {right_operand._var_full_name}" # type: ignore
operation_name = format.wrap(operation_name, "(") operation_name = format.wrap(operation_name, "(")
else: else:
# apply operator to left operand (<operator> left_operand) # apply operator to left operand (<operator> left_operand)
operation_name = f"{op}{self.full_name}" operation_name = f"{op}{self._var_full_name}"
# apply function to operands # apply function to operands
if fn is not None: if fn is not None:
operation_name = ( operation_name = (
f"{fn}({operation_name})" f"{fn}({operation_name})"
if not invoke_fn if not invoke_fn
else f"{self.full_name}.{fn}()" else f"{self._var_full_name}.{fn}()"
) )
return BaseVar( return BaseVar(
name=operation_name, _var_name=operation_name,
type_=type_, _var_type=type_,
is_local=self.is_local, _var_is_local=self._var_is_local,
) )
@staticmethod @staticmethod
@ -554,12 +601,12 @@ class Var(ABC):
Raises: Raises:
TypeError: If the var is not a list. TypeError: If the var is not a list.
""" """
if not types._issubclass(self.type_, List): if not types._issubclass(self._var_type, List):
raise TypeError(f"Cannot get length of non-list var {self}.") raise TypeError(f"Cannot get length of non-list var {self}.")
return BaseVar( return BaseVar(
name=f"{self.full_name}.length", _var_name=f"{self._var_full_name}.length",
type_=int, _var_type=int,
is_local=self.is_local, _var_is_local=self._var_is_local,
) )
def __eq__(self, other: Var) -> Var: def __eq__(self, other: Var) -> Var:
@ -638,12 +685,12 @@ class Var(ABC):
Returns: Returns:
A var representing the sum. A var representing the sum.
""" """
other_type = other.type_ if isinstance(other, Var) else type(other) other_type = other._var_type if isinstance(other, Var) else type(other)
# For list-list addition, javascript concatenates the content of the lists instead of # For list-list addition, javascript concatenates the content of the lists instead of
# merging the list, and for that reason we use the spread operator available through spreadArraysOrObjects # merging the list, and for that reason we use the spread operator available through spreadArraysOrObjects
# utility function # utility function
if ( if (
types.get_base_class(self.type_) == list types.get_base_class(self._var_type) == list
and types.get_base_class(other_type) == list and types.get_base_class(other_type) == list
): ):
return self.operation(",", other, fn="spreadArraysOrObjects", flip=flip) return self.operation(",", other, fn="spreadArraysOrObjects", flip=flip)
@ -692,10 +739,10 @@ class Var(ABC):
Returns: Returns:
A var representing the product. A var representing the product.
""" """
other_type = other.type_ if isinstance(other, Var) else type(other) other_type = other._var_type if isinstance(other, Var) else type(other)
# For str-int multiplication, we use the repeat function. # For str-int multiplication, we use the repeat function.
# i.e "hello" * 2 is equivalent to "hello".repeat(2) in js. # i.e "hello" * 2 is equivalent to "hello".repeat(2) in js.
if (types.get_base_class(self.type_), types.get_base_class(other_type)) in [ if (types.get_base_class(self._var_type), types.get_base_class(other_type)) in [
(int, str), (int, str),
(str, int), (str, int),
]: ]:
@ -703,16 +750,16 @@ class Var(ABC):
# For list-int multiplication, we use the Array function. # For list-int multiplication, we use the Array function.
# i.e ["hello"] * 2 is equivalent to Array(2).fill().map(() => ["hello"]).flat() in js. # i.e ["hello"] * 2 is equivalent to Array(2).fill().map(() => ["hello"]).flat() in js.
if (types.get_base_class(self.type_), types.get_base_class(other_type)) in [ if (types.get_base_class(self._var_type), types.get_base_class(other_type)) in [
(int, list), (int, list),
(list, int), (list, int),
]: ]:
other_name = other.full_name if isinstance(other, Var) else other other_name = other._var_full_name if isinstance(other, Var) else other
name = f"Array({other_name}).fill().map(() => {self.full_name}).flat()" name = f"Array({other_name}).fill().map(() => {self._var_full_name}).flat()"
return BaseVar( return BaseVar(
name=name, _var_name=name,
type_=str, _var_type=str,
is_local=self.is_local, _var_is_local=self._var_is_local,
) )
return self.operation("*", other) return self.operation("*", other)
@ -830,7 +877,7 @@ class Var(ABC):
>>> var1 = Var.create(True) >>> var1 = Var.create(True)
>>> var2 = Var.create(False) >>> var2 = Var.create(False)
>>> js_code = var1 & var2 >>> js_code = var1 & var2
>>> print(js_code.full_name) >>> print(js_code._var_full_name)
'(true && false)' '(true && false)'
""" """
return self.operation("&&", other, type_=bool) return self.operation("&&", other, type_=bool)
@ -860,7 +907,7 @@ class Var(ABC):
>>> var1 = Var.create(True) >>> var1 = Var.create(True)
>>> var2 = Var.create(False) >>> var2 = Var.create(False)
>>> js_code = var1 & var2 >>> js_code = var1 & var2
>>> print(js_code.full_name) >>> print(js_code._var_full_name)
'(false && true)' '(false && true)'
""" """
return self.operation("&&", other, type_=bool, flip=True) return self.operation("&&", other, type_=bool, flip=True)
@ -888,7 +935,7 @@ class Var(ABC):
>>> var1 = Var.create(True) >>> var1 = Var.create(True)
>>> var2 = Var.create(False) >>> var2 = Var.create(False)
>>> js_code = var1 | var2 >>> js_code = var1 | var2
>>> print(js_code.full_name) >>> print(js_code._var_full_name)
'(true || false)' '(true || false)'
""" """
return self.operation("||", other, type_=bool) return self.operation("||", other, type_=bool)
@ -943,35 +990,37 @@ class Var(ABC):
Returns: Returns:
A var representing the contain check. A var representing the contain check.
""" """
if not (types._issubclass(self.type_, Union[dict, list, tuple, str])): if not (types._issubclass(self._var_type, Union[dict, list, tuple, str])):
raise TypeError( raise TypeError(
f"Var {self.full_name} of type {self.type_} does not support contains check." f"Var {self._var_full_name} of type {self._var_type} does not support contains check."
) )
method = ( method = (
"hasOwnProperty" if types.get_base_class(self.type_) == dict else "includes" "hasOwnProperty"
if types.get_base_class(self._var_type) == dict
else "includes"
) )
if isinstance(other, str): if isinstance(other, str):
other = Var.create(json.dumps(other), is_string=True) other = Var.create(json.dumps(other), _var_is_string=True)
elif not isinstance(other, Var): elif not isinstance(other, Var):
other = Var.create(other) other = Var.create(other)
if types._issubclass(self.type_, Dict): if types._issubclass(self._var_type, Dict):
return BaseVar( return BaseVar(
name=f"{self.full_name}.{method}({other.full_name})", _var_name=f"{self._var_full_name}.{method}({other._var_full_name})",
type_=bool, _var_type=bool,
is_local=self.is_local, _var_is_local=self._var_is_local,
) )
else: # str, list, tuple else: # str, list, tuple
# For strings, the left operand must be a string. # For strings, the left operand must be a string.
if types._issubclass(self.type_, str) and not types._issubclass( if types._issubclass(self._var_type, str) and not types._issubclass(
other.type_, str other._var_type, str
): ):
raise TypeError( raise TypeError(
f"'in <string>' requires string as left operand, not {other.type_}" f"'in <string>' requires string as left operand, not {other._var_type}"
) )
return BaseVar( return BaseVar(
name=f"{self.full_name}.includes({other.full_name})", _var_name=f"{self._var_full_name}.includes({other._var_full_name})",
type_=bool, _var_type=bool,
is_local=self.is_local, _var_is_local=self._var_is_local,
) )
def reverse(self) -> Var: def reverse(self) -> Var:
@ -983,13 +1032,13 @@ class Var(ABC):
Returns: Returns:
A var with the reversed list. A var with the reversed list.
""" """
if not types._issubclass(self.type_, list): if not types._issubclass(self._var_type, list):
raise TypeError(f"Cannot reverse non-list var {self.full_name}.") raise TypeError(f"Cannot reverse non-list var {self._var_full_name}.")
return BaseVar( return BaseVar(
name=f"[...{self.full_name}].reverse()", _var_name=f"[...{self._var_full_name}].reverse()",
type_=self.type_, _var_type=self._var_type,
is_local=self.is_local, _var_is_local=self._var_is_local,
) )
def lower(self) -> Var: def lower(self) -> Var:
@ -1001,15 +1050,15 @@ class Var(ABC):
Raises: Raises:
TypeError: If the var is not a string. TypeError: If the var is not a string.
""" """
if not types._issubclass(self.type_, str): if not types._issubclass(self._var_type, str):
raise TypeError( raise TypeError(
f"Cannot convert non-string var {self.full_name} to lowercase." f"Cannot convert non-string var {self._var_full_name} to lowercase."
) )
return BaseVar( return BaseVar(
name=f"{self.full_name}.toLowerCase()", _var_name=f"{self._var_full_name}.toLowerCase()",
type_=str, _var_type=str,
is_local=self.is_local, _var_is_local=self._var_is_local,
) )
def upper(self) -> Var: def upper(self) -> Var:
@ -1021,15 +1070,15 @@ class Var(ABC):
Raises: Raises:
TypeError: If the var is not a string. TypeError: If the var is not a string.
""" """
if not types._issubclass(self.type_, str): if not types._issubclass(self._var_type, str):
raise TypeError( raise TypeError(
f"Cannot convert non-string var {self.full_name} to uppercase." f"Cannot convert non-string var {self._var_full_name} to uppercase."
) )
return BaseVar( return BaseVar(
name=f"{self.full_name}.toUpperCase()", _var_name=f"{self._var_full_name}.toUpperCase()",
type_=str, _var_type=str,
is_local=self.is_local, _var_is_local=self._var_is_local,
) )
def split(self, other: str | Var[str] = " ") -> Var: def split(self, other: str | Var[str] = " ") -> Var:
@ -1044,15 +1093,15 @@ class Var(ABC):
Raises: Raises:
TypeError: If the var is not a string. TypeError: If the var is not a string.
""" """
if not types._issubclass(self.type_, str): if not types._issubclass(self._var_type, str):
raise TypeError(f"Cannot split non-string var {self.full_name}.") raise TypeError(f"Cannot split non-string var {self._var_full_name}.")
other = Var.create_safe(json.dumps(other)) if isinstance(other, str) else other other = Var.create_safe(json.dumps(other)) if isinstance(other, str) else other
return BaseVar( return BaseVar(
name=f"{self.full_name}.split({other.full_name})", _var_name=f"{self._var_full_name}.split({other._var_full_name})",
type_=list[str], _var_type=list[str],
is_local=self.is_local, _var_is_local=self._var_is_local,
) )
def join(self, other: str | Var[str] | None = None) -> Var: def join(self, other: str | Var[str] | None = None) -> Var:
@ -1067,8 +1116,8 @@ class Var(ABC):
Raises: Raises:
TypeError: If the var is not a list. TypeError: If the var is not a list.
""" """
if not types._issubclass(self.type_, list): if not types._issubclass(self._var_type, list):
raise TypeError(f"Cannot join non-list var {self.full_name}.") raise TypeError(f"Cannot join non-list var {self._var_full_name}.")
if other is None: if other is None:
other = Var.create_safe("") other = Var.create_safe("")
@ -1078,9 +1127,9 @@ class Var(ABC):
other = Var.create_safe(other) other = Var.create_safe(other)
return BaseVar( return BaseVar(
name=f"{self.full_name}.join({other.full_name})", _var_name=f"{self._var_full_name}.join({other._var_full_name})",
type_=str, _var_type=str,
is_local=self.is_local, _var_is_local=self._var_is_local,
) )
def foreach(self, fn: Callable) -> Var: def foreach(self, fn: Callable) -> Var:
@ -1093,13 +1142,13 @@ class Var(ABC):
A var representing foreach operation. A var representing foreach operation.
""" """
arg = BaseVar( arg = BaseVar(
name=get_unique_variable_name(), _var_name=get_unique_variable_name(),
type_=self.type_, _var_type=self._var_type,
) )
return BaseVar( return BaseVar(
name=f"{self.full_name}.map(({arg.name}, i) => {fn(arg, key='i')})", _var_name=f"{self._var_full_name}.map(({arg._var_name}, i) => {fn(arg, key='i')})",
type_=self.type_, _var_type=self._var_type,
is_local=self.is_local, _var_is_local=self._var_is_local,
) )
def to(self, type_: Type) -> Var: def to(self, type_: Type) -> Var:
@ -1112,22 +1161,26 @@ class Var(ABC):
The converted var. The converted var.
""" """
return BaseVar( return BaseVar(
name=self.name, _var_name=self._var_name,
type_=type_, _var_type=type_,
state=self.state, _var_state=self._var_state,
is_local=self.is_local, _var_is_local=self._var_is_local,
) )
@property @property
def full_name(self) -> str: def _var_full_name(self) -> str:
"""Get the full name of the var. """Get the full name of the var.
Returns: Returns:
The full name of the var. The full name of the var.
""" """
return self.name if self.state == "" else ".".join([self.state, self.name]) return (
self._var_name
if self._var_state == ""
else ".".join([self._var_state, self._var_name])
)
def set_state(self, state: Type[State]) -> Any: def _var_set_state(self, state: Type[State]) -> Any:
"""Set the state of the var. """Set the state of the var.
Args: Args:
@ -1136,27 +1189,31 @@ class Var(ABC):
Returns: Returns:
The var with the set state. The var with the set state.
""" """
self.state = state.get_full_name() self._var_state = state.get_full_name()
return self return self
class BaseVar(Var, Base): @dataclasses.dataclass(
eq=False,
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class BaseVar(Var):
"""A base (non-computed) var of the app state.""" """A base (non-computed) var of the app state."""
# The name of the var. # The name of the var.
name: str _var_name: str = dataclasses.field()
# The type of the var. # The type of the var.
type_: Any _var_type: Type = dataclasses.field(default=Any)
# The name of the enclosing state. # The name of the enclosing state.
state: str = "" _var_state: str = dataclasses.field(default="")
# Whether this is a local javascript variable. # Whether this is a local javascript variable.
is_local: bool = False _var_is_local: bool = dataclasses.field(default=False)
# Whether this var is a raw string. # Whether the var is a string literal.
is_string: bool = False _var_is_string: bool = dataclasses.field(default=False)
def __hash__(self) -> int: def __hash__(self) -> int:
"""Define a hash function for a var. """Define a hash function for a var.
@ -1164,7 +1221,7 @@ class BaseVar(Var, Base):
Returns: Returns:
The hash of the var. The hash of the var.
""" """
return hash((self.name, str(self.type_))) return hash((self._var_name, str(self._var_type)))
def get_default_value(self) -> Any: def get_default_value(self) -> Any:
"""Get the default value of the var. """Get the default value of the var.
@ -1176,7 +1233,9 @@ class BaseVar(Var, Base):
ImportError: If the var is a dataframe and pandas is not installed. ImportError: If the var is a dataframe and pandas is not installed.
""" """
type_ = ( type_ = (
self.type_.__origin__ if types.is_generic_alias(self.type_) else self.type_ self._var_type.__origin__
if types.is_generic_alias(self._var_type)
else self._var_type
) )
if issubclass(type_, str): if issubclass(type_, str):
return "" return ""
@ -1210,10 +1269,10 @@ class BaseVar(Var, Base):
Returns: Returns:
The name of the setter function. The name of the setter function.
""" """
setter = constants.SETTER_PREFIX + self.name setter = constants.SETTER_PREFIX + self._var_name
if not include_state or self.state == "": if not include_state or self._var_state == "":
return setter return setter
return ".".join((self.state, setter)) return ".".join((self._var_state, setter))
def get_setter(self) -> Callable[[State, Any], None]: def get_setter(self) -> Callable[[State, Any], None]:
"""Get the var's setter function. """Get the var's setter function.
@ -1229,46 +1288,59 @@ class BaseVar(Var, Base):
state: The state within which we add the setter function. state: The state within which we add the setter function.
value: The value to set. value: The value to set.
""" """
if self.type_ in [int, float]: if self._var_type in [int, float]:
try: try:
value = self.type_(value) value = self._var_type(value)
setattr(state, self.name, value) setattr(state, self._var_name, value)
except ValueError: except ValueError:
console.warn( console.warn(
f"{self.name}: Failed conversion of {value} to '{self.type_.__name__}'. Value not set.", f"{self._var_name}: Failed conversion of {value} to '{self._var_type.__name__}'. Value not set.",
) )
else: else:
setattr(state, self.name, value) setattr(state, self._var_name, value)
setter.__qualname__ = self.get_setter_name() setter.__qualname__ = self.get_setter_name()
return setter return setter
@dataclasses.dataclass(init=False, eq=False)
class ComputedVar(Var, property): class ComputedVar(Var, property):
"""A field with computed getters.""" """A field with computed getters."""
# Whether to track dependencies and cache computed values # Whether to track dependencies and cache computed values
cache: bool = False _cache: bool = dataclasses.field(default=False)
@property def __init__(
def name(self) -> str: self,
"""Get the name of the var. fget: Callable[[State], Any],
fset: Callable[[State, Any], None] | None = None,
fdel: Callable[[State], Any] | None = None,
doc: str | None = None,
**kwargs,
):
"""Initialize a ComputedVar.
Returns: Args:
The name of the var. fget: The getter function.
fset: The setter function.
fdel: The deleter function.
doc: The docstring.
**kwargs: additional attributes to set on the instance
""" """
assert self.fget is not None, "Var must have a getter." property.__init__(self, fget, fset, fdel, doc)
return self.fget.__name__ kwargs["_var_name"] = kwargs.pop("_var_name", fget.__name__)
kwargs["_var_type"] = kwargs.pop("_var_type", self._determine_var_type())
BaseVar.__init__(self, **kwargs) # type: ignore
@property @property
def cache_attr(self) -> str: def _cache_attr(self) -> str:
"""Get the attribute used to cache the value on the instance. """Get the attribute used to cache the value on the instance.
Returns: Returns:
An attribute name. An attribute name.
""" """
return f"__cached_{self.name}" return f"__cached_{self._var_name}"
def __get__(self, instance, owner): def __get__(self, instance, owner):
"""Get the ComputedVar value. """Get the ComputedVar value.
@ -1282,15 +1354,15 @@ class ComputedVar(Var, property):
Returns: Returns:
The value of the var for the given instance. The value of the var for the given instance.
""" """
if instance is None or not self.cache: if instance is None or not self._cache:
return super().__get__(instance, owner) return super().__get__(instance, owner)
# handle caching # handle caching
if not hasattr(instance, self.cache_attr): if not hasattr(instance, self._cache_attr):
setattr(instance, self.cache_attr, super().__get__(instance, owner)) setattr(instance, self._cache_attr, super().__get__(instance, owner))
return getattr(instance, self.cache_attr) return getattr(instance, self._cache_attr)
def deps( def _deps(
self, self,
objclass: Type, objclass: Type,
obj: FunctionType | CodeType | None = None, obj: FunctionType | CodeType | None = None,
@ -1348,7 +1420,7 @@ class ComputedVar(Var, property):
elif self_is_top_of_stack and instruction.opname == "LOAD_METHOD": elif self_is_top_of_stack and instruction.opname == "LOAD_METHOD":
# method call on self # method call on self
d.update( d.update(
self.deps( self._deps(
objclass=objclass, objclass=objclass,
obj=getattr(objclass, instruction.argval), obj=getattr(objclass, instruction.argval),
) )
@ -1359,7 +1431,7 @@ class ComputedVar(Var, property):
# recurse into nested functions / comprehensions, which can reference # recurse into nested functions / comprehensions, which can reference
# instance attributes from the outer scope # instance attributes from the outer scope
d.update( d.update(
self.deps( self._deps(
objclass=objclass, objclass=objclass,
obj=instruction.argval, obj=instruction.argval,
self_name=self_name, self_name=self_name,
@ -1375,10 +1447,9 @@ class ComputedVar(Var, property):
instance: the state instance that needs to recompute the value. instance: the state instance that needs to recompute the value.
""" """
with contextlib.suppress(AttributeError): with contextlib.suppress(AttributeError):
delattr(instance, self.cache_attr) delattr(instance, self._cache_attr)
@property def _determine_var_type(self) -> Type:
def type_(self):
"""Get the type of the var. """Get the type of the var.
Returns: Returns:
@ -1403,7 +1474,7 @@ def cached_var(fget: Callable[[Any], Any]) -> ComputedVar:
ComputedVar that is recomputed when dependencies change. ComputedVar that is recomputed when dependencies change.
""" """
cvar = ComputedVar(fget=fget) cvar = ComputedVar(fget=fget)
cvar.cache = True cvar._cache = True
return cvar return cvar
@ -1468,11 +1539,13 @@ def get_local_storage(key: Var | str | None = None) -> BaseVar:
removal_version="0.3.0", removal_version="0.3.0",
) )
if key is not None: if key is not None:
if not (isinstance(key, Var) and key.type_ == str) and not isinstance(key, str): if not (isinstance(key, Var) and key._var_type == str) and not isinstance(
type_ = type(key) if not isinstance(key, Var) else key.type_ key, str
):
type_ = type(key) if not isinstance(key, Var) else key._var_type
raise TypeError( raise TypeError(
f"Local storage keys can only be of type `str` or `var` of type `str`. Got `{type_}` instead." f"Local storage keys can only be of type `str` or `var` of type `str`. Got `{type_}` instead."
) )
key = key.full_name if isinstance(key, Var) else format.wrap(key, "'") key = key._var_full_name if isinstance(key, Var) else format.wrap(key, "'")
return BaseVar(name=f"localStorage.getItem({key})", type_=str) return BaseVar(_var_name=f"localStorage.getItem({key})", _var_type=str)
return BaseVar(name="getAllLocalStorageItems()", type_=Dict) return BaseVar(_var_name="getAllLocalStorageItems()", _var_type=Dict)

View File

@ -1,7 +1,7 @@
""" Generated with stubgen from mypy, then manually edited, do not regen.""" """ Generated with stubgen from mypy, then manually edited, do not regen."""
from dataclasses import dataclass
from _typeshed import Incomplete from _typeshed import Incomplete
from abc import ABC
from reflex import constants as constants from reflex import constants as constants
from reflex.base import Base as Base from reflex.base import Base as Base
from reflex.state import State as State from reflex.state import State as State
@ -23,19 +23,19 @@ USED_VARIABLES: Incomplete
def get_unique_variable_name() -> str: ... def get_unique_variable_name() -> str: ...
class Var(ABC): class Var:
name: str _var_name: str
type_: Type _var_type: Type
state: str = "" _var_state: str = ""
is_local: bool = False _var_is_local: bool = False
is_string: bool = False _var_is_string: bool = False
@classmethod @classmethod
def create( def create(
cls, value: Any, is_local: bool = False, is_string: bool = False cls, value: Any, _var_is_local: bool = False, _var_is_string: bool = False
) -> Optional[Var]: ... ) -> Optional[Var]: ...
@classmethod @classmethod
def create_safe( def create_safe(
cls, value: Any, is_local: bool = False, is_string: bool = False cls, value: Any, _var_is_local: bool = False, _var_is_string: bool = False
) -> Var: ... ) -> Var: ...
@classmethod @classmethod
def __class_getitem__(cls, type_: str) -> _GenericAlias: ... def __class_getitem__(cls, type_: str) -> _GenericAlias: ...
@ -87,31 +87,31 @@ class Var(ABC):
def foreach(self, fn: Callable) -> Var: ... def foreach(self, fn: Callable) -> Var: ...
def to(self, type_: Type) -> Var: ... def to(self, type_: Type) -> Var: ...
@property @property
def full_name(self) -> str: ... def _var_full_name(self) -> str: ...
def set_state(self, state: Type[State]) -> Any: ... def _var_set_state(self, state: Type[State]) -> Any: ...
class BaseVar(Var, Base): @dataclass(eq=False)
name: str class BaseVar(Var):
type_: Any _var_name: str
state: str = "" _var_type: Any
is_local: bool = False _var_state: str = ""
is_string: bool = False _var_is_local: bool = False
_var_is_string: bool = False
def __hash__(self) -> int: ... def __hash__(self) -> int: ...
def get_default_value(self) -> Any: ... def get_default_value(self) -> Any: ...
def get_setter_name(self, include_state: bool = ...) -> str: ... def get_setter_name(self, include_state: bool = ...) -> str: ...
def get_setter(self) -> Callable[[State, Any], None]: ... def get_setter(self) -> Callable[[State, Any], None]: ...
@dataclass(init=False)
class ComputedVar(Var): class ComputedVar(Var):
cache: bool _var_cache: bool
fget: FunctionType
@property @property
def name(self) -> str: ... def _cache_attr(self) -> str: ...
@property
def cache_attr(self) -> str: ...
def __get__(self, instance, owner): ... def __get__(self, instance, owner): ...
def deps(self, objclass: Type, obj: Optional[FunctionType] = ...) -> Set[str]: ... def _deps(self, objclass: Type, obj: Optional[FunctionType] = ...) -> Set[str]: ...
def mark_dirty(self, instance) -> None: ... def mark_dirty(self, instance) -> None: ...
@property def _determine_var_type(self) -> Type: ...
def type_(self): ...
def __init__(self, func) -> None: ... def __init__(self, func) -> None: ...
def cached_var(fget: Callable[[Any], Any]) -> ComputedVar: ... def cached_var(fget: Callable[[Any], Any]) -> ComputedVar: ...

View File

@ -34,7 +34,7 @@ def test_validate_data_table(data_table_state: rx.Var, expected):
expected: expected var name. expected: expected var name.
""" """
if not types.is_dataframe(data_table_state.data.type_): if not types.is_dataframe(data_table_state.data._var_type):
data_table_component = DataTable.create( data_table_component = DataTable.create(
data=data_table_state.data, columns=data_table_state.columns data=data_table_state.data, columns=data_table_state.columns
) )

View File

@ -51,7 +51,9 @@ def test_render_child_props():
)._render() )._render()
assert tag.props["sx"] == {"foo": "bar", "baz": "quuc"} assert tag.props["sx"] == {"foo": "bar", "baz": "quuc"}
assert tag.props["value"].equals( assert tag.props["value"].equals(
BaseVar(name="real", type_=str, is_local=True, is_string=False) BaseVar(
_var_name="real", _var_type=str, _var_is_local=True, _var_is_string=False
)
) )
assert len(tag.props["onChange"].events) == 1 assert len(tag.props["onChange"].events) == 1
assert tag.props["onChange"].events[0].handler == S.on_change assert tag.props["onChange"].events[0].handler == S.on_change
@ -86,11 +88,13 @@ def test_render_child_props_recursive():
)._render() )._render()
assert tag.props["sx"] == {"foo": "bar", "baz": "quuc"} assert tag.props["sx"] == {"foo": "bar", "baz": "quuc"}
assert tag.props["value"].equals( assert tag.props["value"].equals(
BaseVar(name="outer", type_=str, is_local=True, is_string=False) BaseVar(
_var_name="outer", _var_type=str, _var_is_local=True, _var_is_string=False
)
) )
assert tag.props["forceNotifyOnBlur"].name == "false" assert tag.props["forceNotifyOnBlur"]._var_name == "false"
assert tag.props["forceNotifyByEnter"].name == "false" assert tag.props["forceNotifyByEnter"]._var_name == "false"
assert tag.props["debounceTimeout"].name == "42" assert tag.props["debounceTimeout"]._var_name == "42"
assert len(tag.props["onChange"].events) == 1 assert len(tag.props["onChange"].events) == 1
assert tag.props["onChange"].events[0].handler == S.on_change assert tag.props["onChange"].events[0].handler == S.on_change
assert tag.contents == "" assert tag.contents == ""
@ -102,7 +106,7 @@ def test_full_control_implicit_debounce():
value=S.value, value=S.value,
on_change=S.on_change, on_change=S.on_change,
)._render() )._render()
assert tag.props["debounceTimeout"].name == "50" assert tag.props["debounceTimeout"]._var_name == "50"
assert len(tag.props["onChange"].events) == 1 assert len(tag.props["onChange"].events) == 1
assert tag.props["onChange"].events[0].handler == S.on_change assert tag.props["onChange"].events[0].handler == S.on_change
assert tag.contents == "" assert tag.contents == ""
@ -114,7 +118,7 @@ def test_full_control_implicit_debounce_text_area():
value=S.value, value=S.value,
on_change=S.on_change, on_change=S.on_change,
)._render() )._render()
assert tag.props["debounceTimeout"].name == "50" assert tag.props["debounceTimeout"]._var_name == "50"
assert len(tag.props["onChange"].events) == 1 assert len(tag.props["onChange"].events) == 1
assert tag.props["onChange"].events[0].handler == S.on_change assert tag.props["onChange"].events[0].handler == S.on_change
assert tag.contents == "" assert tag.contents == ""

View File

@ -105,7 +105,7 @@ def test_cond_no_else():
comp = comp.children[0] comp = comp.children[0]
assert isinstance(comp, Cond) assert isinstance(comp, Cond)
assert comp.cond._decode() is True # type: ignore assert comp.cond._decode() is True # type: ignore
assert comp.comp1 == Fragment.create(Text.create("hello")) assert comp.comp1.render() == Fragment.create(Text.create("hello")).render()
assert comp.comp2 == Fragment.create() assert comp.comp2 == Fragment.create()
# Props do not support the use of cond without else # Props do not support the use of cond without else

View File

@ -186,6 +186,6 @@ def test_foreach_render(state_var, render_fn, render_dict):
rend = component.render() rend = component.render()
arg_index = rend["arg_index"] arg_index = rend["arg_index"]
assert rend["iterable_state"] == render_dict["iterable_state"] assert rend["iterable_state"] == render_dict["iterable_state"]
assert arg_index.name == render_dict["arg_index"] assert arg_index._var_name == render_dict["arg_index"]
assert arg_index.type_ == int assert arg_index._var_type == int
assert rend["iterable_type"] == render_dict["iterable_type"] assert rend["iterable_type"] == render_dict["iterable_type"]

View File

@ -43,7 +43,7 @@ try:
pil_image: The image to serialize. pil_image: The image to serialize.
""" """
image = Image.create(src=pil_image) image = Image.create(src=pil_image)
assert str(image.src.name) == serialize_image(pil_image) # type: ignore assert str(image.src._var_name) == serialize_image(pil_image) # type: ignore
def test_render(pil_image: Img): def test_render(pil_image: Img):
"""Test that rendering an image works. """Test that rendering an image works.
@ -52,7 +52,7 @@ try:
pil_image: The image to serialize. pil_image: The image to serialize.
""" """
image = Image.create(src=pil_image) image = Image.create(src=pil_image)
assert image.src.is_string # type: ignore assert image.src._var_is_string # type: ignore
except ImportError: except ImportError:

View File

@ -109,7 +109,7 @@ def test_format_cond_tag():
tag = CondTag( tag = CondTag(
true_value=dict(Tag(name="h1", contents="True content")), true_value=dict(Tag(name="h1", contents="True content")),
false_value=dict(Tag(name="h2", contents="False content")), false_value=dict(Tag(name="h2", contents="False content")),
cond=BaseVar(name="logged_in", type_=bool), cond=BaseVar(_var_name="logged_in", _var_type=bool),
) )
tag_dict = dict(tag) tag_dict = dict(tag)
cond, true_value, false_value = ( cond, true_value, false_value = (
@ -117,8 +117,8 @@ def test_format_cond_tag():
tag_dict["true_value"], tag_dict["true_value"],
tag_dict["false_value"], tag_dict["false_value"],
) )
assert cond.name == "logged_in" assert cond._var_name == "logged_in"
assert cond.type_ == bool assert cond._var_type == bool
assert true_value["name"] == "h1" assert true_value["name"] == "h1"
assert true_value["contents"] == "True content" assert true_value["contents"] == "True content"

View File

@ -250,7 +250,7 @@ def test_add_page_set_route_dynamic(app: App, index_page, windows_platform: bool
app.add_page(index_page, route=route) app.add_page(index_page, route=route)
assert set(app.pages.keys()) == {"test/[dynamic]"} assert set(app.pages.keys()) == {"test/[dynamic]"}
assert "dynamic" in app.state.computed_vars assert "dynamic" in app.state.computed_vars
assert app.state.computed_vars["dynamic"].deps(objclass=DefaultState) == { assert app.state.computed_vars["dynamic"]._deps(objclass=DefaultState) == {
constants.ROUTER_DATA constants.ROUTER_DATA
} }
assert constants.ROUTER_DATA in app.state().computed_var_dependencies assert constants.ROUTER_DATA in app.state().computed_var_dependencies
@ -869,7 +869,7 @@ async def test_dynamic_route_var_route_change_completed_on_load(
app.add_page(index_page, route=route, on_load=DynamicState.on_load) # type: ignore app.add_page(index_page, route=route, on_load=DynamicState.on_load) # type: ignore
assert arg_name in app.state.vars assert arg_name in app.state.vars
assert arg_name in app.state.computed_vars assert arg_name in app.state.computed_vars
assert app.state.computed_vars[arg_name].deps(objclass=DynamicState) == { assert app.state.computed_vars[arg_name]._deps(objclass=DynamicState) == {
constants.ROUTER_DATA constants.ROUTER_DATA
} }
assert constants.ROUTER_DATA in app.state().computed_var_dependencies assert constants.ROUTER_DATA in app.state().computed_var_dependencies

View File

@ -197,11 +197,11 @@ def test_base_class_vars(test_state):
continue continue
prop = getattr(cls, field) prop = getattr(cls, field)
assert isinstance(prop, BaseVar) assert isinstance(prop, BaseVar)
assert prop.name == field assert prop._var_name == field
assert cls.num1.type_ == int assert cls.num1._var_type == int
assert cls.num2.type_ == float assert cls.num2._var_type == float
assert cls.key.type_ == str assert cls.key._var_type == str
def test_computed_class_var(test_state): def test_computed_class_var(test_state):
@ -211,7 +211,7 @@ def test_computed_class_var(test_state):
test_state: A state. test_state: A state.
""" """
cls = type(test_state) cls = type(test_state)
vars = [(prop.name, prop.type_) for prop in cls.computed_vars.values()] vars = [(prop._var_name, prop._var_type) for prop in cls.computed_vars.values()]
assert ("sum", float) in vars assert ("sum", float) in vars
assert ("upper", str) in vars assert ("upper", str) in vars
@ -416,11 +416,13 @@ def test_set_class_var():
"""Test setting the var of a class.""" """Test setting the var of a class."""
with pytest.raises(AttributeError): with pytest.raises(AttributeError):
TestState.num3 # type: ignore TestState.num3 # type: ignore
TestState._set_var(BaseVar(name="num3", type_=int).set_state(TestState)) TestState._set_var(
BaseVar(_var_name="num3", _var_type=int)._var_set_state(TestState)
)
var = TestState.num3 # type: ignore var = TestState.num3 # type: ignore
assert var.name == "num3" assert var._var_name == "num3"
assert var.type_ == int assert var._var_type == int
assert var.state == TestState.get_full_name() assert var._var_state == TestState.get_full_name()
def test_set_parent_and_substates(test_state, child_state, grandchild_state): def test_set_parent_and_substates(test_state, child_state, grandchild_state):
@ -984,7 +986,7 @@ def test_conditional_computed_vars():
assert ms._dirty_computed_vars(from_vars={"flag"}) == {"rendered_var"} assert ms._dirty_computed_vars(from_vars={"flag"}) == {"rendered_var"}
assert ms._dirty_computed_vars(from_vars={"t2"}) == {"rendered_var"} assert ms._dirty_computed_vars(from_vars={"t2"}) == {"rendered_var"}
assert ms._dirty_computed_vars(from_vars={"t1"}) == {"rendered_var"} assert ms._dirty_computed_vars(from_vars={"t1"}) == {"rendered_var"}
assert ms.computed_vars["rendered_var"].deps(objclass=MainState) == { assert ms.computed_vars["rendered_var"]._deps(objclass=MainState) == {
"flag", "flag",
"t1", "t1",
"t2", "t2",

View File

@ -16,11 +16,11 @@ from reflex.vars import (
) )
test_vars = [ test_vars = [
BaseVar(name="prop1", type_=int), BaseVar(_var_name="prop1", _var_type=int),
BaseVar(name="key", type_=str), BaseVar(_var_name="key", _var_type=str),
BaseVar(name="value", type_=str, state="state"), BaseVar(_var_name="value", _var_type=str, _var_state="state"),
BaseVar(name="local", type_=str, state="state", is_local=True), BaseVar(_var_name="local", _var_type=str, _var_state="state", _var_is_local=True),
BaseVar(name="local2", type_=str, is_local=True), BaseVar(_var_name="local2", _var_type=str, _var_is_local=True),
] ]
test_import_vars = [ImportVar(tag="DataGrid"), ImportVar(tag="DataGrid", alias="Grid")] test_import_vars = [ImportVar(tag="DataGrid"), ImportVar(tag="DataGrid", alias="Grid")]
@ -124,7 +124,7 @@ def test_full_name(prop, expected):
prop: The var to test. prop: The var to test.
expected: The expected full name. expected: The expected full name.
""" """
assert prop.full_name == expected assert prop._var_full_name == expected
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -147,14 +147,14 @@ def test_str(prop, expected):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"prop,expected", "prop,expected",
[ [
(BaseVar(name="p", type_=int), 0), (BaseVar(_var_name="p", _var_type=int), 0),
(BaseVar(name="p", type_=float), 0.0), (BaseVar(_var_name="p", _var_type=float), 0.0),
(BaseVar(name="p", type_=str), ""), (BaseVar(_var_name="p", _var_type=str), ""),
(BaseVar(name="p", type_=bool), False), (BaseVar(_var_name="p", _var_type=bool), False),
(BaseVar(name="p", type_=list), []), (BaseVar(_var_name="p", _var_type=list), []),
(BaseVar(name="p", type_=dict), {}), (BaseVar(_var_name="p", _var_type=dict), {}),
(BaseVar(name="p", type_=tuple), ()), (BaseVar(_var_name="p", _var_type=tuple), ()),
(BaseVar(name="p", type_=set), set()), (BaseVar(_var_name="p", _var_type=set), set()),
], ],
) )
def test_default_value(prop, expected): def test_default_value(prop, expected):
@ -194,13 +194,13 @@ def test_get_setter(prop, expected):
"value,expected", "value,expected",
[ [
(None, None), (None, None),
(1, BaseVar(name="1", type_=int, is_local=True)), (1, BaseVar(_var_name="1", _var_type=int, _var_is_local=True)),
("key", BaseVar(name="key", type_=str, is_local=True)), ("key", BaseVar(_var_name="key", _var_type=str, _var_is_local=True)),
(3.14, BaseVar(name="3.14", type_=float, is_local=True)), (3.14, BaseVar(_var_name="3.14", _var_type=float, _var_is_local=True)),
([1, 2, 3], BaseVar(name="[1, 2, 3]", type_=list, is_local=True)), ([1, 2, 3], BaseVar(_var_name="[1, 2, 3]", _var_type=list, _var_is_local=True)),
( (
{"a": 1, "b": 2}, {"a": 1, "b": 2},
BaseVar(name='{"a": 1, "b": 2}', type_=dict, is_local=True), BaseVar(_var_name='{"a": 1, "b": 2}', _var_type=dict, _var_is_local=True),
), ),
], ],
) )
@ -232,9 +232,9 @@ def test_create_type_error():
def v(value) -> Var: def v(value) -> Var:
val = ( val = (
Var.create(json.dumps(value), is_string=True, is_local=False) Var.create(json.dumps(value), _var_is_string=True, _var_is_local=False)
if isinstance(value, str) if isinstance(value, str)
else Var.create(value, is_local=False) else Var.create(value, _var_is_local=False)
) )
assert val is not None assert val is not None
return val return val
@ -264,7 +264,8 @@ def test_basic_operations(TestObj):
assert str(v([1, 2, 3])[v(0)]) == "{[1, 2, 3].at(0)}" assert str(v([1, 2, 3])[v(0)]) == "{[1, 2, 3].at(0)}"
assert str(v({"a": 1, "b": 2})["a"]) == '{{"a": 1, "b": 2}["a"]}' assert str(v({"a": 1, "b": 2})["a"]) == '{{"a": 1, "b": 2}["a"]}'
assert ( assert (
str(BaseVar(name="foo", state="state", type_=TestObj).bar) == "{state.foo.bar}" str(BaseVar(_var_name="foo", _var_state="state", _var_type=TestObj).bar)
== "{state.foo.bar}"
) )
assert str(abs(v(1))) == "{Math.abs(1)}" assert str(abs(v(1))) == "{Math.abs(1)}"
assert str(v([1, 2, 3]).length()) == "{[1, 2, 3].length}" assert str(v([1, 2, 3]).length()) == "{[1, 2, 3].length}"
@ -274,10 +275,13 @@ def test_basic_operations(TestObj):
assert str(v([1, 2, 3]).reverse()) == "{[...[1, 2, 3]].reverse()}" assert str(v([1, 2, 3]).reverse()) == "{[...[1, 2, 3]].reverse()}"
assert str(v(["1", "2", "3"]).reverse()) == '{[...["1", "2", "3"]].reverse()}' assert str(v(["1", "2", "3"]).reverse()) == '{[...["1", "2", "3"]].reverse()}'
assert ( assert (
str(BaseVar(name="foo", state="state", type_=list).reverse()) str(BaseVar(_var_name="foo", _var_state="state", _var_type=list).reverse())
== "{[...state.foo].reverse()}" == "{[...state.foo].reverse()}"
) )
assert str(BaseVar(name="foo", type_=list).reverse()) == "{[...foo].reverse()}" assert (
str(BaseVar(_var_name="foo", _var_type=list).reverse())
== "{[...foo].reverse()}"
)
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -285,12 +289,12 @@ def test_basic_operations(TestObj):
[ [
(v([1, 2, 3]), "[1, 2, 3]"), (v([1, 2, 3]), "[1, 2, 3]"),
(v(["1", "2", "3"]), '["1", "2", "3"]'), (v(["1", "2", "3"]), '["1", "2", "3"]'),
(BaseVar(name="foo", state="state", type_=list), "state.foo"), (BaseVar(_var_name="foo", _var_state="state", _var_type=list), "state.foo"),
(BaseVar(name="foo", type_=list), "foo"), (BaseVar(_var_name="foo", _var_type=list), "foo"),
(v((1, 2, 3)), "[1, 2, 3]"), (v((1, 2, 3)), "[1, 2, 3]"),
(v(("1", "2", "3")), '["1", "2", "3"]'), (v(("1", "2", "3")), '["1", "2", "3"]'),
(BaseVar(name="foo", state="state", type_=tuple), "state.foo"), (BaseVar(_var_name="foo", _var_state="state", _var_type=tuple), "state.foo"),
(BaseVar(name="foo", type_=tuple), "foo"), (BaseVar(_var_name="foo", _var_type=tuple), "foo"),
], ],
) )
def test_list_tuple_contains(var, expected): def test_list_tuple_contains(var, expected):
@ -298,8 +302,8 @@ def test_list_tuple_contains(var, expected):
assert str(var.contains("1")) == f'{{{expected}.includes("1")}}' assert str(var.contains("1")) == f'{{{expected}.includes("1")}}'
assert str(var.contains(v(1))) == f"{{{expected}.includes(1)}}" assert str(var.contains(v(1))) == f"{{{expected}.includes(1)}}"
assert str(var.contains(v("1"))) == f'{{{expected}.includes("1")}}' assert str(var.contains(v("1"))) == f'{{{expected}.includes("1")}}'
other_state_var = BaseVar(name="other", state="state", type_=str) other_state_var = BaseVar(_var_name="other", _var_state="state", _var_type=str)
other_var = BaseVar(name="other", type_=str) other_var = BaseVar(_var_name="other", _var_type=str)
assert str(var.contains(other_state_var)) == f"{{{expected}.includes(state.other)}}" assert str(var.contains(other_state_var)) == f"{{{expected}.includes(state.other)}}"
assert str(var.contains(other_var)) == f"{{{expected}.includes(other)}}" assert str(var.contains(other_var)) == f"{{{expected}.includes(other)}}"
@ -308,15 +312,15 @@ def test_list_tuple_contains(var, expected):
"var, expected", "var, expected",
[ [
(v("123"), json.dumps("123")), (v("123"), json.dumps("123")),
(BaseVar(name="foo", state="state", type_=str), "state.foo"), (BaseVar(_var_name="foo", _var_state="state", _var_type=str), "state.foo"),
(BaseVar(name="foo", type_=str), "foo"), (BaseVar(_var_name="foo", _var_type=str), "foo"),
], ],
) )
def test_str_contains(var, expected): def test_str_contains(var, expected):
assert str(var.contains("1")) == f'{{{expected}.includes("1")}}' assert str(var.contains("1")) == f'{{{expected}.includes("1")}}'
assert str(var.contains(v("1"))) == f'{{{expected}.includes("1")}}' assert str(var.contains(v("1"))) == f'{{{expected}.includes("1")}}'
other_state_var = BaseVar(name="other", state="state", type_=str) other_state_var = BaseVar(_var_name="other", _var_state="state", _var_type=str)
other_var = BaseVar(name="other", type_=str) other_var = BaseVar(_var_name="other", _var_type=str)
assert str(var.contains(other_state_var)) == f"{{{expected}.includes(state.other)}}" assert str(var.contains(other_state_var)) == f"{{{expected}.includes(state.other)}}"
assert str(var.contains(other_var)) == f"{{{expected}.includes(other)}}" assert str(var.contains(other_var)) == f"{{{expected}.includes(other)}}"
@ -325,8 +329,8 @@ def test_str_contains(var, expected):
"var, expected", "var, expected",
[ [
(v({"a": 1, "b": 2}), '{"a": 1, "b": 2}'), (v({"a": 1, "b": 2}), '{"a": 1, "b": 2}'),
(BaseVar(name="foo", state="state", type_=dict), "state.foo"), (BaseVar(_var_name="foo", _var_state="state", _var_type=dict), "state.foo"),
(BaseVar(name="foo", type_=dict), "foo"), (BaseVar(_var_name="foo", _var_type=dict), "foo"),
], ],
) )
def test_dict_contains(var, expected): def test_dict_contains(var, expected):
@ -334,8 +338,8 @@ def test_dict_contains(var, expected):
assert str(var.contains("1")) == f'{{{expected}.hasOwnProperty("1")}}' assert str(var.contains("1")) == f'{{{expected}.hasOwnProperty("1")}}'
assert str(var.contains(v(1))) == f"{{{expected}.hasOwnProperty(1)}}" assert str(var.contains(v(1))) == f"{{{expected}.hasOwnProperty(1)}}"
assert str(var.contains(v("1"))) == f'{{{expected}.hasOwnProperty("1")}}' assert str(var.contains(v("1"))) == f'{{{expected}.hasOwnProperty("1")}}'
other_state_var = BaseVar(name="other", state="state", type_=str) other_state_var = BaseVar(_var_name="other", _var_state="state", _var_type=str)
other_var = BaseVar(name="other", type_=str) other_var = BaseVar(_var_name="other", _var_type=str)
assert ( assert (
str(var.contains(other_state_var)) str(var.contains(other_state_var))
== f"{{{expected}.hasOwnProperty(state.other)}}" == f"{{{expected}.hasOwnProperty(state.other)}}"
@ -346,9 +350,9 @@ def test_dict_contains(var, expected):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"var", "var",
[ [
BaseVar(name="list", type_=List[int]), BaseVar(_var_name="list", _var_type=List[int]),
BaseVar(name="tuple", type_=Tuple[int, int]), BaseVar(_var_name="tuple", _var_type=Tuple[int, int]),
BaseVar(name="str", type_=str), BaseVar(_var_name="str", _var_type=str),
], ],
) )
def test_var_indexing_lists(var): def test_var_indexing_lists(var):
@ -358,49 +362,70 @@ def test_var_indexing_lists(var):
var : The str, list or tuple base var. var : The str, list or tuple base var.
""" """
# Test basic indexing. # Test basic indexing.
assert str(var[0]) == f"{{{var.name}.at(0)}}" assert str(var[0]) == f"{{{var._var_name}.at(0)}}"
assert str(var[1]) == f"{{{var.name}.at(1)}}" assert str(var[1]) == f"{{{var._var_name}.at(1)}}"
# Test negative indexing. # Test negative indexing.
assert str(var[-1]) == f"{{{var.name}.at(-1)}}" assert str(var[-1]) == f"{{{var._var_name}.at(-1)}}"
@pytest.mark.parametrize( @pytest.mark.parametrize(
"var, index", "var, index",
[ [
(BaseVar(name="lst", type_=List[int]), [1, 2]), (BaseVar(_var_name="lst", _var_type=List[int]), [1, 2]),
(BaseVar(name="lst", type_=List[int]), {"name": "dict"}), (BaseVar(_var_name="lst", _var_type=List[int]), {"name": "dict"}),
(BaseVar(name="lst", type_=List[int]), {"set"}), (BaseVar(_var_name="lst", _var_type=List[int]), {"set"}),
( (
BaseVar(name="lst", type_=List[int]), BaseVar(_var_name="lst", _var_type=List[int]),
( (
1, 1,
2, 2,
), ),
), ),
(BaseVar(name="lst", type_=List[int]), 1.5), (BaseVar(_var_name="lst", _var_type=List[int]), 1.5),
(BaseVar(name="lst", type_=List[int]), "str"), (BaseVar(_var_name="lst", _var_type=List[int]), "str"),
(BaseVar(name="lst", type_=List[int]), BaseVar(name="string_var", type_=str)),
(BaseVar(name="lst", type_=List[int]), BaseVar(name="float_var", type_=float)),
( (
BaseVar(name="lst", type_=List[int]), BaseVar(_var_name="lst", _var_type=List[int]),
BaseVar(name="list_var", type_=List[int]), BaseVar(_var_name="string_var", _var_type=str),
), ),
(BaseVar(name="lst", type_=List[int]), BaseVar(name="set_var", type_=Set[str])),
( (
BaseVar(name="lst", type_=List[int]), BaseVar(_var_name="lst", _var_type=List[int]),
BaseVar(name="dict_var", type_=Dict[str, str]), BaseVar(_var_name="float_var", _var_type=float),
),
(
BaseVar(_var_name="lst", _var_type=List[int]),
BaseVar(_var_name="list_var", _var_type=List[int]),
),
(
BaseVar(_var_name="lst", _var_type=List[int]),
BaseVar(_var_name="set_var", _var_type=Set[str]),
),
(
BaseVar(_var_name="lst", _var_type=List[int]),
BaseVar(_var_name="dict_var", _var_type=Dict[str, str]),
),
(BaseVar(_var_name="str", _var_type=str), [1, 2]),
(BaseVar(_var_name="lst", _var_type=str), {"name": "dict"}),
(BaseVar(_var_name="lst", _var_type=str), {"set"}),
(
BaseVar(_var_name="lst", _var_type=str),
BaseVar(_var_name="string_var", _var_type=str),
),
(
BaseVar(_var_name="lst", _var_type=str),
BaseVar(_var_name="float_var", _var_type=float),
),
(BaseVar(_var_name="str", _var_type=Tuple[str]), [1, 2]),
(BaseVar(_var_name="lst", _var_type=Tuple[str]), {"name": "dict"}),
(BaseVar(_var_name="lst", _var_type=Tuple[str]), {"set"}),
(
BaseVar(_var_name="lst", _var_type=Tuple[str]),
BaseVar(_var_name="string_var", _var_type=str),
),
(
BaseVar(_var_name="lst", _var_type=Tuple[str]),
BaseVar(_var_name="float_var", _var_type=float),
), ),
(BaseVar(name="str", type_=str), [1, 2]),
(BaseVar(name="lst", type_=str), {"name": "dict"}),
(BaseVar(name="lst", type_=str), {"set"}),
(BaseVar(name="lst", type_=str), BaseVar(name="string_var", type_=str)),
(BaseVar(name="lst", type_=str), BaseVar(name="float_var", type_=float)),
(BaseVar(name="str", type_=Tuple[str]), [1, 2]),
(BaseVar(name="lst", type_=Tuple[str]), {"name": "dict"}),
(BaseVar(name="lst", type_=Tuple[str]), {"set"}),
(BaseVar(name="lst", type_=Tuple[str]), BaseVar(name="string_var", type_=str)),
(BaseVar(name="lst", type_=Tuple[str]), BaseVar(name="float_var", type_=float)),
], ],
) )
def test_var_unsupported_indexing_lists(var, index): def test_var_unsupported_indexing_lists(var, index):
@ -417,9 +442,9 @@ def test_var_unsupported_indexing_lists(var, index):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"var", "var",
[ [
BaseVar(name="lst", type_=List[int]), BaseVar(_var_name="lst", _var_type=List[int]),
BaseVar(name="tuple", type_=Tuple[int, int]), BaseVar(_var_name="tuple", _var_type=Tuple[int, int]),
BaseVar(name="str", type_=str), BaseVar(_var_name="str", _var_type=str),
], ],
) )
def test_var_list_slicing(var): def test_var_list_slicing(var):
@ -428,14 +453,14 @@ def test_var_list_slicing(var):
Args: Args:
var : The str, list or tuple base var. var : The str, list or tuple base var.
""" """
assert str(var[:1]) == f"{{{var.name}.slice(0, 1)}}" assert str(var[:1]) == f"{{{var._var_name}.slice(0, 1)}}"
assert str(var[:1]) == f"{{{var.name}.slice(0, 1)}}" assert str(var[:1]) == f"{{{var._var_name}.slice(0, 1)}}"
assert str(var[:]) == f"{{{var.name}.slice(0, undefined)}}" assert str(var[:]) == f"{{{var._var_name}.slice(0, undefined)}}"
def test_dict_indexing(): def test_dict_indexing():
"""Test that we can index into dict vars.""" """Test that we can index into dict vars."""
dct = BaseVar(name="dct", type_=Dict[str, int]) dct = BaseVar(_var_name="dct", _var_type=Dict[str, int])
# Check correct indexing. # Check correct indexing.
assert str(dct["a"]) == '{dct["a"]}' assert str(dct["a"]) == '{dct["a"]}'
@ -446,66 +471,66 @@ def test_dict_indexing():
"var, index", "var, index",
[ [
( (
BaseVar(name="dict", type_=Dict[str, str]), BaseVar(_var_name="dict", _var_type=Dict[str, str]),
[1, 2], [1, 2],
), ),
( (
BaseVar(name="dict", type_=Dict[str, str]), BaseVar(_var_name="dict", _var_type=Dict[str, str]),
{"name": "dict"}, {"name": "dict"},
), ),
( (
BaseVar(name="dict", type_=Dict[str, str]), BaseVar(_var_name="dict", _var_type=Dict[str, str]),
{"set"}, {"set"},
), ),
( (
BaseVar(name="dict", type_=Dict[str, str]), BaseVar(_var_name="dict", _var_type=Dict[str, str]),
( (
1, 1,
2, 2,
), ),
), ),
( (
BaseVar(name="lst", type_=Dict[str, str]), BaseVar(_var_name="lst", _var_type=Dict[str, str]),
BaseVar(name="list_var", type_=List[int]), BaseVar(_var_name="list_var", _var_type=List[int]),
), ),
( (
BaseVar(name="lst", type_=Dict[str, str]), BaseVar(_var_name="lst", _var_type=Dict[str, str]),
BaseVar(name="set_var", type_=Set[str]), BaseVar(_var_name="set_var", _var_type=Set[str]),
), ),
( (
BaseVar(name="lst", type_=Dict[str, str]), BaseVar(_var_name="lst", _var_type=Dict[str, str]),
BaseVar(name="dict_var", type_=Dict[str, str]), BaseVar(_var_name="dict_var", _var_type=Dict[str, str]),
), ),
( (
BaseVar(name="df", type_=DataFrame), BaseVar(_var_name="df", _var_type=DataFrame),
[1, 2], [1, 2],
), ),
( (
BaseVar(name="df", type_=DataFrame), BaseVar(_var_name="df", _var_type=DataFrame),
{"name": "dict"}, {"name": "dict"},
), ),
( (
BaseVar(name="df", type_=DataFrame), BaseVar(_var_name="df", _var_type=DataFrame),
{"set"}, {"set"},
), ),
( (
BaseVar(name="df", type_=DataFrame), BaseVar(_var_name="df", _var_type=DataFrame),
( (
1, 1,
2, 2,
), ),
), ),
( (
BaseVar(name="df", type_=DataFrame), BaseVar(_var_name="df", _var_type=DataFrame),
BaseVar(name="list_var", type_=List[int]), BaseVar(_var_name="list_var", _var_type=List[int]),
), ),
( (
BaseVar(name="df", type_=DataFrame), BaseVar(_var_name="df", _var_type=DataFrame),
BaseVar(name="set_var", type_=Set[str]), BaseVar(_var_name="set_var", _var_type=Set[str]),
), ),
( (
BaseVar(name="df", type_=DataFrame), BaseVar(_var_name="df", _var_type=DataFrame),
BaseVar(name="dict_var", type_=Dict[str, str]), BaseVar(_var_name="dict_var", _var_type=Dict[str, str]),
), ),
], ],
) )
@ -577,8 +602,7 @@ def test_computed_var_with_annotation_error(request, fixture, full_name):
state.var_with_annotation.foo state.var_with_annotation.foo
assert ( assert (
err.value.args[0] err.value.args[0]
== f"The State var `{full_name}` has no attribute 'foo' or may have been annotated wrongly.\n" == f"The State var `{full_name}` has no attribute 'foo' or may have been annotated wrongly."
f"original message: 'ComputedVar' object has no attribute 'foo'"
) )
@ -605,16 +629,19 @@ def test_import_var(import_var, expected):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"key, expected", "key, expected",
[ [
("test_key", BaseVar(name="localStorage.getItem('test_key')", type_=str)),
( (
BaseVar(name="key_var", type_=str), "test_key",
BaseVar(name="localStorage.getItem(key_var)", type_=str), BaseVar(_var_name="localStorage.getItem('test_key')", _var_type=str),
),
(
BaseVar(_var_name="key_var", _var_type=str),
BaseVar(_var_name="localStorage.getItem(key_var)", _var_type=str),
), ),
( (
BaseState.val, BaseState.val,
BaseVar(name="localStorage.getItem(base_state.val)", type_=str), BaseVar(_var_name="localStorage.getItem(base_state.val)", _var_type=str),
), ),
(None, BaseVar(name="getAllLocalStorageItems()", type_=Dict)), (None, BaseVar(_var_name="getAllLocalStorageItems()", _var_type=Dict)),
], ],
) )
def test_get_local_storage(key, expected): def test_get_local_storage(key, expected):
@ -626,8 +653,8 @@ def test_get_local_storage(key, expected):
""" """
local_storage = get_local_storage(key) local_storage = get_local_storage(key)
assert local_storage.name == expected.name assert local_storage._var_name == expected._var_name
assert local_storage.type_ == expected.type_ assert local_storage._var_type == expected._var_type
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -636,8 +663,8 @@ def test_get_local_storage(key, expected):
["list", "values"], ["list", "values"],
{"name": "dict"}, {"name": "dict"},
10, 10,
BaseVar(name="key_var", type_=List), BaseVar(_var_name="key_var", _var_type=List),
BaseVar(name="key_var", type_=Dict[str, str]), BaseVar(_var_name="key_var", _var_type=Dict[str, str]),
], ],
) )
def test_get_local_storage_raise_error(key): def test_get_local_storage_raise_error(key):
@ -648,7 +675,7 @@ def test_get_local_storage_raise_error(key):
""" """
with pytest.raises(TypeError) as err: with pytest.raises(TypeError) as err:
get_local_storage(key) get_local_storage(key)
type_ = type(key) if not isinstance(key, Var) else key.type_ type_ = type(key) if not isinstance(key, Var) else key._var_type
assert ( assert (
err.value.args[0] err.value.args[0]
== f"Local storage keys can only be of type `str` or `var` of type `str`. Got `{type_}` instead." == f"Local storage keys can only be of type `str` or `var` of type `str`. Got `{type_}` instead."
@ -658,13 +685,13 @@ def test_get_local_storage_raise_error(key):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"out, expected", "out, expected",
[ [
(f"{BaseVar(name='var', type_=str)}", "${var}"), (f"{BaseVar(_var_name='var', _var_type=str)}", "${var}"),
( (
f"testing f-string with {BaseVar(name='myvar', state='state', type_=int)}", f"testing f-string with {BaseVar(_var_name='myvar', _var_state='state', _var_type=int)}",
"testing f-string with ${state.myvar}", "testing f-string with ${state.myvar}",
), ),
( (
f"testing local f-string {BaseVar(name='x', is_local=True, type_=str)}", f"testing local f-string {BaseVar(_var_name='x', _var_is_local=True, _var_type=str)}",
"testing local f-string x", "testing local f-string x",
), ),
], ],
@ -676,14 +703,14 @@ def test_fstrings(out, expected):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"var", "var",
[ [
BaseVar(name="var", type_=int), BaseVar(_var_name="var", _var_type=int),
BaseVar(name="var", type_=float), BaseVar(_var_name="var", _var_type=float),
BaseVar(name="var", type_=str), BaseVar(_var_name="var", _var_type=str),
BaseVar(name="var", type_=bool), BaseVar(_var_name="var", _var_type=bool),
BaseVar(name="var", type_=dict), BaseVar(_var_name="var", _var_type=dict),
BaseVar(name="var", type_=tuple), BaseVar(_var_name="var", _var_type=tuple),
BaseVar(name="var", type_=set), BaseVar(_var_name="var", _var_type=set),
BaseVar(name="var", type_=None), BaseVar(_var_name="var", _var_type=None),
], ],
) )
def test_unsupported_types_for_reverse(var): def test_unsupported_types_for_reverse(var):
@ -700,11 +727,11 @@ def test_unsupported_types_for_reverse(var):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"var", "var",
[ [
BaseVar(name="var", type_=int), BaseVar(_var_name="var", _var_type=int),
BaseVar(name="var", type_=float), BaseVar(_var_name="var", _var_type=float),
BaseVar(name="var", type_=bool), BaseVar(_var_name="var", _var_type=bool),
BaseVar(name="var", type_=set), BaseVar(_var_name="var", _var_type=set),
BaseVar(name="var", type_=None), BaseVar(_var_name="var", _var_type=None),
], ],
) )
def test_unsupported_types_for_contains(var): def test_unsupported_types_for_contains(var):
@ -717,34 +744,34 @@ def test_unsupported_types_for_contains(var):
assert var.contains(1) assert var.contains(1)
assert ( assert (
err.value.args[0] err.value.args[0]
== f"Var var of type {var.type_} does not support contains check." == f"Var var of type {var._var_type} does not support contains check."
) )
@pytest.mark.parametrize( @pytest.mark.parametrize(
"other", "other",
[ [
BaseVar(name="other", type_=int), BaseVar(_var_name="other", _var_type=int),
BaseVar(name="other", type_=float), BaseVar(_var_name="other", _var_type=float),
BaseVar(name="other", type_=bool), BaseVar(_var_name="other", _var_type=bool),
BaseVar(name="other", type_=list), BaseVar(_var_name="other", _var_type=list),
BaseVar(name="other", type_=dict), BaseVar(_var_name="other", _var_type=dict),
BaseVar(name="other", type_=tuple), BaseVar(_var_name="other", _var_type=tuple),
BaseVar(name="other", type_=set), BaseVar(_var_name="other", _var_type=set),
], ],
) )
def test_unsupported_types_for_string_contains(other): def test_unsupported_types_for_string_contains(other):
with pytest.raises(TypeError) as err: with pytest.raises(TypeError) as err:
assert BaseVar(name="var", type_=str).contains(other) assert BaseVar(_var_name="var", _var_type=str).contains(other)
assert ( assert (
err.value.args[0] err.value.args[0]
== f"'in <string>' requires string as left operand, not {other.type_}" == f"'in <string>' requires string as left operand, not {other._var_type}"
) )
def test_unsupported_default_contains(): def test_unsupported_default_contains():
with pytest.raises(TypeError) as err: with pytest.raises(TypeError) as err:
assert 1 in BaseVar(name="var", type_=str) assert 1 in BaseVar(_var_name="var", _var_type=str)
assert ( assert (
err.value.args[0] err.value.args[0]
== "'in' operator not supported for Var types, use Var.contains() instead." == "'in' operator not supported for Var types, use Var.contains() instead."

View File

@ -211,11 +211,11 @@ def test_format_string(input: str, output: str):
"input,output", "input,output",
[ [
(Var.create(value="test"), "{`test`}"), (Var.create(value="test"), "{`test`}"),
(Var.create(value="test", is_local=True), "{`test`}"), (Var.create(value="test", _var_is_local=True), "{`test`}"),
(Var.create(value="test", is_local=False), "{test}"), (Var.create(value="test", _var_is_local=False), "{test}"),
(Var.create(value="test", is_string=True), "{`test`}"), (Var.create(value="test", _var_is_string=True), "{`test`}"),
(Var.create(value="test", is_string=False), "{`test`}"), (Var.create(value="test", _var_is_string=False), "{`test`}"),
(Var.create(value="test", is_local=False, is_string=False), "{test}"), (Var.create(value="test", _var_is_local=False, _var_is_string=False), "{test}"),
], ],
) )
def test_format_var(input: Var, output: str): def test_format_var(input: Var, output: str):
@ -285,7 +285,7 @@ def test_format_cond(condition: str, true_value: str, false_value: str, expected
( (
{ {
"a": 'foo "{ "bar" }" baz', "a": 'foo "{ "bar" }" baz',
"b": BaseVar(name="val", type_="str"), "b": BaseVar(_var_name="val", _var_type="str"),
}, },
r'{{"a": "foo \"{ \"bar\" }\" baz", "b": val}}', r'{{"a": "foo \"{ \"bar\" }\" baz", "b": val}}',
), ),
@ -308,22 +308,25 @@ def test_format_cond(condition: str, true_value: str, false_value: str, expected
'{_e => addEvents([Event("mock_event", {arg:_e.target.value})], _e)}', '{_e => addEvents([Event("mock_event", {arg:_e.target.value})], _e)}',
), ),
({"a": "red", "b": "blue"}, '{{"a": "red", "b": "blue"}}'), ({"a": "red", "b": "blue"}, '{{"a": "red", "b": "blue"}}'),
(BaseVar(name="var", type_="int"), "{var}"), (BaseVar(_var_name="var", _var_type="int"), "{var}"),
( (
BaseVar( BaseVar(
name="_", _var_name="_",
type_=Any, _var_type=Any,
state="", _var_state="",
is_local=True, _var_is_local=True,
is_string=False, _var_is_string=False,
), ),
"{_}", "{_}",
), ),
(BaseVar(name='state.colors["a"]', type_="str"), '{state.colors["a"]}'),
({"a": BaseVar(name="val", type_="str")}, '{{"a": val}}'),
({"a": BaseVar(name='"val"', type_="str")}, '{{"a": "val"}}'),
( (
{"a": BaseVar(name='state.colors["val"]', type_="str")}, BaseVar(_var_name='state.colors["a"]', _var_type="str"),
'{state.colors["a"]}',
),
({"a": BaseVar(_var_name="val", _var_type="str")}, '{{"a": val}}'),
({"a": BaseVar(_var_name='"val"', _var_type="str")}, '{{"a": "val"}}'),
(
{"a": BaseVar(_var_name='state.colors["val"]', _var_type="str")},
'{{"a": state.colors["val"]}}', '{{"a": state.colors["val"]}}',
), ),
# tricky real-world case from markdown component # tricky real-world case from markdown component

View File

@ -105,16 +105,16 @@ def test_add_serializer():
([1, 2, 3], "[1, 2, 3]"), ([1, 2, 3], "[1, 2, 3]"),
([1, "2", 3.0], '[1, "2", 3.0]'), ([1, "2", 3.0], '[1, "2", 3.0]'),
( (
[1, Var.create_safe("hi"), Var.create_safe("bye", is_local=False)], [1, Var.create_safe("hi"), Var.create_safe("bye", _var_is_local=False)],
'[1, "hi", bye]', '[1, "hi", bye]',
), ),
( (
(1, Var.create_safe("hi"), Var.create_safe("bye", is_local=False)), (1, Var.create_safe("hi"), Var.create_safe("bye", _var_is_local=False)),
'[1, "hi", bye]', '[1, "hi", bye]',
), ),
({1: 2, 3: 4}, '{"1": 2, "3": 4}'), ({1: 2, 3: 4}, '{"1": 2, "3": 4}'),
( (
{1: Var.create_safe("hi"), 3: Var.create_safe("bye", is_local=False)}, {1: Var.create_safe("hi"), 3: Var.create_safe("bye", _var_is_local=False)},
'{"1": "hi", "3": bye}', '{"1": "hi", "3": bye}',
), ),
(datetime.datetime(2021, 1, 1, 1, 1, 1, 1), "2021-01-01 01:01:01.000001"), (datetime.datetime(2021, 1, 1, 1, 1, 1, 1), "2021-01-01 01:01:01.000001"),