Var field cleanup (#1943)
This commit is contained in:
parent
684912e33b
commit
024cb5fa9b
@ -131,8 +131,8 @@ def EventChain():
|
||||
@app.add_page
|
||||
def index():
|
||||
return rx.fragment(
|
||||
rx.input(value=State.token, readonly=True, id="token"),
|
||||
rx.input(value=State.interim_value, readonly=True, id="interim_value"),
|
||||
rx.input(value=State.token, is_read_only=True, id="token"),
|
||||
rx.input(value=State.interim_value, is_read_only=True, id="interim_value"),
|
||||
rx.button(
|
||||
"Return Event",
|
||||
id="return_event",
|
||||
|
@ -65,13 +65,13 @@ class Base(pydantic.BaseModel):
|
||||
default_value: The default value of the field
|
||||
"""
|
||||
new_field = ModelField.infer(
|
||||
name=var.name,
|
||||
name=var._var_name,
|
||||
value=default_value,
|
||||
annotation=var.type_,
|
||||
annotation=var._var_type,
|
||||
class_validators=None,
|
||||
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:
|
||||
"""Get the value of a field.
|
||||
|
@ -243,7 +243,7 @@ def compile_custom_component(
|
||||
}
|
||||
|
||||
# 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.
|
||||
return (
|
||||
|
@ -94,4 +94,4 @@ def client_side(javascript_code) -> Var[EventChain]:
|
||||
deprecation_version="0.2.9",
|
||||
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)
|
||||
|
@ -155,7 +155,7 @@ class Component(Base, ABC):
|
||||
raise TypeError
|
||||
|
||||
# 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]
|
||||
except TypeError:
|
||||
# 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 isinstance(value, Var):
|
||||
if value.type_ is not EventChain:
|
||||
if value._var_type is not EventChain:
|
||||
raise ValueError(f"Invalid event chain: {value}")
|
||||
return value
|
||||
|
||||
@ -388,7 +388,7 @@ class Component(Base, ABC):
|
||||
# Add ref to element if `id` is not None.
|
||||
ref = self.get_ref()
|
||||
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)
|
||||
|
||||
@ -440,7 +440,7 @@ class Component(Base, ABC):
|
||||
children = [
|
||||
child
|
||||
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
|
||||
]
|
||||
|
||||
@ -808,11 +808,13 @@ class CustomComponent(Component):
|
||||
# Handle subclasses of Base.
|
||||
if types._issubclass(type_, Base):
|
||||
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:
|
||||
value = Var.create(value)
|
||||
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.
|
||||
self.props[format.to_camel_case(key)] = value
|
||||
@ -888,8 +890,10 @@ class CustomComponent(Component):
|
||||
"""
|
||||
return [
|
||||
BaseVar(
|
||||
name=name,
|
||||
type_=prop.type_ if types._isinstance(prop, Var) else type(prop),
|
||||
_var_name=name,
|
||||
_var_type=prop._var_type
|
||||
if types._isinstance(prop, Var)
|
||||
else type(prop),
|
||||
)
|
||||
for name, prop in self.props.items()
|
||||
]
|
||||
|
@ -48,7 +48,8 @@ class CodeBlock(Component):
|
||||
merged_imports = super()._get_imports()
|
||||
if self.theme is not None:
|
||||
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
|
||||
|
||||
@ -113,7 +114,7 @@ class CodeBlock(Component):
|
||||
out = super()._render()
|
||||
if self.theme is not None:
|
||||
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")
|
||||
return out
|
||||
|
||||
|
@ -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
|
||||
# render pandas dataframes.
|
||||
if isinstance(data, ComputedVar) and data.type_ == Any:
|
||||
if isinstance(data, ComputedVar) and data._var_type == Any:
|
||||
raise ValueError(
|
||||
"Annotation of the computed var assigned to the data field should be provided."
|
||||
)
|
||||
@ -72,7 +72,7 @@ class DataTable(Gridjs):
|
||||
if (
|
||||
columns is not None
|
||||
and isinstance(columns, ComputedVar)
|
||||
and columns.type_ == Any
|
||||
and columns._var_type == Any
|
||||
):
|
||||
raise ValueError(
|
||||
"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 (
|
||||
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:
|
||||
raise ValueError(
|
||||
"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 (
|
||||
(isinstance(data, Var) and types._issubclass(data.type_, List))
|
||||
(isinstance(data, Var) and types._issubclass(data._var_type, List))
|
||||
or issubclass(type(data), List)
|
||||
) and columns is None:
|
||||
raise ValueError(
|
||||
@ -109,16 +109,16 @@ class DataTable(Gridjs):
|
||||
)
|
||||
|
||||
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(
|
||||
name=f"{self.data.name}.columns",
|
||||
type_=List[Any],
|
||||
state=self.data.state,
|
||||
_var_name=f"{self.data._var_name}.columns",
|
||||
_var_type=List[Any],
|
||||
_var_state=self.data._var_state,
|
||||
)
|
||||
self.data = BaseVar(
|
||||
name=f"{self.data.name}.data",
|
||||
type_=List[List[Any]],
|
||||
state=self.data.state,
|
||||
_var_name=f"{self.data._var_name}.data",
|
||||
_var_type=List[List[Any]],
|
||||
_var_state=self.data._var_state,
|
||||
)
|
||||
if types.is_dataframe(type(self.data)):
|
||||
# If given a pandas df break up the data and columns
|
||||
|
@ -101,7 +101,9 @@ class Thead(ChakraComponent):
|
||||
if (
|
||||
(
|
||||
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)
|
||||
and not types.check_type_in_allowed_types(type(headers), allowed_types)
|
||||
@ -156,7 +158,7 @@ class Tbody(ChakraComponent):
|
||||
"""
|
||||
allowed_subclasses = (List, Tuple)
|
||||
if isinstance(rows, Var):
|
||||
outer_type = rows.type_
|
||||
outer_type = rows._var_type
|
||||
inner_type = (
|
||||
outer_type.__args__[0] if hasattr(outer_type, "__args__") else None
|
||||
)
|
||||
@ -222,7 +224,9 @@ class Tfoot(ChakraComponent):
|
||||
if (
|
||||
(
|
||||
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)
|
||||
and not types.check_type_in_allowed_types(type(footers), allowed_types)
|
||||
|
@ -58,7 +58,7 @@ class DebounceInput(Component):
|
||||
raise ValueError("DebounceInput child requires an on_change handler")
|
||||
child_ref = child.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 = []
|
||||
tag = super()._render()
|
||||
tag.add_props(
|
||||
@ -67,7 +67,9 @@ class DebounceInput(Component):
|
||||
sx=child.style,
|
||||
id=child.id,
|
||||
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
|
||||
object.__setattr__(child, "render", lambda: "")
|
||||
|
@ -29,10 +29,12 @@ class Form(ChakraComponent):
|
||||
# to collect data
|
||||
if ref.startswith("refs_"):
|
||||
form_refs[ref[5:-3]] = Var.create(
|
||||
f"getRefValues({ref[:-3]})", is_local=False
|
||||
f"getRefValues({ref[:-3]})", _var_is_local=False
|
||||
)
|
||||
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 {
|
||||
**super().get_event_triggers(),
|
||||
|
@ -308,7 +308,9 @@ class Select(Component):
|
||||
return {
|
||||
**super().get_event_triggers(),
|
||||
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
|
||||
else lambda e0: [e0]
|
||||
),
|
||||
|
@ -11,13 +11,17 @@ from reflex.event import EventChain
|
||||
from reflex.vars import BaseVar, Var
|
||||
|
||||
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.
|
||||
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(
|
||||
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.
|
||||
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.
|
||||
zone = Box.create(
|
||||
@ -85,7 +91,7 @@ class Upload(Component):
|
||||
*children,
|
||||
**{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.
|
||||
return super().create(zone, on_drop=upload_file, **upload_props)
|
||||
|
@ -77,7 +77,7 @@ class Cond(Component):
|
||||
).set(
|
||||
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.
|
||||
return BaseVar(
|
||||
name=format.format_cond(
|
||||
cond=cond_var.full_name,
|
||||
_var_name=format.format_cond(
|
||||
cond=cond_var._var_full_name,
|
||||
true_value=c1,
|
||||
false_value=c2,
|
||||
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),
|
||||
)
|
||||
|
@ -35,18 +35,18 @@ class Foreach(Component):
|
||||
"""
|
||||
try:
|
||||
type_ = (
|
||||
iterable.type_
|
||||
if iterable.type_.mro()[0] == dict
|
||||
else iterable.type_.__args__[0]
|
||||
iterable._var_type
|
||||
if iterable._var_type.mro()[0] == dict
|
||||
else iterable._var_type.__args__[0]
|
||||
)
|
||||
except Exception:
|
||||
type_ = Any
|
||||
iterable = Var.create(iterable) # type: ignore
|
||||
if iterable.type_ == Any:
|
||||
if iterable._var_type == Any:
|
||||
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.)"
|
||||
)
|
||||
arg = BaseVar(name="_", type_=type_, is_local=True)
|
||||
arg = BaseVar(_var_name="_", _var_type=type_, _var_is_local=True)
|
||||
return cls(
|
||||
iterable=iterable,
|
||||
render_fn=render_fn,
|
||||
@ -66,15 +66,15 @@ class Foreach(Component):
|
||||
tag = self._render()
|
||||
try:
|
||||
type_ = (
|
||||
self.iterable.type_
|
||||
if self.iterable.type_.mro()[0] == dict
|
||||
else self.iterable.type_.__args__[0]
|
||||
self.iterable._var_type
|
||||
if self.iterable._var_type.mro()[0] == dict
|
||||
else self.iterable._var_type.__args__[0]
|
||||
)
|
||||
except Exception:
|
||||
type_ = Any
|
||||
arg = BaseVar(
|
||||
name=get_unique_variable_name(),
|
||||
type_=type_,
|
||||
_var_name=get_unique_variable_name(),
|
||||
_var_type=type_,
|
||||
)
|
||||
index_arg = tag.get_index_var_arg()
|
||||
component = tag.render_component(self.render_fn, arg)
|
||||
@ -89,8 +89,8 @@ class Foreach(Component):
|
||||
children=[component.render()],
|
||||
props=tag.format_props(),
|
||||
),
|
||||
iterable_state=tag.iterable.full_name,
|
||||
arg_name=arg.name,
|
||||
iterable_state=tag.iterable._var_full_name,
|
||||
arg_name=arg._var_name,
|
||||
arg_index=index_arg,
|
||||
iterable_type=tag.iterable.type_.mro()[0].__name__,
|
||||
iterable_type=tag.iterable._var_type.mro()[0].__name__,
|
||||
)
|
||||
|
@ -77,7 +77,7 @@ class Image(ChakraComponent):
|
||||
"""
|
||||
src = props.get("src", None)
|
||||
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)
|
||||
|
||||
|
||||
|
@ -23,7 +23,7 @@ class Link(ChakraComponent):
|
||||
text: Var[str]
|
||||
|
||||
# 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.
|
||||
is_external: Var[bool]
|
||||
|
@ -13,14 +13,14 @@ from reflex.vars import ImportVar, Var
|
||||
|
||||
connection_error: Var = Var.create_safe(
|
||||
value="(connectError !== null) ? connectError.message : ''",
|
||||
is_local=False,
|
||||
is_string=False,
|
||||
_var_is_local=False,
|
||||
_var_is_string=False,
|
||||
)
|
||||
has_connection_error: Var = Var.create_safe(
|
||||
value="connectError !== null",
|
||||
is_string=False,
|
||||
_var_is_string=False,
|
||||
)
|
||||
has_connection_error.type_ = bool
|
||||
has_connection_error._var_type = bool
|
||||
|
||||
|
||||
class WebsocketTargetURL(Bare):
|
||||
|
@ -31,8 +31,8 @@ class IterTag(Tag):
|
||||
The index var.
|
||||
"""
|
||||
return BaseVar(
|
||||
name=INDEX_VAR,
|
||||
type_=int,
|
||||
_var_name=INDEX_VAR,
|
||||
_var_type=int,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@ -43,9 +43,9 @@ class IterTag(Tag):
|
||||
The index var.
|
||||
"""
|
||||
return BaseVar(
|
||||
name=INDEX_VAR,
|
||||
type_=int,
|
||||
is_local=True,
|
||||
_var_name=INDEX_VAR,
|
||||
_var_type=int,
|
||||
_var_is_local=True,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
@ -17,18 +17,18 @@ from reflex.utils import console, imports, types
|
||||
from reflex.vars import ImportVar, Var
|
||||
|
||||
# Special vars used in the component map.
|
||||
_CHILDREN = Var.create_safe("children", is_local=False)
|
||||
_PROPS = Var.create_safe("...props", is_local=False)
|
||||
_CHILDREN = Var.create_safe("children", _var_is_local=False)
|
||||
_PROPS = Var.create_safe("...props", _var_is_local=False)
|
||||
_MOCK_ARG = Var.create_safe("")
|
||||
|
||||
# Special remark plugins.
|
||||
_REMARK_MATH = Var.create_safe("remarkMath", is_local=False)
|
||||
_REMARK_GFM = Var.create_safe("remarkGfm", is_local=False)
|
||||
_REMARK_MATH = Var.create_safe("remarkMath", _var_is_local=False)
|
||||
_REMARK_GFM = Var.create_safe("remarkGfm", _var_is_local=False)
|
||||
_REMARK_PLUGINS = Var.create_safe([_REMARK_MATH, _REMARK_GFM])
|
||||
|
||||
# Special rehype plugins.
|
||||
_REHYPE_KATEX = Var.create_safe("rehypeKatex", is_local=False)
|
||||
_REHYPE_RAW = Var.create_safe("rehypeRaw", is_local=False)
|
||||
_REHYPE_KATEX = Var.create_safe("rehypeKatex", _var_is_local=False)
|
||||
_REHYPE_RAW = Var.create_safe("rehypeRaw", _var_is_local=False)
|
||||
_REHYPE_PLUGINS = Var.create_safe([_REHYPE_KATEX, _REHYPE_RAW])
|
||||
|
||||
# Component Mapping
|
||||
@ -153,13 +153,17 @@ class Markdown(Component):
|
||||
{
|
||||
"": {ImportVar(tag="katex/dist/katex.min.css")},
|
||||
"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": {
|
||||
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.
|
||||
"""
|
||||
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
|
||||
}
|
||||
|
||||
# Separate out inline code and code blocks.
|
||||
components[
|
||||
"code"
|
||||
] = f"""{{({{inline, className, {_CHILDREN.name}, {_PROPS.name}}}) => {{
|
||||
] = f"""{{({{inline, className, {_CHILDREN._var_name}, {_PROPS._var_name}}}) => {{
|
||||
const match = (className || '').match(/language-(?<lang>.*)/);
|
||||
const language = match ? match[1] : '';
|
||||
return inline ? (
|
||||
{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(
|
||||
"\n", " "
|
||||
|
@ -153,7 +153,7 @@ class EventHandler(Base):
|
||||
|
||||
# Otherwise, convert to JSON.
|
||||
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:
|
||||
raise TypeError(
|
||||
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.
|
||||
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):
|
||||
@ -241,7 +241,7 @@ def server_side(name: str, sig: inspect.Signature, **kwargs) -> EventSpec:
|
||||
return EventSpec(
|
||||
handler=EventHandler(fn=fn),
|
||||
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()
|
||||
),
|
||||
)
|
||||
@ -560,9 +560,9 @@ def parse_args_spec(arg_spec: ArgsSpec):
|
||||
return arg_spec(
|
||||
*[
|
||||
BaseVar(
|
||||
name=f"_{l_arg}",
|
||||
type_=spec.annotations.get(l_arg, FrontendEvent),
|
||||
is_local=True,
|
||||
_var_name=f"_{l_arg}",
|
||||
_var_type=spec.annotations.get(l_arg, FrontendEvent),
|
||||
_var_is_local=True,
|
||||
)
|
||||
for l_arg in spec.args
|
||||
]
|
||||
@ -675,7 +675,7 @@ def fix_events(
|
||||
e = e()
|
||||
assert isinstance(e, EventSpec), f"Unexpected event type, {type(e)}."
|
||||
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.
|
||||
out.append(
|
||||
|
@ -132,7 +132,7 @@ class State(Base, ABC, extra=pydantic.Extra.allow):
|
||||
)
|
||||
for cvar_name, cvar in self.computed_vars.items():
|
||||
# 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)
|
||||
if var in inherited_vars:
|
||||
# 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.
|
||||
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()
|
||||
if f.name not in cls.get_skip_vars()
|
||||
}
|
||||
cls.computed_vars = {
|
||||
v.name: v.set_state(cls)
|
||||
v._var_name: v._var_set_state(cls)
|
||||
for v in cls.__dict__.values()
|
||||
if isinstance(v, ComputedVar)
|
||||
}
|
||||
@ -389,12 +391,12 @@ class State(Base, ABC, extra=pydantic.Extra.allow):
|
||||
Raises:
|
||||
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(
|
||||
"State vars must be primitive Python types, "
|
||||
"Plotly figures, Pandas dataframes, "
|
||||
"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._create_setter(prop)
|
||||
@ -421,8 +423,8 @@ class State(Base, ABC, extra=pydantic.Extra.allow):
|
||||
)
|
||||
|
||||
# create the variable based on name and type
|
||||
var = BaseVar(name=name, type_=type_)
|
||||
var.set_state(cls)
|
||||
var = BaseVar(_var_name=name, _var_type=type_)
|
||||
var._var_set_state(cls)
|
||||
|
||||
# add the pydantic field dynamically (must be done before _init_var)
|
||||
cls.add_field(var, default_value)
|
||||
@ -444,7 +446,7 @@ class State(Base, ABC, extra=pydantic.Extra.allow):
|
||||
Args:
|
||||
prop: The var instance to set.
|
||||
"""
|
||||
setattr(cls, prop.name, prop)
|
||||
setattr(cls, prop._var_name, prop)
|
||||
|
||||
@classmethod
|
||||
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.
|
||||
"""
|
||||
# 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()
|
||||
if field.required and default_value is not None:
|
||||
field.required = False
|
||||
@ -599,8 +601,9 @@ class State(Base, ABC, extra=pydantic.Extra.allow):
|
||||
func = arglist_factory(param)
|
||||
else:
|
||||
continue
|
||||
func.fget.__name__ = param # to allow passing as a prop # type: ignore
|
||||
cls.vars[param] = cls.computed_vars[param] = func.set_state(cls) # type: ignore
|
||||
# to allow passing as a prop
|
||||
func._var_name = param
|
||||
cls.vars[param] = cls.computed_vars[param] = func._var_set_state(cls) # type: ignore
|
||||
setattr(cls, param, func)
|
||||
|
||||
def __getattribute__(self, name: str) -> Any:
|
||||
@ -912,7 +915,7 @@ class State(Base, ABC, extra=pydantic.Extra.allow):
|
||||
return set(
|
||||
cvar_name
|
||||
for cvar_name, cvar in self.computed_vars.items()
|
||||
if not cvar.cache
|
||||
if not cvar._cache
|
||||
)
|
||||
|
||||
def _mark_dirty_computed_vars(self) -> None:
|
||||
|
@ -7,8 +7,8 @@ from reflex.event import EventChain
|
||||
from reflex.utils import format
|
||||
from reflex.vars import BaseVar, Var
|
||||
|
||||
color_mode = BaseVar(name=constants.ColorMode.NAME, type_="str")
|
||||
toggle_color_mode = BaseVar(name=constants.ColorMode.TOGGLE, type_=EventChain)
|
||||
color_mode = BaseVar(_var_name=constants.ColorMode.NAME, _var_type="str")
|
||||
toggle_color_mode = BaseVar(_var_name=constants.ColorMode.TOGGLE, _var_type=EventChain)
|
||||
|
||||
|
||||
def convert(style_dict):
|
||||
|
@ -106,6 +106,7 @@ class AppHarness:
|
||||
app_instance: Optional[reflex.App] = None
|
||||
frontend_process: Optional[subprocess.Popen] = None
|
||||
frontend_url: Optional[str] = None
|
||||
frontend_output_thread: Optional[threading.Thread] = None
|
||||
backend_thread: Optional[threading.Thread] = None
|
||||
backend: Optional[uvicorn.Server] = None
|
||||
state_manager: Optional[StateManagerMemory | StateManagerRedis] = None
|
||||
@ -230,6 +231,18 @@ class AppHarness:
|
||||
if self.frontend_url is None:
|
||||
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":
|
||||
"""Start the backend in a new thread and dev frontend as a separate process.
|
||||
|
||||
@ -278,6 +291,8 @@ class AppHarness:
|
||||
self.frontend_process.communicate()
|
||||
if self.backend_thread is not None:
|
||||
self.backend_thread.join()
|
||||
if self.frontend_output_thread is not None:
|
||||
self.frontend_output_thread.join()
|
||||
for driver in self._frontends:
|
||||
driver.quit()
|
||||
|
||||
|
@ -203,13 +203,13 @@ def format_var(var: Var) -> str:
|
||||
Returns:
|
||||
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)
|
||||
if types._issubclass(var.type_, str):
|
||||
return format_string(var.full_name)
|
||||
if is_wrapped(var.full_name, "{"):
|
||||
return var.full_name
|
||||
return json_dumps(var.full_name)
|
||||
if types._issubclass(var._var_type, str):
|
||||
return format_string(var._var_full_name)
|
||||
if is_wrapped(var._var_full_name, "{"):
|
||||
return var._var_full_name
|
||||
return json_dumps(var._var_full_name)
|
||||
|
||||
|
||||
def format_route(route: str, format_case=True) -> str:
|
||||
@ -259,12 +259,16 @@ def format_cond(
|
||||
|
||||
# Format prop conds.
|
||||
if is_prop:
|
||||
prop1 = Var.create_safe(true_value, is_string=type(true_value) is str).set(
|
||||
is_local=True
|
||||
) # type: ignore
|
||||
prop2 = Var.create_safe(false_value, is_string=type(false_value) is str).set(
|
||||
is_local=True
|
||||
) # type: ignore
|
||||
prop1 = Var.create_safe(
|
||||
true_value,
|
||||
_var_is_string=type(true_value) is str,
|
||||
)
|
||||
prop1._var_is_local = True
|
||||
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("}", "")
|
||||
|
||||
# Format component conds.
|
||||
@ -292,11 +296,11 @@ def format_prop(
|
||||
try:
|
||||
# Handle var props.
|
||||
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)
|
||||
if types._issubclass(prop.type_, str):
|
||||
return format_string(prop.full_name)
|
||||
prop = prop.full_name
|
||||
if types._issubclass(prop._var_type, str):
|
||||
return format_string(prop._var_full_name)
|
||||
prop = prop._var_full_name
|
||||
|
||||
# Handle event props.
|
||||
elif isinstance(prop, EventChain):
|
||||
@ -414,7 +418,12 @@ def format_event(event_spec: EventSpec) -> str:
|
||||
args = ",".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
|
||||
]
|
||||
@ -448,7 +457,7 @@ def format_event_chain(
|
||||
if isinstance(event_chain, Var):
|
||||
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}")
|
||||
return "".join(
|
||||
[
|
||||
@ -540,7 +549,7 @@ def format_array_ref(refs: str, idx: Var | None) -> str:
|
||||
"""
|
||||
clean_ref = re.sub(r"[^\w]+", "_", refs)
|
||||
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}"
|
||||
|
||||
|
481
reflex/vars.py
481
reflex/vars.py
@ -2,11 +2,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import dataclasses
|
||||
import dis
|
||||
import json
|
||||
import random
|
||||
import string
|
||||
from abc import ABC
|
||||
import sys
|
||||
from types import CodeType, FunctionType
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
@ -64,6 +65,18 @@ OPERATION_MAPPING = {
|
||||
(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:
|
||||
"""Get a unique variable name.
|
||||
@ -78,34 +91,34 @@ def get_unique_variable_name() -> str:
|
||||
return get_unique_variable_name()
|
||||
|
||||
|
||||
class Var(ABC):
|
||||
class Var:
|
||||
"""An abstract var."""
|
||||
|
||||
# The name of the var.
|
||||
name: str
|
||||
_var_name: str
|
||||
|
||||
# The type of the var.
|
||||
type_: Type
|
||||
_var_type: Type
|
||||
|
||||
# The name of the enclosing state.
|
||||
state: str = ""
|
||||
_var_state: str
|
||||
|
||||
# Whether this is a local javascript variable.
|
||||
is_local: bool = False
|
||||
_var_is_local: bool
|
||||
|
||||
# Whether the var is a string literal.
|
||||
is_string: bool = False
|
||||
_var_is_string: bool
|
||||
|
||||
@classmethod
|
||||
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:
|
||||
"""Create a var from a value.
|
||||
|
||||
Args:
|
||||
value: The value to create the var from.
|
||||
is_local: Whether the var is local.
|
||||
is_string: Whether the var is a string literal.
|
||||
_var_is_local: Whether the var is local.
|
||||
_var_is_string: Whether the var is a string literal.
|
||||
|
||||
Returns:
|
||||
The var.
|
||||
@ -130,23 +143,32 @@ class Var(ABC):
|
||||
)
|
||||
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
|
||||
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:
|
||||
"""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:
|
||||
value: The value to create the var from.
|
||||
is_local: Whether the var is local.
|
||||
is_string: Whether the var is a string literal.
|
||||
_var_is_local: Whether the var is local.
|
||||
_var_is_string: Whether the var is a string literal.
|
||||
|
||||
Returns:
|
||||
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
|
||||
return var
|
||||
|
||||
@ -171,14 +193,14 @@ class Var(ABC):
|
||||
Returns:
|
||||
The decoded value or the Var name.
|
||||
"""
|
||||
if self.state:
|
||||
return self.full_name
|
||||
if self.is_string:
|
||||
return self.name
|
||||
if self._var_state:
|
||||
return self._var_full_name
|
||||
if self._var_is_string:
|
||||
return self._var_name
|
||||
try:
|
||||
return json.loads(self.name)
|
||||
return json.loads(self._var_name)
|
||||
except ValueError:
|
||||
return self.name
|
||||
return self._var_name
|
||||
|
||||
def equals(self, other: Var) -> bool:
|
||||
"""Check if two vars are equal.
|
||||
@ -190,10 +212,10 @@ class Var(ABC):
|
||||
Whether the vars are equal.
|
||||
"""
|
||||
return (
|
||||
self.name == other.name
|
||||
and self.type_ == other.type_
|
||||
and self.state == other.state
|
||||
and self.is_local == other.is_local
|
||||
self._var_name == other._var_name
|
||||
and self._var_type == other._var_type
|
||||
and self._var_state == other._var_state
|
||||
and self._var_is_local == other._var_is_local
|
||||
)
|
||||
|
||||
def to_string(self, json: bool = True) -> Var:
|
||||
@ -214,7 +236,7 @@ class Var(ABC):
|
||||
Returns:
|
||||
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:
|
||||
"""Wrap the var so it can be used in templates.
|
||||
@ -222,8 +244,12 @@ class Var(ABC):
|
||||
Returns:
|
||||
The wrapped var, i.e. {state.var}.
|
||||
"""
|
||||
out = self.full_name if self.is_local else format.wrap(self.full_name, "{")
|
||||
if self.is_string:
|
||||
out = (
|
||||
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)
|
||||
return out
|
||||
|
||||
@ -234,7 +260,7 @@ class Var(ABC):
|
||||
TypeError: when attempting to bool-ify the Var.
|
||||
"""
|
||||
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)."
|
||||
)
|
||||
|
||||
@ -245,7 +271,7 @@ class Var(ABC):
|
||||
TypeError: when attempting to iterate over the Var.
|
||||
"""
|
||||
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:
|
||||
@ -257,7 +283,7 @@ class Var(ABC):
|
||||
Returns:
|
||||
The formatted var.
|
||||
"""
|
||||
if self.is_local:
|
||||
if self._var_is_local:
|
||||
return str(self)
|
||||
return f"${str(self)}"
|
||||
|
||||
@ -275,16 +301,16 @@ class Var(ABC):
|
||||
"""
|
||||
# Indexing is only supported for strings, lists, tuples, dicts, and dataframes.
|
||||
if not (
|
||||
types._issubclass(self.type_, Union[List, Dict, Tuple, str])
|
||||
or types.is_dataframe(self.type_)
|
||||
types._issubclass(self._var_type, Union[List, Dict, Tuple, str])
|
||||
or types.is_dataframe(self._var_type)
|
||||
):
|
||||
if self.type_ == Any:
|
||||
if self._var_type == Any:
|
||||
raise TypeError(
|
||||
"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.)"
|
||||
)
|
||||
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.
|
||||
@ -292,15 +318,20 @@ class Var(ABC):
|
||||
|
||||
# Convert any vars to local vars.
|
||||
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.
|
||||
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.
|
||||
if (
|
||||
not isinstance(i, types.get_args(Union[int, slice, 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.")
|
||||
|
||||
@ -312,35 +343,40 @@ class Var(ABC):
|
||||
|
||||
# Use the slice function.
|
||||
return BaseVar(
|
||||
name=f"{self.name}.slice({start}, {stop})",
|
||||
type_=self.type_,
|
||||
state=self.state,
|
||||
is_local=self.is_local,
|
||||
_var_name=f"{self._var_name}.slice({start}, {stop})",
|
||||
_var_type=self._var_type,
|
||||
_var_state=self._var_state,
|
||||
_var_is_local=self._var_is_local,
|
||||
)
|
||||
|
||||
# Get the type of the indexed var.
|
||||
type_ = (
|
||||
types.get_args(self.type_)[0]
|
||||
if types.is_generic_alias(self.type_)
|
||||
types.get_args(self._var_type)[0]
|
||||
if types.is_generic_alias(self._var_type)
|
||||
else Any
|
||||
)
|
||||
|
||||
# Use `at` to support negative indices.
|
||||
return BaseVar(
|
||||
name=f"{self.name}.at({i})",
|
||||
type_=type_,
|
||||
state=self.state,
|
||||
is_local=self.is_local,
|
||||
_var_name=f"{self._var_name}.at({i})",
|
||||
_var_type=type_,
|
||||
_var_state=self._var_state,
|
||||
_var_is_local=self._var_is_local,
|
||||
)
|
||||
|
||||
# Dictionary / dataframe indexing.
|
||||
# Tuples are currently not supported as indexes.
|
||||
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]))
|
||||
) or (
|
||||
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(
|
||||
"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):
|
||||
i = format.wrap(i, '"')
|
||||
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.
|
||||
return BaseVar(
|
||||
name=f"{self.name}[{i}]",
|
||||
type_=type_,
|
||||
state=self.state,
|
||||
is_local=self.is_local,
|
||||
_var_name=f"{self._var_name}[{i}]",
|
||||
_var_type=type_,
|
||||
_var_state=self._var_state,
|
||||
_var_is_local=self._var_is_local,
|
||||
)
|
||||
|
||||
def __getattribute__(self, name: str) -> Var:
|
||||
def __getattr__(self, name: str) -> Var:
|
||||
"""Get a var attribute.
|
||||
|
||||
Args:
|
||||
@ -373,30 +411,39 @@ class Var(ABC):
|
||||
AttributeError: If the var is wrongly annotated or can't find attribute.
|
||||
TypeError: If an annotation to the var isn't provided.
|
||||
"""
|
||||
try:
|
||||
return super().__getattribute__(name)
|
||||
except Exception as e:
|
||||
# Check if the attribute is one of the class fields.
|
||||
if not name.startswith("_"):
|
||||
if self.type_ == Any:
|
||||
raise TypeError(
|
||||
f"You must provide an annotation for the state var `{self.full_name}`. Annotation cannot be `{self.type_}`"
|
||||
) from None
|
||||
if hasattr(self.type_, "__fields__") and name in self.type_.__fields__:
|
||||
type_ = self.type_.__fields__[name].outer_type_
|
||||
if isinstance(type_, ModelField):
|
||||
type_ = type_.type_
|
||||
return BaseVar(
|
||||
name=f"{self.name}.{name}",
|
||||
type_=type_,
|
||||
state=self.state,
|
||||
is_local=self.is_local,
|
||||
)
|
||||
# Check if the attribute is one of the class fields.
|
||||
if not name.startswith("_"):
|
||||
if self._var_type == Any:
|
||||
raise TypeError(
|
||||
f"You must provide an annotation for the state var `{self._var_full_name}`. Annotation cannot be `{self._var_type}`"
|
||||
) from None
|
||||
if (
|
||||
hasattr(self._var_type, "__fields__")
|
||||
and name in self._var_type.__fields__
|
||||
):
|
||||
type_ = self._var_type.__fields__[name].outer_type_
|
||||
if isinstance(type_, ModelField):
|
||||
type_ = type_.type_
|
||||
return BaseVar(
|
||||
_var_name=f"{self._var_name}.{name}",
|
||||
_var_type=type_,
|
||||
_var_state=self._var_state,
|
||||
_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(
|
||||
f"The State var `{self.full_name}` has no attribute '{name}' or may have been annotated "
|
||||
f"wrongly.\n"
|
||||
f"original message: {e.args[0]}"
|
||||
) from e
|
||||
f"The State var `{self._var_full_name}` has no attribute '{name}' or may have been annotated "
|
||||
f"wrongly."
|
||||
)
|
||||
|
||||
raise AttributeError(
|
||||
f"The State var has no attribute '{name}' or may have been annotated wrongly.",
|
||||
)
|
||||
|
||||
def operation(
|
||||
self,
|
||||
@ -429,7 +476,7 @@ class Var(ABC):
|
||||
else:
|
||||
other = Var.create(other)
|
||||
|
||||
type_ = type_ or self.type_
|
||||
type_ = type_ or self._var_type
|
||||
|
||||
if other is None and flip:
|
||||
raise ValueError(
|
||||
@ -441,42 +488,42 @@ class Var(ABC):
|
||||
if other is not None:
|
||||
# check if the operation between operands is valid.
|
||||
if op and not self.is_valid_operation(
|
||||
types.get_base_class(left_operand.type_), # type: ignore
|
||||
types.get_base_class(right_operand.type_), # type: ignore
|
||||
types.get_base_class(left_operand._var_type), # type: ignore
|
||||
types.get_base_class(right_operand._var_type), # type: ignore
|
||||
op,
|
||||
):
|
||||
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
|
||||
if fn is not None:
|
||||
if invoke_fn:
|
||||
# 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:
|
||||
# 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})"
|
||||
else:
|
||||
# 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, "(")
|
||||
else:
|
||||
# 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
|
||||
if fn is not None:
|
||||
operation_name = (
|
||||
f"{fn}({operation_name})"
|
||||
if not invoke_fn
|
||||
else f"{self.full_name}.{fn}()"
|
||||
else f"{self._var_full_name}.{fn}()"
|
||||
)
|
||||
|
||||
return BaseVar(
|
||||
name=operation_name,
|
||||
type_=type_,
|
||||
is_local=self.is_local,
|
||||
_var_name=operation_name,
|
||||
_var_type=type_,
|
||||
_var_is_local=self._var_is_local,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@ -554,12 +601,12 @@ class Var(ABC):
|
||||
Raises:
|
||||
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}.")
|
||||
return BaseVar(
|
||||
name=f"{self.full_name}.length",
|
||||
type_=int,
|
||||
is_local=self.is_local,
|
||||
_var_name=f"{self._var_full_name}.length",
|
||||
_var_type=int,
|
||||
_var_is_local=self._var_is_local,
|
||||
)
|
||||
|
||||
def __eq__(self, other: Var) -> Var:
|
||||
@ -638,12 +685,12 @@ class Var(ABC):
|
||||
Returns:
|
||||
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
|
||||
# merging the list, and for that reason we use the spread operator available through spreadArraysOrObjects
|
||||
# utility function
|
||||
if (
|
||||
types.get_base_class(self.type_) == list
|
||||
types.get_base_class(self._var_type) == list
|
||||
and types.get_base_class(other_type) == list
|
||||
):
|
||||
return self.operation(",", other, fn="spreadArraysOrObjects", flip=flip)
|
||||
@ -692,10 +739,10 @@ class Var(ABC):
|
||||
Returns:
|
||||
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.
|
||||
# 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),
|
||||
(str, int),
|
||||
]:
|
||||
@ -703,16 +750,16 @@ class Var(ABC):
|
||||
|
||||
# For list-int multiplication, we use the Array function.
|
||||
# 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),
|
||||
(list, int),
|
||||
]:
|
||||
other_name = other.full_name if isinstance(other, Var) else other
|
||||
name = f"Array({other_name}).fill().map(() => {self.full_name}).flat()"
|
||||
other_name = other._var_full_name if isinstance(other, Var) else other
|
||||
name = f"Array({other_name}).fill().map(() => {self._var_full_name}).flat()"
|
||||
return BaseVar(
|
||||
name=name,
|
||||
type_=str,
|
||||
is_local=self.is_local,
|
||||
_var_name=name,
|
||||
_var_type=str,
|
||||
_var_is_local=self._var_is_local,
|
||||
)
|
||||
|
||||
return self.operation("*", other)
|
||||
@ -830,7 +877,7 @@ class Var(ABC):
|
||||
>>> var1 = Var.create(True)
|
||||
>>> var2 = Var.create(False)
|
||||
>>> js_code = var1 & var2
|
||||
>>> print(js_code.full_name)
|
||||
>>> print(js_code._var_full_name)
|
||||
'(true && false)'
|
||||
"""
|
||||
return self.operation("&&", other, type_=bool)
|
||||
@ -860,7 +907,7 @@ class Var(ABC):
|
||||
>>> var1 = Var.create(True)
|
||||
>>> var2 = Var.create(False)
|
||||
>>> js_code = var1 & var2
|
||||
>>> print(js_code.full_name)
|
||||
>>> print(js_code._var_full_name)
|
||||
'(false && true)'
|
||||
"""
|
||||
return self.operation("&&", other, type_=bool, flip=True)
|
||||
@ -888,7 +935,7 @@ class Var(ABC):
|
||||
>>> var1 = Var.create(True)
|
||||
>>> var2 = Var.create(False)
|
||||
>>> js_code = var1 | var2
|
||||
>>> print(js_code.full_name)
|
||||
>>> print(js_code._var_full_name)
|
||||
'(true || false)'
|
||||
"""
|
||||
return self.operation("||", other, type_=bool)
|
||||
@ -943,35 +990,37 @@ class Var(ABC):
|
||||
Returns:
|
||||
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(
|
||||
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 = (
|
||||
"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):
|
||||
other = Var.create(json.dumps(other), is_string=True)
|
||||
other = Var.create(json.dumps(other), _var_is_string=True)
|
||||
elif not isinstance(other, Var):
|
||||
other = Var.create(other)
|
||||
if types._issubclass(self.type_, Dict):
|
||||
if types._issubclass(self._var_type, Dict):
|
||||
return BaseVar(
|
||||
name=f"{self.full_name}.{method}({other.full_name})",
|
||||
type_=bool,
|
||||
is_local=self.is_local,
|
||||
_var_name=f"{self._var_full_name}.{method}({other._var_full_name})",
|
||||
_var_type=bool,
|
||||
_var_is_local=self._var_is_local,
|
||||
)
|
||||
else: # str, list, tuple
|
||||
# For strings, the left operand must be a string.
|
||||
if types._issubclass(self.type_, str) and not types._issubclass(
|
||||
other.type_, str
|
||||
if types._issubclass(self._var_type, str) and not types._issubclass(
|
||||
other._var_type, str
|
||||
):
|
||||
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(
|
||||
name=f"{self.full_name}.includes({other.full_name})",
|
||||
type_=bool,
|
||||
is_local=self.is_local,
|
||||
_var_name=f"{self._var_full_name}.includes({other._var_full_name})",
|
||||
_var_type=bool,
|
||||
_var_is_local=self._var_is_local,
|
||||
)
|
||||
|
||||
def reverse(self) -> Var:
|
||||
@ -983,13 +1032,13 @@ class Var(ABC):
|
||||
Returns:
|
||||
A var with the reversed list.
|
||||
"""
|
||||
if not types._issubclass(self.type_, list):
|
||||
raise TypeError(f"Cannot reverse non-list var {self.full_name}.")
|
||||
if not types._issubclass(self._var_type, list):
|
||||
raise TypeError(f"Cannot reverse non-list var {self._var_full_name}.")
|
||||
|
||||
return BaseVar(
|
||||
name=f"[...{self.full_name}].reverse()",
|
||||
type_=self.type_,
|
||||
is_local=self.is_local,
|
||||
_var_name=f"[...{self._var_full_name}].reverse()",
|
||||
_var_type=self._var_type,
|
||||
_var_is_local=self._var_is_local,
|
||||
)
|
||||
|
||||
def lower(self) -> Var:
|
||||
@ -1001,15 +1050,15 @@ class Var(ABC):
|
||||
Raises:
|
||||
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 convert non-string var {self.full_name} to lowercase."
|
||||
f"Cannot convert non-string var {self._var_full_name} to lowercase."
|
||||
)
|
||||
|
||||
return BaseVar(
|
||||
name=f"{self.full_name}.toLowerCase()",
|
||||
type_=str,
|
||||
is_local=self.is_local,
|
||||
_var_name=f"{self._var_full_name}.toLowerCase()",
|
||||
_var_type=str,
|
||||
_var_is_local=self._var_is_local,
|
||||
)
|
||||
|
||||
def upper(self) -> Var:
|
||||
@ -1021,15 +1070,15 @@ class Var(ABC):
|
||||
Raises:
|
||||
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 convert non-string var {self.full_name} to uppercase."
|
||||
f"Cannot convert non-string var {self._var_full_name} to uppercase."
|
||||
)
|
||||
|
||||
return BaseVar(
|
||||
name=f"{self.full_name}.toUpperCase()",
|
||||
type_=str,
|
||||
is_local=self.is_local,
|
||||
_var_name=f"{self._var_full_name}.toUpperCase()",
|
||||
_var_type=str,
|
||||
_var_is_local=self._var_is_local,
|
||||
)
|
||||
|
||||
def split(self, other: str | Var[str] = " ") -> Var:
|
||||
@ -1044,15 +1093,15 @@ class Var(ABC):
|
||||
Raises:
|
||||
TypeError: If the var is not a string.
|
||||
"""
|
||||
if not types._issubclass(self.type_, str):
|
||||
raise TypeError(f"Cannot split non-string var {self.full_name}.")
|
||||
if not types._issubclass(self._var_type, str):
|
||||
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
|
||||
|
||||
return BaseVar(
|
||||
name=f"{self.full_name}.split({other.full_name})",
|
||||
type_=list[str],
|
||||
is_local=self.is_local,
|
||||
_var_name=f"{self._var_full_name}.split({other._var_full_name})",
|
||||
_var_type=list[str],
|
||||
_var_is_local=self._var_is_local,
|
||||
)
|
||||
|
||||
def join(self, other: str | Var[str] | None = None) -> Var:
|
||||
@ -1067,8 +1116,8 @@ class Var(ABC):
|
||||
Raises:
|
||||
TypeError: If the var is not a list.
|
||||
"""
|
||||
if not types._issubclass(self.type_, list):
|
||||
raise TypeError(f"Cannot join non-list var {self.full_name}.")
|
||||
if not types._issubclass(self._var_type, list):
|
||||
raise TypeError(f"Cannot join non-list var {self._var_full_name}.")
|
||||
|
||||
if other is None:
|
||||
other = Var.create_safe("")
|
||||
@ -1078,9 +1127,9 @@ class Var(ABC):
|
||||
other = Var.create_safe(other)
|
||||
|
||||
return BaseVar(
|
||||
name=f"{self.full_name}.join({other.full_name})",
|
||||
type_=str,
|
||||
is_local=self.is_local,
|
||||
_var_name=f"{self._var_full_name}.join({other._var_full_name})",
|
||||
_var_type=str,
|
||||
_var_is_local=self._var_is_local,
|
||||
)
|
||||
|
||||
def foreach(self, fn: Callable) -> Var:
|
||||
@ -1093,13 +1142,13 @@ class Var(ABC):
|
||||
A var representing foreach operation.
|
||||
"""
|
||||
arg = BaseVar(
|
||||
name=get_unique_variable_name(),
|
||||
type_=self.type_,
|
||||
_var_name=get_unique_variable_name(),
|
||||
_var_type=self._var_type,
|
||||
)
|
||||
return BaseVar(
|
||||
name=f"{self.full_name}.map(({arg.name}, i) => {fn(arg, key='i')})",
|
||||
type_=self.type_,
|
||||
is_local=self.is_local,
|
||||
_var_name=f"{self._var_full_name}.map(({arg._var_name}, i) => {fn(arg, key='i')})",
|
||||
_var_type=self._var_type,
|
||||
_var_is_local=self._var_is_local,
|
||||
)
|
||||
|
||||
def to(self, type_: Type) -> Var:
|
||||
@ -1112,22 +1161,26 @@ class Var(ABC):
|
||||
The converted var.
|
||||
"""
|
||||
return BaseVar(
|
||||
name=self.name,
|
||||
type_=type_,
|
||||
state=self.state,
|
||||
is_local=self.is_local,
|
||||
_var_name=self._var_name,
|
||||
_var_type=type_,
|
||||
_var_state=self._var_state,
|
||||
_var_is_local=self._var_is_local,
|
||||
)
|
||||
|
||||
@property
|
||||
def full_name(self) -> str:
|
||||
def _var_full_name(self) -> str:
|
||||
"""Get the full name of the var.
|
||||
|
||||
Returns:
|
||||
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.
|
||||
|
||||
Args:
|
||||
@ -1136,27 +1189,31 @@ class Var(ABC):
|
||||
Returns:
|
||||
The var with the set state.
|
||||
"""
|
||||
self.state = state.get_full_name()
|
||||
self._var_state = state.get_full_name()
|
||||
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."""
|
||||
|
||||
# The name of the var.
|
||||
name: str
|
||||
_var_name: str = dataclasses.field()
|
||||
|
||||
# The type of the var.
|
||||
type_: Any
|
||||
_var_type: Type = dataclasses.field(default=Any)
|
||||
|
||||
# The name of the enclosing state.
|
||||
state: str = ""
|
||||
_var_state: str = dataclasses.field(default="")
|
||||
|
||||
# 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.
|
||||
is_string: bool = False
|
||||
# Whether the var is a string literal.
|
||||
_var_is_string: bool = dataclasses.field(default=False)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
"""Define a hash function for a var.
|
||||
@ -1164,7 +1221,7 @@ class BaseVar(Var, Base):
|
||||
Returns:
|
||||
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:
|
||||
"""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.
|
||||
"""
|
||||
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):
|
||||
return ""
|
||||
@ -1210,10 +1269,10 @@ class BaseVar(Var, Base):
|
||||
Returns:
|
||||
The name of the setter function.
|
||||
"""
|
||||
setter = constants.SETTER_PREFIX + self.name
|
||||
if not include_state or self.state == "":
|
||||
setter = constants.SETTER_PREFIX + self._var_name
|
||||
if not include_state or self._var_state == "":
|
||||
return setter
|
||||
return ".".join((self.state, setter))
|
||||
return ".".join((self._var_state, setter))
|
||||
|
||||
def get_setter(self) -> Callable[[State, Any], None]:
|
||||
"""Get the var's setter function.
|
||||
@ -1229,46 +1288,59 @@ class BaseVar(Var, Base):
|
||||
state: The state within which we add the setter function.
|
||||
value: The value to set.
|
||||
"""
|
||||
if self.type_ in [int, float]:
|
||||
if self._var_type in [int, float]:
|
||||
try:
|
||||
value = self.type_(value)
|
||||
setattr(state, self.name, value)
|
||||
value = self._var_type(value)
|
||||
setattr(state, self._var_name, value)
|
||||
except ValueError:
|
||||
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:
|
||||
setattr(state, self.name, value)
|
||||
setattr(state, self._var_name, value)
|
||||
|
||||
setter.__qualname__ = self.get_setter_name()
|
||||
|
||||
return setter
|
||||
|
||||
|
||||
@dataclasses.dataclass(init=False, eq=False)
|
||||
class ComputedVar(Var, property):
|
||||
"""A field with computed getters."""
|
||||
|
||||
# Whether to track dependencies and cache computed values
|
||||
cache: bool = False
|
||||
_cache: bool = dataclasses.field(default=False)
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Get the name of the var.
|
||||
def __init__(
|
||||
self,
|
||||
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:
|
||||
The name of the var.
|
||||
Args:
|
||||
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."
|
||||
return self.fget.__name__
|
||||
property.__init__(self, fget, fset, fdel, doc)
|
||||
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
|
||||
def cache_attr(self) -> str:
|
||||
def _cache_attr(self) -> str:
|
||||
"""Get the attribute used to cache the value on the instance.
|
||||
|
||||
Returns:
|
||||
An attribute name.
|
||||
"""
|
||||
return f"__cached_{self.name}"
|
||||
return f"__cached_{self._var_name}"
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
"""Get the ComputedVar value.
|
||||
@ -1282,15 +1354,15 @@ class ComputedVar(Var, property):
|
||||
Returns:
|
||||
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)
|
||||
|
||||
# handle caching
|
||||
if not hasattr(instance, self.cache_attr):
|
||||
setattr(instance, self.cache_attr, super().__get__(instance, owner))
|
||||
return getattr(instance, self.cache_attr)
|
||||
if not hasattr(instance, self._cache_attr):
|
||||
setattr(instance, self._cache_attr, super().__get__(instance, owner))
|
||||
return getattr(instance, self._cache_attr)
|
||||
|
||||
def deps(
|
||||
def _deps(
|
||||
self,
|
||||
objclass: Type,
|
||||
obj: FunctionType | CodeType | None = None,
|
||||
@ -1348,7 +1420,7 @@ class ComputedVar(Var, property):
|
||||
elif self_is_top_of_stack and instruction.opname == "LOAD_METHOD":
|
||||
# method call on self
|
||||
d.update(
|
||||
self.deps(
|
||||
self._deps(
|
||||
objclass=objclass,
|
||||
obj=getattr(objclass, instruction.argval),
|
||||
)
|
||||
@ -1359,7 +1431,7 @@ class ComputedVar(Var, property):
|
||||
# recurse into nested functions / comprehensions, which can reference
|
||||
# instance attributes from the outer scope
|
||||
d.update(
|
||||
self.deps(
|
||||
self._deps(
|
||||
objclass=objclass,
|
||||
obj=instruction.argval,
|
||||
self_name=self_name,
|
||||
@ -1375,10 +1447,9 @@ class ComputedVar(Var, property):
|
||||
instance: the state instance that needs to recompute the value.
|
||||
"""
|
||||
with contextlib.suppress(AttributeError):
|
||||
delattr(instance, self.cache_attr)
|
||||
delattr(instance, self._cache_attr)
|
||||
|
||||
@property
|
||||
def type_(self):
|
||||
def _determine_var_type(self) -> Type:
|
||||
"""Get the type of the var.
|
||||
|
||||
Returns:
|
||||
@ -1403,7 +1474,7 @@ def cached_var(fget: Callable[[Any], Any]) -> ComputedVar:
|
||||
ComputedVar that is recomputed when dependencies change.
|
||||
"""
|
||||
cvar = ComputedVar(fget=fget)
|
||||
cvar.cache = True
|
||||
cvar._cache = True
|
||||
return cvar
|
||||
|
||||
|
||||
@ -1468,11 +1539,13 @@ def get_local_storage(key: Var | str | None = None) -> BaseVar:
|
||||
removal_version="0.3.0",
|
||||
)
|
||||
if key is not None:
|
||||
if not (isinstance(key, Var) and key.type_ == str) and not isinstance(key, str):
|
||||
type_ = type(key) if not isinstance(key, Var) else key.type_
|
||||
if not (isinstance(key, Var) and key._var_type == str) and not isinstance(
|
||||
key, str
|
||||
):
|
||||
type_ = type(key) if not isinstance(key, Var) else key._var_type
|
||||
raise TypeError(
|
||||
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, "'")
|
||||
return BaseVar(name=f"localStorage.getItem({key})", type_=str)
|
||||
return BaseVar(name="getAllLocalStorageItems()", type_=Dict)
|
||||
key = key._var_full_name if isinstance(key, Var) else format.wrap(key, "'")
|
||||
return BaseVar(_var_name=f"localStorage.getItem({key})", _var_type=str)
|
||||
return BaseVar(_var_name="getAllLocalStorageItems()", _var_type=Dict)
|
||||
|
@ -1,7 +1,7 @@
|
||||
""" Generated with stubgen from mypy, then manually edited, do not regen."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from _typeshed import Incomplete
|
||||
from abc import ABC
|
||||
from reflex import constants as constants
|
||||
from reflex.base import Base as Base
|
||||
from reflex.state import State as State
|
||||
@ -23,19 +23,19 @@ USED_VARIABLES: Incomplete
|
||||
|
||||
def get_unique_variable_name() -> str: ...
|
||||
|
||||
class Var(ABC):
|
||||
name: str
|
||||
type_: Type
|
||||
state: str = ""
|
||||
is_local: bool = False
|
||||
is_string: bool = False
|
||||
class Var:
|
||||
_var_name: str
|
||||
_var_type: Type
|
||||
_var_state: str = ""
|
||||
_var_is_local: bool = False
|
||||
_var_is_string: bool = False
|
||||
@classmethod
|
||||
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]: ...
|
||||
@classmethod
|
||||
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: ...
|
||||
@classmethod
|
||||
def __class_getitem__(cls, type_: str) -> _GenericAlias: ...
|
||||
@ -87,31 +87,31 @@ class Var(ABC):
|
||||
def foreach(self, fn: Callable) -> Var: ...
|
||||
def to(self, type_: Type) -> Var: ...
|
||||
@property
|
||||
def full_name(self) -> str: ...
|
||||
def set_state(self, state: Type[State]) -> Any: ...
|
||||
def _var_full_name(self) -> str: ...
|
||||
def _var_set_state(self, state: Type[State]) -> Any: ...
|
||||
|
||||
class BaseVar(Var, Base):
|
||||
name: str
|
||||
type_: Any
|
||||
state: str = ""
|
||||
is_local: bool = False
|
||||
is_string: bool = False
|
||||
@dataclass(eq=False)
|
||||
class BaseVar(Var):
|
||||
_var_name: str
|
||||
_var_type: Any
|
||||
_var_state: str = ""
|
||||
_var_is_local: bool = False
|
||||
_var_is_string: bool = False
|
||||
def __hash__(self) -> int: ...
|
||||
def get_default_value(self) -> Any: ...
|
||||
def get_setter_name(self, include_state: bool = ...) -> str: ...
|
||||
def get_setter(self) -> Callable[[State, Any], None]: ...
|
||||
|
||||
@dataclass(init=False)
|
||||
class ComputedVar(Var):
|
||||
cache: bool
|
||||
_var_cache: bool
|
||||
fget: FunctionType
|
||||
@property
|
||||
def name(self) -> str: ...
|
||||
@property
|
||||
def cache_attr(self) -> str: ...
|
||||
def _cache_attr(self) -> str: ...
|
||||
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: ...
|
||||
@property
|
||||
def type_(self): ...
|
||||
def _determine_var_type(self) -> Type: ...
|
||||
def __init__(self, func) -> None: ...
|
||||
|
||||
def cached_var(fget: Callable[[Any], Any]) -> ComputedVar: ...
|
||||
|
@ -34,7 +34,7 @@ def test_validate_data_table(data_table_state: rx.Var, expected):
|
||||
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=data_table_state.data, columns=data_table_state.columns
|
||||
)
|
||||
|
@ -51,7 +51,9 @@ def test_render_child_props():
|
||||
)._render()
|
||||
assert tag.props["sx"] == {"foo": "bar", "baz": "quuc"}
|
||||
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 tag.props["onChange"].events[0].handler == S.on_change
|
||||
@ -86,11 +88,13 @@ def test_render_child_props_recursive():
|
||||
)._render()
|
||||
assert tag.props["sx"] == {"foo": "bar", "baz": "quuc"}
|
||||
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["forceNotifyByEnter"].name == "false"
|
||||
assert tag.props["debounceTimeout"].name == "42"
|
||||
assert tag.props["forceNotifyOnBlur"]._var_name == "false"
|
||||
assert tag.props["forceNotifyByEnter"]._var_name == "false"
|
||||
assert tag.props["debounceTimeout"]._var_name == "42"
|
||||
assert len(tag.props["onChange"].events) == 1
|
||||
assert tag.props["onChange"].events[0].handler == S.on_change
|
||||
assert tag.contents == ""
|
||||
@ -102,7 +106,7 @@ def test_full_control_implicit_debounce():
|
||||
value=S.value,
|
||||
on_change=S.on_change,
|
||||
)._render()
|
||||
assert tag.props["debounceTimeout"].name == "50"
|
||||
assert tag.props["debounceTimeout"]._var_name == "50"
|
||||
assert len(tag.props["onChange"].events) == 1
|
||||
assert tag.props["onChange"].events[0].handler == S.on_change
|
||||
assert tag.contents == ""
|
||||
@ -114,7 +118,7 @@ def test_full_control_implicit_debounce_text_area():
|
||||
value=S.value,
|
||||
on_change=S.on_change,
|
||||
)._render()
|
||||
assert tag.props["debounceTimeout"].name == "50"
|
||||
assert tag.props["debounceTimeout"]._var_name == "50"
|
||||
assert len(tag.props["onChange"].events) == 1
|
||||
assert tag.props["onChange"].events[0].handler == S.on_change
|
||||
assert tag.contents == ""
|
||||
|
@ -105,7 +105,7 @@ def test_cond_no_else():
|
||||
comp = comp.children[0]
|
||||
assert isinstance(comp, Cond)
|
||||
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()
|
||||
|
||||
# Props do not support the use of cond without else
|
||||
|
@ -186,6 +186,6 @@ def test_foreach_render(state_var, render_fn, render_dict):
|
||||
rend = component.render()
|
||||
arg_index = rend["arg_index"]
|
||||
assert rend["iterable_state"] == render_dict["iterable_state"]
|
||||
assert arg_index.name == render_dict["arg_index"]
|
||||
assert arg_index.type_ == int
|
||||
assert arg_index._var_name == render_dict["arg_index"]
|
||||
assert arg_index._var_type == int
|
||||
assert rend["iterable_type"] == render_dict["iterable_type"]
|
||||
|
@ -43,7 +43,7 @@ try:
|
||||
pil_image: The image to serialize.
|
||||
"""
|
||||
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):
|
||||
"""Test that rendering an image works.
|
||||
@ -52,7 +52,7 @@ try:
|
||||
pil_image: The image to serialize.
|
||||
"""
|
||||
image = Image.create(src=pil_image)
|
||||
assert image.src.is_string # type: ignore
|
||||
assert image.src._var_is_string # type: ignore
|
||||
|
||||
except ImportError:
|
||||
|
||||
|
@ -109,7 +109,7 @@ def test_format_cond_tag():
|
||||
tag = CondTag(
|
||||
true_value=dict(Tag(name="h1", contents="True 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)
|
||||
cond, true_value, false_value = (
|
||||
@ -117,8 +117,8 @@ def test_format_cond_tag():
|
||||
tag_dict["true_value"],
|
||||
tag_dict["false_value"],
|
||||
)
|
||||
assert cond.name == "logged_in"
|
||||
assert cond.type_ == bool
|
||||
assert cond._var_name == "logged_in"
|
||||
assert cond._var_type == bool
|
||||
|
||||
assert true_value["name"] == "h1"
|
||||
assert true_value["contents"] == "True content"
|
||||
|
@ -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)
|
||||
assert set(app.pages.keys()) == {"test/[dynamic]"}
|
||||
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
|
||||
}
|
||||
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
|
||||
assert arg_name in app.state.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
|
||||
}
|
||||
assert constants.ROUTER_DATA in app.state().computed_var_dependencies
|
||||
|
@ -197,11 +197,11 @@ def test_base_class_vars(test_state):
|
||||
continue
|
||||
prop = getattr(cls, field)
|
||||
assert isinstance(prop, BaseVar)
|
||||
assert prop.name == field
|
||||
assert prop._var_name == field
|
||||
|
||||
assert cls.num1.type_ == int
|
||||
assert cls.num2.type_ == float
|
||||
assert cls.key.type_ == str
|
||||
assert cls.num1._var_type == int
|
||||
assert cls.num2._var_type == float
|
||||
assert cls.key._var_type == str
|
||||
|
||||
|
||||
def test_computed_class_var(test_state):
|
||||
@ -211,7 +211,7 @@ def test_computed_class_var(test_state):
|
||||
test_state: A 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 ("upper", str) in vars
|
||||
|
||||
@ -416,11 +416,13 @@ def test_set_class_var():
|
||||
"""Test setting the var of a class."""
|
||||
with pytest.raises(AttributeError):
|
||||
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
|
||||
assert var.name == "num3"
|
||||
assert var.type_ == int
|
||||
assert var.state == TestState.get_full_name()
|
||||
assert var._var_name == "num3"
|
||||
assert var._var_type == int
|
||||
assert var._var_state == TestState.get_full_name()
|
||||
|
||||
|
||||
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={"t2"}) == {"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",
|
||||
"t1",
|
||||
"t2",
|
||||
|
@ -16,11 +16,11 @@ from reflex.vars import (
|
||||
)
|
||||
|
||||
test_vars = [
|
||||
BaseVar(name="prop1", type_=int),
|
||||
BaseVar(name="key", type_=str),
|
||||
BaseVar(name="value", type_=str, state="state"),
|
||||
BaseVar(name="local", type_=str, state="state", is_local=True),
|
||||
BaseVar(name="local2", type_=str, is_local=True),
|
||||
BaseVar(_var_name="prop1", _var_type=int),
|
||||
BaseVar(_var_name="key", _var_type=str),
|
||||
BaseVar(_var_name="value", _var_type=str, _var_state="state"),
|
||||
BaseVar(_var_name="local", _var_type=str, _var_state="state", _var_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")]
|
||||
@ -124,7 +124,7 @@ def test_full_name(prop, expected):
|
||||
prop: The var to test.
|
||||
expected: The expected full name.
|
||||
"""
|
||||
assert prop.full_name == expected
|
||||
assert prop._var_full_name == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@ -147,14 +147,14 @@ def test_str(prop, expected):
|
||||
@pytest.mark.parametrize(
|
||||
"prop,expected",
|
||||
[
|
||||
(BaseVar(name="p", type_=int), 0),
|
||||
(BaseVar(name="p", type_=float), 0.0),
|
||||
(BaseVar(name="p", type_=str), ""),
|
||||
(BaseVar(name="p", type_=bool), False),
|
||||
(BaseVar(name="p", type_=list), []),
|
||||
(BaseVar(name="p", type_=dict), {}),
|
||||
(BaseVar(name="p", type_=tuple), ()),
|
||||
(BaseVar(name="p", type_=set), set()),
|
||||
(BaseVar(_var_name="p", _var_type=int), 0),
|
||||
(BaseVar(_var_name="p", _var_type=float), 0.0),
|
||||
(BaseVar(_var_name="p", _var_type=str), ""),
|
||||
(BaseVar(_var_name="p", _var_type=bool), False),
|
||||
(BaseVar(_var_name="p", _var_type=list), []),
|
||||
(BaseVar(_var_name="p", _var_type=dict), {}),
|
||||
(BaseVar(_var_name="p", _var_type=tuple), ()),
|
||||
(BaseVar(_var_name="p", _var_type=set), set()),
|
||||
],
|
||||
)
|
||||
def test_default_value(prop, expected):
|
||||
@ -194,13 +194,13 @@ def test_get_setter(prop, expected):
|
||||
"value,expected",
|
||||
[
|
||||
(None, None),
|
||||
(1, BaseVar(name="1", type_=int, is_local=True)),
|
||||
("key", BaseVar(name="key", type_=str, is_local=True)),
|
||||
(3.14, BaseVar(name="3.14", type_=float, is_local=True)),
|
||||
([1, 2, 3], BaseVar(name="[1, 2, 3]", type_=list, is_local=True)),
|
||||
(1, BaseVar(_var_name="1", _var_type=int, _var_is_local=True)),
|
||||
("key", BaseVar(_var_name="key", _var_type=str, _var_is_local=True)),
|
||||
(3.14, BaseVar(_var_name="3.14", _var_type=float, _var_is_local=True)),
|
||||
([1, 2, 3], BaseVar(_var_name="[1, 2, 3]", _var_type=list, _var_is_local=True)),
|
||||
(
|
||||
{"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:
|
||||
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)
|
||||
else Var.create(value, is_local=False)
|
||||
else Var.create(value, _var_is_local=False)
|
||||
)
|
||||
assert val is not None
|
||||
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({"a": 1, "b": 2})["a"]) == '{{"a": 1, "b": 2}["a"]}'
|
||||
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(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(BaseVar(name="foo", state="state", type_=list).reverse())
|
||||
str(BaseVar(_var_name="foo", _var_state="state", _var_type=list).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(
|
||||
@ -285,12 +289,12 @@ def test_basic_operations(TestObj):
|
||||
[
|
||||
(v([1, 2, 3]), "[1, 2, 3]"),
|
||||
(v(["1", "2", "3"]), '["1", "2", "3"]'),
|
||||
(BaseVar(name="foo", state="state", type_=list), "state.foo"),
|
||||
(BaseVar(name="foo", type_=list), "foo"),
|
||||
(BaseVar(_var_name="foo", _var_state="state", _var_type=list), "state.foo"),
|
||||
(BaseVar(_var_name="foo", _var_type=list), "foo"),
|
||||
(v((1, 2, 3)), "[1, 2, 3]"),
|
||||
(v(("1", "2", "3")), '["1", "2", "3"]'),
|
||||
(BaseVar(name="foo", state="state", type_=tuple), "state.foo"),
|
||||
(BaseVar(name="foo", type_=tuple), "foo"),
|
||||
(BaseVar(_var_name="foo", _var_state="state", _var_type=tuple), "state.foo"),
|
||||
(BaseVar(_var_name="foo", _var_type=tuple), "foo"),
|
||||
],
|
||||
)
|
||||
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(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_var = BaseVar(name="other", type_=str)
|
||||
other_state_var = BaseVar(_var_name="other", _var_state="state", _var_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_var)) == f"{{{expected}.includes(other)}}"
|
||||
|
||||
@ -308,15 +312,15 @@ def test_list_tuple_contains(var, expected):
|
||||
"var, expected",
|
||||
[
|
||||
(v("123"), json.dumps("123")),
|
||||
(BaseVar(name="foo", state="state", type_=str), "state.foo"),
|
||||
(BaseVar(name="foo", type_=str), "foo"),
|
||||
(BaseVar(_var_name="foo", _var_state="state", _var_type=str), "state.foo"),
|
||||
(BaseVar(_var_name="foo", _var_type=str), "foo"),
|
||||
],
|
||||
)
|
||||
def test_str_contains(var, expected):
|
||||
assert str(var.contains("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_var = BaseVar(name="other", type_=str)
|
||||
other_state_var = BaseVar(_var_name="other", _var_state="state", _var_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_var)) == f"{{{expected}.includes(other)}}"
|
||||
|
||||
@ -325,8 +329,8 @@ def test_str_contains(var, expected):
|
||||
"var, expected",
|
||||
[
|
||||
(v({"a": 1, "b": 2}), '{"a": 1, "b": 2}'),
|
||||
(BaseVar(name="foo", state="state", type_=dict), "state.foo"),
|
||||
(BaseVar(name="foo", type_=dict), "foo"),
|
||||
(BaseVar(_var_name="foo", _var_state="state", _var_type=dict), "state.foo"),
|
||||
(BaseVar(_var_name="foo", _var_type=dict), "foo"),
|
||||
],
|
||||
)
|
||||
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(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_var = BaseVar(name="other", type_=str)
|
||||
other_state_var = BaseVar(_var_name="other", _var_state="state", _var_type=str)
|
||||
other_var = BaseVar(_var_name="other", _var_type=str)
|
||||
assert (
|
||||
str(var.contains(other_state_var))
|
||||
== f"{{{expected}.hasOwnProperty(state.other)}}"
|
||||
@ -346,9 +350,9 @@ def test_dict_contains(var, expected):
|
||||
@pytest.mark.parametrize(
|
||||
"var",
|
||||
[
|
||||
BaseVar(name="list", type_=List[int]),
|
||||
BaseVar(name="tuple", type_=Tuple[int, int]),
|
||||
BaseVar(name="str", type_=str),
|
||||
BaseVar(_var_name="list", _var_type=List[int]),
|
||||
BaseVar(_var_name="tuple", _var_type=Tuple[int, int]),
|
||||
BaseVar(_var_name="str", _var_type=str),
|
||||
],
|
||||
)
|
||||
def test_var_indexing_lists(var):
|
||||
@ -358,49 +362,70 @@ def test_var_indexing_lists(var):
|
||||
var : The str, list or tuple base var.
|
||||
"""
|
||||
# Test basic indexing.
|
||||
assert str(var[0]) == f"{{{var.name}.at(0)}}"
|
||||
assert str(var[1]) == f"{{{var.name}.at(1)}}"
|
||||
assert str(var[0]) == f"{{{var._var_name}.at(0)}}"
|
||||
assert str(var[1]) == f"{{{var._var_name}.at(1)}}"
|
||||
|
||||
# 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(
|
||||
"var, index",
|
||||
[
|
||||
(BaseVar(name="lst", type_=List[int]), [1, 2]),
|
||||
(BaseVar(name="lst", type_=List[int]), {"name": "dict"}),
|
||||
(BaseVar(name="lst", type_=List[int]), {"set"}),
|
||||
(BaseVar(_var_name="lst", _var_type=List[int]), [1, 2]),
|
||||
(BaseVar(_var_name="lst", _var_type=List[int]), {"name": "dict"}),
|
||||
(BaseVar(_var_name="lst", _var_type=List[int]), {"set"}),
|
||||
(
|
||||
BaseVar(name="lst", type_=List[int]),
|
||||
BaseVar(_var_name="lst", _var_type=List[int]),
|
||||
(
|
||||
1,
|
||||
2,
|
||||
),
|
||||
),
|
||||
(BaseVar(name="lst", type_=List[int]), 1.5),
|
||||
(BaseVar(name="lst", 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(_var_name="lst", _var_type=List[int]), 1.5),
|
||||
(BaseVar(_var_name="lst", _var_type=List[int]), "str"),
|
||||
(
|
||||
BaseVar(name="lst", type_=List[int]),
|
||||
BaseVar(name="list_var", type_=List[int]),
|
||||
BaseVar(_var_name="lst", _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(name="dict_var", type_=Dict[str, str]),
|
||||
BaseVar(_var_name="lst", _var_type=List[int]),
|
||||
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):
|
||||
@ -417,9 +442,9 @@ def test_var_unsupported_indexing_lists(var, index):
|
||||
@pytest.mark.parametrize(
|
||||
"var",
|
||||
[
|
||||
BaseVar(name="lst", type_=List[int]),
|
||||
BaseVar(name="tuple", type_=Tuple[int, int]),
|
||||
BaseVar(name="str", type_=str),
|
||||
BaseVar(_var_name="lst", _var_type=List[int]),
|
||||
BaseVar(_var_name="tuple", _var_type=Tuple[int, int]),
|
||||
BaseVar(_var_name="str", _var_type=str),
|
||||
],
|
||||
)
|
||||
def test_var_list_slicing(var):
|
||||
@ -428,14 +453,14 @@ def test_var_list_slicing(var):
|
||||
Args:
|
||||
var : The str, list or tuple base var.
|
||||
"""
|
||||
assert str(var[:1]) == f"{{{var.name}.slice(0, 1)}}"
|
||||
assert str(var[:1]) == f"{{{var.name}.slice(0, 1)}}"
|
||||
assert str(var[:]) == f"{{{var.name}.slice(0, undefined)}}"
|
||||
assert str(var[:1]) == f"{{{var._var_name}.slice(0, 1)}}"
|
||||
assert str(var[:1]) == f"{{{var._var_name}.slice(0, 1)}}"
|
||||
assert str(var[:]) == f"{{{var._var_name}.slice(0, undefined)}}"
|
||||
|
||||
|
||||
def test_dict_indexing():
|
||||
"""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.
|
||||
assert str(dct["a"]) == '{dct["a"]}'
|
||||
@ -446,66 +471,66 @@ def test_dict_indexing():
|
||||
"var, index",
|
||||
[
|
||||
(
|
||||
BaseVar(name="dict", type_=Dict[str, str]),
|
||||
BaseVar(_var_name="dict", _var_type=Dict[str, str]),
|
||||
[1, 2],
|
||||
),
|
||||
(
|
||||
BaseVar(name="dict", type_=Dict[str, str]),
|
||||
BaseVar(_var_name="dict", _var_type=Dict[str, str]),
|
||||
{"name": "dict"},
|
||||
),
|
||||
(
|
||||
BaseVar(name="dict", type_=Dict[str, str]),
|
||||
BaseVar(_var_name="dict", _var_type=Dict[str, str]),
|
||||
{"set"},
|
||||
),
|
||||
(
|
||||
BaseVar(name="dict", type_=Dict[str, str]),
|
||||
BaseVar(_var_name="dict", _var_type=Dict[str, str]),
|
||||
(
|
||||
1,
|
||||
2,
|
||||
),
|
||||
),
|
||||
(
|
||||
BaseVar(name="lst", type_=Dict[str, str]),
|
||||
BaseVar(name="list_var", type_=List[int]),
|
||||
BaseVar(_var_name="lst", _var_type=Dict[str, str]),
|
||||
BaseVar(_var_name="list_var", _var_type=List[int]),
|
||||
),
|
||||
(
|
||||
BaseVar(name="lst", type_=Dict[str, str]),
|
||||
BaseVar(name="set_var", type_=Set[str]),
|
||||
BaseVar(_var_name="lst", _var_type=Dict[str, str]),
|
||||
BaseVar(_var_name="set_var", _var_type=Set[str]),
|
||||
),
|
||||
(
|
||||
BaseVar(name="lst", type_=Dict[str, str]),
|
||||
BaseVar(name="dict_var", type_=Dict[str, str]),
|
||||
BaseVar(_var_name="lst", _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],
|
||||
),
|
||||
(
|
||||
BaseVar(name="df", type_=DataFrame),
|
||||
BaseVar(_var_name="df", _var_type=DataFrame),
|
||||
{"name": "dict"},
|
||||
),
|
||||
(
|
||||
BaseVar(name="df", type_=DataFrame),
|
||||
BaseVar(_var_name="df", _var_type=DataFrame),
|
||||
{"set"},
|
||||
),
|
||||
(
|
||||
BaseVar(name="df", type_=DataFrame),
|
||||
BaseVar(_var_name="df", _var_type=DataFrame),
|
||||
(
|
||||
1,
|
||||
2,
|
||||
),
|
||||
),
|
||||
(
|
||||
BaseVar(name="df", type_=DataFrame),
|
||||
BaseVar(name="list_var", type_=List[int]),
|
||||
BaseVar(_var_name="df", _var_type=DataFrame),
|
||||
BaseVar(_var_name="list_var", _var_type=List[int]),
|
||||
),
|
||||
(
|
||||
BaseVar(name="df", type_=DataFrame),
|
||||
BaseVar(name="set_var", type_=Set[str]),
|
||||
BaseVar(_var_name="df", _var_type=DataFrame),
|
||||
BaseVar(_var_name="set_var", _var_type=Set[str]),
|
||||
),
|
||||
(
|
||||
BaseVar(name="df", type_=DataFrame),
|
||||
BaseVar(name="dict_var", type_=Dict[str, str]),
|
||||
BaseVar(_var_name="df", _var_type=DataFrame),
|
||||
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
|
||||
assert (
|
||||
err.value.args[0]
|
||||
== f"The State var `{full_name}` has no attribute 'foo' or may have been annotated wrongly.\n"
|
||||
f"original message: 'ComputedVar' object has no attribute 'foo'"
|
||||
== f"The State var `{full_name}` has no attribute 'foo' or may have been annotated wrongly."
|
||||
)
|
||||
|
||||
|
||||
@ -605,16 +629,19 @@ def test_import_var(import_var, expected):
|
||||
@pytest.mark.parametrize(
|
||||
"key, expected",
|
||||
[
|
||||
("test_key", BaseVar(name="localStorage.getItem('test_key')", type_=str)),
|
||||
(
|
||||
BaseVar(name="key_var", type_=str),
|
||||
BaseVar(name="localStorage.getItem(key_var)", type_=str),
|
||||
"test_key",
|
||||
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,
|
||||
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):
|
||||
@ -626,8 +653,8 @@ def test_get_local_storage(key, expected):
|
||||
|
||||
"""
|
||||
local_storage = get_local_storage(key)
|
||||
assert local_storage.name == expected.name
|
||||
assert local_storage.type_ == expected.type_
|
||||
assert local_storage._var_name == expected._var_name
|
||||
assert local_storage._var_type == expected._var_type
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@ -636,8 +663,8 @@ def test_get_local_storage(key, expected):
|
||||
["list", "values"],
|
||||
{"name": "dict"},
|
||||
10,
|
||||
BaseVar(name="key_var", type_=List),
|
||||
BaseVar(name="key_var", type_=Dict[str, str]),
|
||||
BaseVar(_var_name="key_var", _var_type=List),
|
||||
BaseVar(_var_name="key_var", _var_type=Dict[str, str]),
|
||||
],
|
||||
)
|
||||
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:
|
||||
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 (
|
||||
err.value.args[0]
|
||||
== 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(
|
||||
"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}",
|
||||
),
|
||||
(
|
||||
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",
|
||||
),
|
||||
],
|
||||
@ -676,14 +703,14 @@ def test_fstrings(out, expected):
|
||||
@pytest.mark.parametrize(
|
||||
"var",
|
||||
[
|
||||
BaseVar(name="var", type_=int),
|
||||
BaseVar(name="var", type_=float),
|
||||
BaseVar(name="var", type_=str),
|
||||
BaseVar(name="var", type_=bool),
|
||||
BaseVar(name="var", type_=dict),
|
||||
BaseVar(name="var", type_=tuple),
|
||||
BaseVar(name="var", type_=set),
|
||||
BaseVar(name="var", type_=None),
|
||||
BaseVar(_var_name="var", _var_type=int),
|
||||
BaseVar(_var_name="var", _var_type=float),
|
||||
BaseVar(_var_name="var", _var_type=str),
|
||||
BaseVar(_var_name="var", _var_type=bool),
|
||||
BaseVar(_var_name="var", _var_type=dict),
|
||||
BaseVar(_var_name="var", _var_type=tuple),
|
||||
BaseVar(_var_name="var", _var_type=set),
|
||||
BaseVar(_var_name="var", _var_type=None),
|
||||
],
|
||||
)
|
||||
def test_unsupported_types_for_reverse(var):
|
||||
@ -700,11 +727,11 @@ def test_unsupported_types_for_reverse(var):
|
||||
@pytest.mark.parametrize(
|
||||
"var",
|
||||
[
|
||||
BaseVar(name="var", type_=int),
|
||||
BaseVar(name="var", type_=float),
|
||||
BaseVar(name="var", type_=bool),
|
||||
BaseVar(name="var", type_=set),
|
||||
BaseVar(name="var", type_=None),
|
||||
BaseVar(_var_name="var", _var_type=int),
|
||||
BaseVar(_var_name="var", _var_type=float),
|
||||
BaseVar(_var_name="var", _var_type=bool),
|
||||
BaseVar(_var_name="var", _var_type=set),
|
||||
BaseVar(_var_name="var", _var_type=None),
|
||||
],
|
||||
)
|
||||
def test_unsupported_types_for_contains(var):
|
||||
@ -717,34 +744,34 @@ def test_unsupported_types_for_contains(var):
|
||||
assert var.contains(1)
|
||||
assert (
|
||||
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(
|
||||
"other",
|
||||
[
|
||||
BaseVar(name="other", type_=int),
|
||||
BaseVar(name="other", type_=float),
|
||||
BaseVar(name="other", type_=bool),
|
||||
BaseVar(name="other", type_=list),
|
||||
BaseVar(name="other", type_=dict),
|
||||
BaseVar(name="other", type_=tuple),
|
||||
BaseVar(name="other", type_=set),
|
||||
BaseVar(_var_name="other", _var_type=int),
|
||||
BaseVar(_var_name="other", _var_type=float),
|
||||
BaseVar(_var_name="other", _var_type=bool),
|
||||
BaseVar(_var_name="other", _var_type=list),
|
||||
BaseVar(_var_name="other", _var_type=dict),
|
||||
BaseVar(_var_name="other", _var_type=tuple),
|
||||
BaseVar(_var_name="other", _var_type=set),
|
||||
],
|
||||
)
|
||||
def test_unsupported_types_for_string_contains(other):
|
||||
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 (
|
||||
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():
|
||||
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 (
|
||||
err.value.args[0]
|
||||
== "'in' operator not supported for Var types, use Var.contains() instead."
|
||||
|
@ -211,11 +211,11 @@ def test_format_string(input: str, output: str):
|
||||
"input,output",
|
||||
[
|
||||
(Var.create(value="test"), "{`test`}"),
|
||||
(Var.create(value="test", is_local=True), "{`test`}"),
|
||||
(Var.create(value="test", is_local=False), "{test}"),
|
||||
(Var.create(value="test", is_string=True), "{`test`}"),
|
||||
(Var.create(value="test", is_string=False), "{`test`}"),
|
||||
(Var.create(value="test", is_local=False, is_string=False), "{test}"),
|
||||
(Var.create(value="test", _var_is_local=True), "{`test`}"),
|
||||
(Var.create(value="test", _var_is_local=False), "{test}"),
|
||||
(Var.create(value="test", _var_is_string=True), "{`test`}"),
|
||||
(Var.create(value="test", _var_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):
|
||||
@ -285,7 +285,7 @@ def test_format_cond(condition: str, true_value: str, false_value: str, expected
|
||||
(
|
||||
{
|
||||
"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}}',
|
||||
),
|
||||
@ -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)}',
|
||||
),
|
||||
({"a": "red", "b": "blue"}, '{{"a": "red", "b": "blue"}}'),
|
||||
(BaseVar(name="var", type_="int"), "{var}"),
|
||||
(BaseVar(_var_name="var", _var_type="int"), "{var}"),
|
||||
(
|
||||
BaseVar(
|
||||
name="_",
|
||||
type_=Any,
|
||||
state="",
|
||||
is_local=True,
|
||||
is_string=False,
|
||||
_var_name="_",
|
||||
_var_type=Any,
|
||||
_var_state="",
|
||||
_var_is_local=True,
|
||||
_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"]}}',
|
||||
),
|
||||
# tricky real-world case from markdown component
|
||||
|
@ -105,16 +105,16 @@ def test_add_serializer():
|
||||
([1, 2, 3], "[1, 2, 3]"),
|
||||
([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, 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: 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}',
|
||||
),
|
||||
(datetime.datetime(2021, 1, 1, 1, 1, 1, 1), "2021-01-01 01:01:01.000001"),
|
||||
|
Loading…
Reference in New Issue
Block a user