From 71db9ae76a026438870b40b55ae780645306b5b8 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 31 Jul 2024 10:30:36 -0700 Subject: [PATCH] add type hinting to existing types --- reflex/experimental/vars/base.py | 52 ++++++- reflex/experimental/vars/function.py | 2 +- reflex/experimental/vars/number.py | 4 +- reflex/experimental/vars/object.py | 222 ++++++++++++++++++++++++--- reflex/experimental/vars/sequence.py | 170 +++++++++++++++----- tests/test_var.py | 27 +++- 6 files changed, 412 insertions(+), 65 deletions(-) diff --git a/reflex/experimental/vars/base.py b/reflex/experimental/vars/base.py index dadcc38bd99..7da1e6537af 100644 --- a/reflex/experimental/vars/base.py +++ b/reflex/experimental/vars/base.py @@ -10,9 +10,15 @@ TYPE_CHECKING, Any, Callable, + Dict, + Generic, + List, Optional, + Set, + Tuple, Type, TypeVar, + Union, overload, ) @@ -42,13 +48,15 @@ from .object import ObjectVar, ToObjectOperation from .sequence import ArrayVar, StringVar, ToArrayOperation, ToStringOperation +VAR_TYPE = TypeVar("VAR_TYPE") + @dataclasses.dataclass( eq=False, frozen=True, **{"slots": True} if sys.version_info >= (3, 10) else {}, ) -class ImmutableVar(Var): +class ImmutableVar(Var, Generic[VAR_TYPE]): """Base class for immutable vars.""" # The name of the var. @@ -405,6 +413,8 @@ def guess_type(self) -> ImmutableVar: return self.to(ArrayVar, var_type) if issubclass(fixed_type, str): return self.to(StringVar) + if issubclass(fixed_type, Base): + return self.to(ObjectVar, var_type) return self @@ -531,3 +541,43 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> T: return wrapper return decorator + + +def unionize(*args: Type) -> Type: + """Unionize the types. + + Args: + args: The types to unionize. + + Returns: + The unionized types. + """ + if not args: + return Any + first, *rest = args + if not rest: + return first + return Union[first, unionize(*rest)] + + +def figure_out_type(value: Any) -> Type: + """Figure out the type of the value. + + Args: + value: The value to figure out the type of. + + Returns: + The type of the value. + """ + if isinstance(value, list): + return List[unionize(*(figure_out_type(v) for v in value))] + if isinstance(value, set): + return Set[unionize(*(figure_out_type(v) for v in value))] + if isinstance(value, tuple): + return Tuple[unionize(*(figure_out_type(v) for v in value)), ...] + if isinstance(value, dict): + return Dict[ + unionize(*(figure_out_type(k) for k in value)), + unionize(*(figure_out_type(v) for v in value.values())), + ] + return type(value) diff --git a/reflex/experimental/vars/function.py b/reflex/experimental/vars/function.py index adce1329d1d..4514a482d15 100644 --- a/reflex/experimental/vars/function.py +++ b/reflex/experimental/vars/function.py @@ -11,7 +11,7 @@ from reflex.vars import ImmutableVarData, Var, VarData -class FunctionVar(ImmutableVar): +class FunctionVar(ImmutableVar[Callable]): """Base class for immutable function vars.""" def __call__(self, *args: Var | Any) -> ArgsFunctionOperation: diff --git a/reflex/experimental/vars/number.py b/reflex/experimental/vars/number.py index c83c5c4d2d5..6bd3a7ff767 100644 --- a/reflex/experimental/vars/number.py +++ b/reflex/experimental/vars/number.py @@ -15,7 +15,7 @@ from reflex.vars import ImmutableVarData, Var, VarData -class NumberVar(ImmutableVar): +class NumberVar(ImmutableVar[Union[int, float]]): """Base class for immutable number vars.""" def __add__(self, other: number_types | boolean_types) -> NumberAddOperation: @@ -693,7 +693,7 @@ def _cached_var_name(self) -> str: return f"Math.trunc({str(value)})" -class BooleanVar(ImmutableVar): +class BooleanVar(ImmutableVar[bool]): """Base class for immutable boolean vars.""" def __and__(self, other: bool) -> BooleanAndOperation: diff --git a/reflex/experimental/vars/object.py b/reflex/experimental/vars/object.py index 4522473c764..a1655083da1 100644 --- a/reflex/experimental/vars/object.py +++ b/reflex/experimental/vars/object.py @@ -6,23 +6,69 @@ import sys import typing from functools import cached_property -from typing import Any, Dict, List, Tuple, Type, Union +from inspect import isclass +from typing import ( + Any, + Dict, + List, + NoReturn, + Tuple, + Type, + TypeVar, + Union, + get_args, + overload, +) + +from typing_extensions import get_origin -from reflex.experimental.vars.base import ImmutableVar, LiteralVar -from reflex.experimental.vars.sequence import ArrayVar, unionize +from reflex.experimental.vars.base import ( + ImmutableVar, + LiteralVar, + figure_out_type, +) +from reflex.experimental.vars.number import NumberVar +from reflex.experimental.vars.sequence import ArrayVar, StringVar +from reflex.utils.exceptions import VarAttributeError +from reflex.utils.types import GenericType, get_attribute_access_type from reflex.vars import ImmutableVarData, Var, VarData +OBJECT_TYPE = TypeVar("OBJECT_TYPE") + +KEY_TYPE = TypeVar("KEY_TYPE") +VALUE_TYPE = TypeVar("VALUE_TYPE") + +ARRAY_INNER_TYPE = TypeVar("ARRAY_INNER_TYPE") -class ObjectVar(ImmutableVar): +OTHER_KEY_TYPE = TypeVar("OTHER_KEY_TYPE") + + +class ObjectVar(ImmutableVar[OBJECT_TYPE]): """Base class for immutable object vars.""" + @overload + def _key_type(self: ObjectVar[Dict[KEY_TYPE, VALUE_TYPE]]) -> KEY_TYPE: ... + + @overload + def _key_type(self) -> Type: ... + def _key_type(self) -> Type: """Get the type of the keys of the object. Returns: The type of the keys of the object. """ - return ImmutableVar + fixed_type = ( + self._var_type if isclass(self._var_type) else get_origin(self._var_type) + ) + args = get_args(self._var_type) if issubclass(fixed_type, dict) else () + return args[0] if args else Any + + @overload + def _value_type(self: ObjectVar[Dict[KEY_TYPE, VALUE_TYPE]]) -> VALUE_TYPE: ... + + @overload + def _value_type(self) -> Type: ... def _value_type(self) -> Type: """Get the type of the values of the object. @@ -30,9 +76,21 @@ def _value_type(self) -> Type: Returns: The type of the values of the object. """ - return ImmutableVar + fixed_type = ( + self._var_type if isclass(self._var_type) else get_origin(self._var_type) + ) + args = get_args(self._var_type) if issubclass(fixed_type, dict) else () + return args[1] if args else Any + + @overload + def keys( + self: ObjectVar[Dict[KEY_TYPE, VALUE_TYPE]], + ) -> ArrayVar[List[KEY_TYPE]]: ... - def keys(self) -> ObjectKeysOperation: + @overload + def keys(self) -> ArrayVar: ... + + def keys(self) -> ArrayVar: """Get the keys of the object. Returns: @@ -40,7 +98,15 @@ def keys(self) -> ObjectKeysOperation: """ return ObjectKeysOperation(self) - def values(self) -> ObjectValuesOperation: + @overload + def values( + self: ObjectVar[Dict[KEY_TYPE, VALUE_TYPE]], + ) -> ArrayVar[List[VALUE_TYPE]]: ... + + @overload + def values(self) -> ArrayVar: ... + + def values(self) -> ArrayVar: """Get the values of the object. Returns: @@ -48,7 +114,15 @@ def values(self) -> ObjectValuesOperation: """ return ObjectValuesOperation(self) - def entries(self) -> ObjectEntriesOperation: + @overload + def entries( + self: ObjectVar[Dict[KEY_TYPE, VALUE_TYPE]], + ) -> ArrayVar[List[Tuple[KEY_TYPE, VALUE_TYPE]]]: ... + + @overload + def entries(self) -> ArrayVar: ... + + def entries(self) -> ArrayVar: """Get the entries of the object. Returns: @@ -67,6 +141,53 @@ def merge(self, other: ObjectVar) -> ObjectMergeOperation: """ return ObjectMergeOperation(self, other) + # NoReturn is used here to catch when key value is Any + @overload + def __getitem__( + self: ObjectVar[Dict[KEY_TYPE, NoReturn]], + key: Var | Any, + ) -> ImmutableVar: ... + + @overload + def __getitem__( + self: ( + ObjectVar[Dict[KEY_TYPE, int]] + | ObjectVar[Dict[KEY_TYPE, float]] + | ObjectVar[Dict[KEY_TYPE, int | float]] + ), + key: Var | Any, + ) -> NumberVar: ... + + @overload + def __getitem__( + self: ObjectVar[Dict[KEY_TYPE, str]], + key: Var | Any, + ) -> StringVar: ... + + @overload + def __getitem__( + self: ObjectVar[Dict[KEY_TYPE, list[ARRAY_INNER_TYPE]]], + key: Var | Any, + ) -> ArrayVar[list[ARRAY_INNER_TYPE]]: ... + + @overload + def __getitem__( + self: ObjectVar[Dict[KEY_TYPE, set[ARRAY_INNER_TYPE]]], + key: Var | Any, + ) -> ArrayVar[set[ARRAY_INNER_TYPE]]: ... + + @overload + def __getitem__( + self: ObjectVar[Dict[KEY_TYPE, tuple[ARRAY_INNER_TYPE, ...]]], + key: Var | Any, + ) -> ArrayVar[tuple[ARRAY_INNER_TYPE, ...]]: ... + + @overload + def __getitem__( + self: ObjectVar[Dict[KEY_TYPE, dict[OTHER_KEY_TYPE, VALUE_TYPE]]], + key: Var | Any, + ) -> ObjectVar[dict[OTHER_KEY_TYPE, VALUE_TYPE]]: ... + def __getitem__(self, key: Var | Any) -> ImmutableVar: """Get an item from the object. @@ -78,7 +199,54 @@ def __getitem__(self, key: Var | Any) -> ImmutableVar: """ return ObjectItemOperation(self, key).guess_type() - def __getattr__(self, name) -> ObjectItemOperation: + # NoReturn is used here to catch when key value is Any + @overload + def __getattr__( + self: ObjectVar[Dict[KEY_TYPE, NoReturn]], + name: str, + ) -> ImmutableVar: ... + + @overload + def __getattr__( + self: ( + ObjectVar[Dict[KEY_TYPE, int]] + | ObjectVar[Dict[KEY_TYPE, float]] + | ObjectVar[Dict[KEY_TYPE, int | float]] + ), + name: str, + ) -> NumberVar: ... + + @overload + def __getattr__( + self: ObjectVar[Dict[KEY_TYPE, str]], + name: str, + ) -> StringVar: ... + + @overload + def __getattr__( + self: ObjectVar[Dict[KEY_TYPE, list[ARRAY_INNER_TYPE]]], + name: str, + ) -> ArrayVar[list[ARRAY_INNER_TYPE]]: ... + + @overload + def __getattr__( + self: ObjectVar[Dict[KEY_TYPE, set[ARRAY_INNER_TYPE]]], + name: str, + ) -> ArrayVar[set[ARRAY_INNER_TYPE]]: ... + + @overload + def __getattr__( + self: ObjectVar[Dict[KEY_TYPE, tuple[ARRAY_INNER_TYPE, ...]]], + name: str, + ) -> ArrayVar[tuple[ARRAY_INNER_TYPE, ...]]: ... + + @overload + def __getattr__( + self: ObjectVar[Dict[KEY_TYPE, dict[OTHER_KEY_TYPE, VALUE_TYPE]]], + name: str, + ) -> ObjectVar[dict[OTHER_KEY_TYPE, VALUE_TYPE]]: ... + + def __getattr__(self, name) -> ImmutableVar: """Get an attribute of the var. Args: @@ -87,7 +255,19 @@ def __getattr__(self, name) -> ObjectItemOperation: Returns: The attribute of the var. """ - return ObjectItemOperation(self, name) + fixed_type = ( + self._var_type if isclass(self._var_type) else get_origin(self._var_type) + ) + if not issubclass(fixed_type, dict): + attribute_type = get_attribute_access_type(self._var_type, name) + if attribute_type is None: + raise VarAttributeError( + f"The State var `{self._var_name}` has no attribute '{name}' or may have been annotated " + f"wrongly." + ) + return ObjectItemOperation(self, name, attribute_type).guess_type() + else: + return ObjectItemOperation(self, name).guess_type() @dataclasses.dataclass( @@ -95,7 +275,7 @@ def __getattr__(self, name) -> ObjectItemOperation: frozen=True, **{"slots": True} if sys.version_info >= (3, 10) else {}, ) -class LiteralObjectVar(LiteralVar, ObjectVar): +class LiteralObjectVar(LiteralVar, ObjectVar[OBJECT_TYPE]): """Base class for immutable literal object vars.""" _var_value: Dict[Union[Var, Any], Union[Var, Any]] = dataclasses.field( @@ -103,9 +283,9 @@ class LiteralObjectVar(LiteralVar, ObjectVar): ) def __init__( - self, - _var_value: dict[Var | Any, Var | Any], - _var_type: Type | None = None, + self: LiteralObjectVar[OBJECT_TYPE], + _var_value: OBJECT_TYPE, + _var_type: Type[OBJECT_TYPE] | None = None, _var_data: VarData | None = None, ): """Initialize the object var. @@ -117,14 +297,7 @@ def __init__( """ super(LiteralObjectVar, self).__init__( _var_name="", - _var_type=( - Dict[ - unionize(*map(type, _var_value.keys())), - unionize(*map(type, _var_value.values())), - ] - if _var_type is None - else _var_type - ), + _var_type=(figure_out_type(_var_value) if _var_type is None else _var_type), _var_data=ImmutableVarData.merge(_var_data), ) object.__setattr__( @@ -489,6 +662,7 @@ def __init__( self, value: ObjectVar, key: Var | Any, + _var_type: GenericType | None = None, _var_data: VarData | None = None, ): """Initialize the object item operation. @@ -500,7 +674,7 @@ def __init__( """ super(ObjectItemOperation, self).__init__( _var_name="", - _var_type=value._value_type(), + _var_type=value._value_type() if _var_type is None else _var_type, _var_data=ImmutableVarData.merge(_var_data), ) object.__setattr__(self, "value", value) diff --git a/reflex/experimental/vars/sequence.py b/reflex/experimental/vars/sequence.py index 8db1300ec15..b2dcfd47684 100644 --- a/reflex/experimental/vars/sequence.py +++ b/reflex/experimental/vars/sequence.py @@ -10,7 +10,18 @@ import sys import typing from functools import cached_property -from typing import Any, List, Set, Tuple, Type, Union, overload +from typing import ( + TYPE_CHECKING, + Any, + Dict, + List, + Literal, + Set, + Tuple, + TypeVar, + Union, + overload, +) from typing_extensions import get_origin @@ -19,6 +30,8 @@ from reflex.experimental.vars.base import ( ImmutableVar, LiteralVar, + figure_out_type, + unionize, ) from reflex.experimental.vars.number import ( BooleanVar, @@ -29,8 +42,11 @@ from reflex.utils.types import GenericType from reflex.vars import ImmutableVarData, Var, VarData, _global_vars +if TYPE_CHECKING: + from .object import ObjectVar + -class StringVar(ImmutableVar): +class StringVar(ImmutableVar[str]): """Base class for immutable string vars.""" def __add__(self, other: StringVar | str) -> ConcatVarOperation: @@ -699,7 +715,17 @@ def __post_init__(self): pass -class ArrayVar(ImmutableVar): +ARRAY_VAR_TYPE = TypeVar("ARRAY_VAR_TYPE", bound=Union[List, Tuple, Set]) + +OTHER_TUPLE = TypeVar("OTHER_TUPLE") + +INNER_ARRAY_VAR = TypeVar("INNER_ARRAY_VAR") + +KEY_TYPE = TypeVar("KEY_TYPE") +VALUE_TYPE = TypeVar("VALUE_TYPE") + + +class ArrayVar(ImmutableVar[ARRAY_VAR_TYPE]): """Base class for immutable array vars.""" from reflex.experimental.vars.sequence import StringVar @@ -717,7 +743,7 @@ def join(self, sep: StringVar | str = "") -> ArrayJoinOperation: return ArrayJoinOperation(self, sep) - def reverse(self) -> ArrayReverseOperation: + def reverse(self) -> ArrayVar[ARRAY_VAR_TYPE]: """Reverse the array. Returns: @@ -726,14 +752,98 @@ def reverse(self) -> ArrayReverseOperation: return ArrayReverseOperation(self) @overload - def __getitem__(self, i: slice) -> ArraySliceOperation: ... + def __getitem__(self, i: slice) -> ArrayVar[ARRAY_VAR_TYPE]: ... + + @overload + def __getitem__( + self: ( + ArrayVar[Tuple[int, OTHER_TUPLE]] + | ArrayVar[Tuple[float, OTHER_TUPLE]] + | ArrayVar[Tuple[int | float, OTHER_TUPLE]] + ), + i: Literal[0, -2], + ) -> NumberVar: ... + + @overload + def __getitem__( + self: ( + ArrayVar[Tuple[OTHER_TUPLE, int]] + | ArrayVar[Tuple[OTHER_TUPLE, float]] + | ArrayVar[Tuple[OTHER_TUPLE, int | float]] + ), + i: Literal[1, -1], + ) -> NumberVar: ... + + @overload + def __getitem__( + self: ArrayVar[Tuple[str, OTHER_TUPLE]], i: Literal[0, -2] + ) -> StringVar: ... + + @overload + def __getitem__( + self: ArrayVar[Tuple[OTHER_TUPLE, str]], i: Literal[1, -1] + ) -> StringVar: ... + + @overload + def __getitem__( + self: ArrayVar[Tuple[bool, OTHER_TUPLE]], i: Literal[0, -2] + ) -> BooleanVar: ... + + @overload + def __getitem__( + self: ArrayVar[Tuple[OTHER_TUPLE, bool]], i: Literal[1, -1] + ) -> BooleanVar: ... + + @overload + def __getitem__( + self: ( + ARRAY_VAR_OF_LIST_ELEMENT[int] + | ARRAY_VAR_OF_LIST_ELEMENT[float] + | ARRAY_VAR_OF_LIST_ELEMENT[int | float] + ), + i: int | NumberVar, + ) -> NumberVar: ... + + @overload + def __getitem__( + self: ARRAY_VAR_OF_LIST_ELEMENT[str], i: int | NumberVar + ) -> StringVar: ... + + @overload + def __getitem__( + self: ARRAY_VAR_OF_LIST_ELEMENT[bool], i: int | NumberVar + ) -> BooleanVar: ... + + @overload + def __getitem__( + self: ARRAY_VAR_OF_LIST_ELEMENT[List[INNER_ARRAY_VAR]], + i: int | NumberVar, + ) -> ArrayVar[List[INNER_ARRAY_VAR]]: ... + + @overload + def __getitem__( + self: ARRAY_VAR_OF_LIST_ELEMENT[Set[INNER_ARRAY_VAR]], + i: int | NumberVar, + ) -> ArrayVar[Set[INNER_ARRAY_VAR]]: ... + + @overload + def __getitem__( + self: ARRAY_VAR_OF_LIST_ELEMENT[Tuple[INNER_ARRAY_VAR, ...]], + i: int | NumberVar, + ) -> ArrayVar[Tuple[INNER_ARRAY_VAR, ...]]: ... + + @overload + def __getitem__( + self: ARRAY_VAR_OF_LIST_ELEMENT[Dict[KEY_TYPE, VALUE_TYPE]], + i: int | NumberVar, + ) -> ObjectVar[Dict[KEY_TYPE, VALUE_TYPE]]: ... @overload def __getitem__(self, i: int | NumberVar) -> ImmutableVar: ... def __getitem__( self, i: slice | int | NumberVar - ) -> ArraySliceOperation | ImmutableVar: + ) -> ArrayVar[ARRAY_VAR_TYPE] | ImmutableVar: """Get a slice of the array. Args: @@ -756,7 +866,7 @@ def length(self) -> NumberVar: @overload @classmethod - def range(cls, stop: int | NumberVar, /) -> RangeOperation: ... + def range(cls, stop: int | NumberVar, /) -> ArrayVar[List[int]]: ... @overload @classmethod @@ -766,7 +876,7 @@ def range( end: int | NumberVar, step: int | NumberVar = 1, /, - ) -> RangeOperation: ... + ) -> ArrayVar[List[int]]: ... @classmethod def range( @@ -774,7 +884,7 @@ def range( first_endpoint: int | NumberVar, second_endpoint: int | NumberVar | None = None, step: int | NumberVar | None = None, - ) -> RangeOperation: + ) -> ArrayVar[List[int]]: """Create a range of numbers. Args: @@ -794,7 +904,7 @@ def range( return RangeOperation(start, end, step or 1) - def contains(self, other: Any) -> ArrayContainsOperation: + def contains(self, other: Any) -> BooleanVar: """Check if the array contains an element. Args: @@ -806,12 +916,21 @@ def contains(self, other: Any) -> ArrayContainsOperation: return ArrayContainsOperation(self, other) +LIST_ELEMENT = TypeVar("LIST_ELEMENT") + +ARRAY_VAR_OF_LIST_ELEMENT = ( + ArrayVar[List[LIST_ELEMENT]] + | ArrayVar[Set[LIST_ELEMENT]] + | ArrayVar[Tuple[LIST_ELEMENT, ...]] +) + + @dataclasses.dataclass( eq=False, frozen=True, **{"slots": True} if sys.version_info >= (3, 10) else {}, ) -class LiteralArrayVar(LiteralVar, ArrayVar): +class LiteralArrayVar(LiteralVar, ArrayVar[ARRAY_VAR_TYPE]): """Base class for immutable literal array vars.""" _var_value: Union[ @@ -819,9 +938,9 @@ class LiteralArrayVar(LiteralVar, ArrayVar): ] = dataclasses.field(default_factory=list) def __init__( - self, - _var_value: list[Var | Any] | tuple[Var | Any, ...] | set[Var | Any], - _var_type: type[list] | type[tuple] | type[set] | None = None, + self: LiteralArrayVar[ARRAY_VAR_TYPE], + _var_value: ARRAY_VAR_TYPE, + _var_type: type[ARRAY_VAR_TYPE] | None = None, _var_data: VarData | None = None, ): """Initialize the array var. @@ -834,11 +953,7 @@ def __init__( super(LiteralArrayVar, self).__init__( _var_name="", _var_data=ImmutableVarData.merge(_var_data), - _var_type=( - List[unionize(*map(type, _var_value))] - if _var_type is None - else _var_type - ), + _var_type=(figure_out_type(_var_value) if _var_type is None else _var_type), ) object.__setattr__(self, "_var_value", _var_value) object.__delattr__(self, "_var_name") @@ -1261,23 +1376,6 @@ def _cached_var_name(self) -> str: return f"{str(self.a)}.length" -def unionize(*args: Type) -> Type: - """Unionize the types. - - Args: - args: The types to unionize. - - Returns: - The unionized types. - """ - if not args: - return Any - first, *rest = args - if not rest: - return first - return Union[first, unionize(*rest)] - - def is_tuple_type(t: GenericType) -> bool: """Check if a type is a tuple type. diff --git a/tests/test_var.py b/tests/test_var.py index 66599db72f6..28ca5afca77 100644 --- a/tests/test_var.py +++ b/tests/test_var.py @@ -1042,7 +1042,7 @@ def test_object_operations(): def test_type_chains(): object_var = LiteralObjectVar({"a": 1, "b": 2, "c": 3}) - assert object_var._var_type is Dict[str, int] + assert (object_var._key_type(), object_var._value_type()) == (str, int) assert (object_var.keys()._var_type, object_var.values()._var_type) == ( List[str], List[int], @@ -1061,6 +1061,31 @@ def test_type_chains(): ) +def test_nested_dict(): + arr = LiteralArrayVar([{"bar": ["foo", "bar"]}], List[Dict[str, list[str]]]) + + assert ( + str(arr[0]["bar"][0]) == '[({ ["bar"] : ["foo", "bar"] })].at(0)["bar"].at(0)' + ) + + +def nested_base(): + class Boo(Base): + foo: str + bar: int + + class Foo(Base): + bar: Boo + baz: int + + parent_obj = LiteralVar.create(Foo(bar=Boo(foo="bar", bar=5), baz=5)) + + assert ( + str(parent_obj.bar.foo) + == '({ ["bar"] : ({ ["foo"] : "bar", ["bar"] : 5 }), ["baz"] : 5 })["bar"]["foo"]' + ) + + def test_retrival(): var_without_data = ImmutableVar.create("test") assert var_without_data is not None