From 024cb5fa9b1003941d4235cb999d9d59b460f062 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Fri, 13 Oct 2023 14:53:55 -0700 Subject: [PATCH] Var field cleanup (#1943) --- integration/test_event_chain.py | 4 +- reflex/base.py | 6 +- reflex/compiler/utils.py | 2 +- reflex/components/base/script.py | 2 +- reflex/components/component.py | 20 +- reflex/components/datadisplay/code.py | 5 +- reflex/components/datadisplay/datatable.py | 22 +- reflex/components/datadisplay/table.py | 10 +- reflex/components/forms/debounce.py | 6 +- reflex/components/forms/form.py | 6 +- reflex/components/forms/multiselect.py | 4 +- reflex/components/forms/upload.py | 16 +- reflex/components/layout/cond.py | 8 +- reflex/components/layout/foreach.py | 26 +- reflex/components/media/image.py | 2 +- reflex/components/navigation/link.py | 2 +- reflex/components/overlay/banner.py | 8 +- reflex/components/tags/iter_tag.py | 10 +- reflex/components/typography/markdown.py | 30 +- reflex/event.py | 14 +- reflex/state.py | 27 +- reflex/style.py | 4 +- reflex/testing.py | 15 + reflex/utils/format.py | 47 +- reflex/vars.py | 481 ++++++++++-------- reflex/vars.pyi | 48 +- .../components/datadisplay/test_datatable.py | 2 +- tests/components/forms/test_debounce.py | 18 +- tests/components/layout/test_cond.py | 2 +- tests/components/layout/test_foreach.py | 4 +- tests/components/media/test_image.py | 4 +- tests/components/test_tag.py | 6 +- tests/test_app.py | 4 +- tests/test_state.py | 22 +- tests/test_var.py | 293 ++++++----- tests/utils/test_format.py | 35 +- tests/utils/test_serializers.py | 6 +- 37 files changed, 691 insertions(+), 530 deletions(-) diff --git a/integration/test_event_chain.py b/integration/test_event_chain.py index 454cf51a6..7e5d81197 100644 --- a/integration/test_event_chain.py +++ b/integration/test_event_chain.py @@ -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", diff --git a/reflex/base.py b/reflex/base.py index 53510786b..fef91189b 100644 --- a/reflex/base.py +++ b/reflex/base.py @@ -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. diff --git a/reflex/compiler/utils.py b/reflex/compiler/utils.py index 3b737f6ff..f8c894202 100644 --- a/reflex/compiler/utils.py +++ b/reflex/compiler/utils.py @@ -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 ( diff --git a/reflex/components/base/script.py b/reflex/components/base/script.py index 45fb137b4..67470f273 100644 --- a/reflex/components/base/script.py +++ b/reflex/components/base/script.py @@ -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) diff --git a/reflex/components/component.py b/reflex/components/component.py index b0a907b89..300bad3de 100644 --- a/reflex/components/component.py +++ b/reflex/components/component.py @@ -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() ] diff --git a/reflex/components/datadisplay/code.py b/reflex/components/datadisplay/code.py index 31a9291ea..7ab991a12 100644 --- a/reflex/components/datadisplay/code.py +++ b/reflex/components/datadisplay/code.py @@ -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 diff --git a/reflex/components/datadisplay/datatable.py b/reflex/components/datadisplay/datatable.py index 302369ea5..52bd45282 100644 --- a/reflex/components/datadisplay/datatable.py +++ b/reflex/components/datadisplay/datatable.py @@ -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 diff --git a/reflex/components/datadisplay/table.py b/reflex/components/datadisplay/table.py index 28b9a0b4a..3094f7470 100644 --- a/reflex/components/datadisplay/table.py +++ b/reflex/components/datadisplay/table.py @@ -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) diff --git a/reflex/components/forms/debounce.py b/reflex/components/forms/debounce.py index fb35a4bf1..5095fe405 100644 --- a/reflex/components/forms/debounce.py +++ b/reflex/components/forms/debounce.py @@ -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: "") diff --git a/reflex/components/forms/form.py b/reflex/components/forms/form.py index 2f5dc57f7..2572c5746 100644 --- a/reflex/components/forms/form.py +++ b/reflex/components/forms/form.py @@ -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(), diff --git a/reflex/components/forms/multiselect.py b/reflex/components/forms/multiselect.py index 9625e1598..453b21284 100644 --- a/reflex/components/forms/multiselect.py +++ b/reflex/components/forms/multiselect.py @@ -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] ), diff --git a/reflex/components/forms/upload.py b/reflex/components/forms/upload.py index bb0781f4f..d1a91adc7 100644 --- a/reflex/components/forms/upload.py +++ b/reflex/components/forms/upload.py @@ -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) diff --git a/reflex/components/layout/cond.py b/reflex/components/layout/cond.py index e9e3d22f8..b584b0f0c 100644 --- a/reflex/components/layout/cond.py +++ b/reflex/components/layout/cond.py @@ -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), ) diff --git a/reflex/components/layout/foreach.py b/reflex/components/layout/foreach.py index af04b186c..325615577 100644 --- a/reflex/components/layout/foreach.py +++ b/reflex/components/layout/foreach.py @@ -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__, ) diff --git a/reflex/components/media/image.py b/reflex/components/media/image.py index 0109e0dad..74f905da9 100644 --- a/reflex/components/media/image.py +++ b/reflex/components/media/image.py @@ -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) diff --git a/reflex/components/navigation/link.py b/reflex/components/navigation/link.py index 9c66f712f..6b8c61cd7 100644 --- a/reflex/components/navigation/link.py +++ b/reflex/components/navigation/link.py @@ -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] diff --git a/reflex/components/overlay/banner.py b/reflex/components/overlay/banner.py index 4a37346c9..7b9a31eb9 100644 --- a/reflex/components/overlay/banner.py +++ b/reflex/components/overlay/banner.py @@ -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): diff --git a/reflex/components/tags/iter_tag.py b/reflex/components/tags/iter_tag.py index b64c669bf..e70e2416b 100644 --- a/reflex/components/tags/iter_tag.py +++ b/reflex/components/tags/iter_tag.py @@ -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 diff --git a/reflex/components/typography/markdown.py b/reflex/components/typography/markdown.py index dc1c3712a..ab53480f2 100644 --- a/reflex/components/typography/markdown.py +++ b/reflex/components/typography/markdown.py @@ -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-(?.*)/); 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", " " diff --git a/reflex/event.py b/reflex/event.py index 1251bc623..450c7cd98 100644 --- a/reflex/event.py +++ b/reflex/event.py @@ -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( diff --git a/reflex/state.py b/reflex/state.py index b716a5e46..f9ab8a8c3 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -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: diff --git a/reflex/style.py b/reflex/style.py index cf1125350..bb8320163 100644 --- a/reflex/style.py +++ b/reflex/style.py @@ -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): diff --git a/reflex/testing.py b/reflex/testing.py index 167841d12..e9962ab8a 100644 --- a/reflex/testing.py +++ b/reflex/testing.py @@ -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() diff --git a/reflex/utils/format.py b/reflex/utils/format.py index dd0948fdc..ab5cc80c6 100644 --- a/reflex/utils/format.py +++ b/reflex/utils/format.py @@ -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}" diff --git a/reflex/vars.py b/reflex/vars.py index 2891dcbed..a1a3d72bc 100644 --- a/reflex/vars.py +++ b/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 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 ( 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 ' requires string as left operand, not {other.type_}" + f"'in ' 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) diff --git a/reflex/vars.pyi b/reflex/vars.pyi index bd820dd61..1040e202b 100644 --- a/reflex/vars.pyi +++ b/reflex/vars.pyi @@ -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: ... diff --git a/tests/components/datadisplay/test_datatable.py b/tests/components/datadisplay/test_datatable.py index f6a154b0a..2910878ba 100644 --- a/tests/components/datadisplay/test_datatable.py +++ b/tests/components/datadisplay/test_datatable.py @@ -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 ) diff --git a/tests/components/forms/test_debounce.py b/tests/components/forms/test_debounce.py index 128afb16d..1965fabf5 100644 --- a/tests/components/forms/test_debounce.py +++ b/tests/components/forms/test_debounce.py @@ -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 == "" diff --git a/tests/components/layout/test_cond.py b/tests/components/layout/test_cond.py index 7b1b18537..3bf373bb4 100644 --- a/tests/components/layout/test_cond.py +++ b/tests/components/layout/test_cond.py @@ -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 diff --git a/tests/components/layout/test_foreach.py b/tests/components/layout/test_foreach.py index 6830985b5..5b407d77f 100644 --- a/tests/components/layout/test_foreach.py +++ b/tests/components/layout/test_foreach.py @@ -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"] diff --git a/tests/components/media/test_image.py b/tests/components/media/test_image.py index 9651c5dfd..9d11dcfc0 100644 --- a/tests/components/media/test_image.py +++ b/tests/components/media/test_image.py @@ -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: diff --git a/tests/components/test_tag.py b/tests/components/test_tag.py index daf5418eb..35e943fb0 100644 --- a/tests/components/test_tag.py +++ b/tests/components/test_tag.py @@ -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" diff --git a/tests/test_app.py b/tests/test_app.py index 4986205ef..41e185e5e 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -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 diff --git a/tests/test_state.py b/tests/test_state.py index a3dc49cda..308d9282b 100644 --- a/tests/test_state.py +++ b/tests/test_state.py @@ -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", diff --git a/tests/test_var.py b/tests/test_var.py index 3d9d212a2..7dcfde050 100644 --- a/tests/test_var.py +++ b/tests/test_var.py @@ -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 ' requires string as left operand, not {other.type_}" + == f"'in ' 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." diff --git a/tests/utils/test_format.py b/tests/utils/test_format.py index 61c7427ea..d2a19b799 100644 --- a/tests/utils/test_format.py +++ b/tests/utils/test_format.py @@ -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 diff --git a/tests/utils/test_serializers.py b/tests/utils/test_serializers.py index 778e55c06..5abad9f15 100644 --- a/tests/utils/test_serializers.py +++ b/tests/utils/test_serializers.py @@ -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"),