Type Validation for Var Operations and Enhanced Compatibility (#1674)
This commit is contained in:
parent
1c598b8428
commit
f2b0915aff
701
integration/test_var_operations.py
Normal file
701
integration/test_var_operations.py
Normal file
@ -0,0 +1,701 @@
|
|||||||
|
"""Integration tests for var operations."""
|
||||||
|
from typing import Generator
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
|
||||||
|
from reflex.testing import AppHarness
|
||||||
|
|
||||||
|
# pyright: reportOptionalMemberAccess=false, reportGeneralTypeIssues=false, reportUnknownMemberType=false
|
||||||
|
|
||||||
|
|
||||||
|
def VarOperations():
|
||||||
|
"""App with var operations."""
|
||||||
|
import reflex as rx
|
||||||
|
|
||||||
|
class VarOperationState(rx.State):
|
||||||
|
int_var1: int = 10
|
||||||
|
int_var2: int = 5
|
||||||
|
int_var3: int = 7
|
||||||
|
float_var1: float = 10.5
|
||||||
|
float_var2: float = 5.5
|
||||||
|
list1: list = [1, 2]
|
||||||
|
list2: list = [3, 4]
|
||||||
|
str_var1: str = "first"
|
||||||
|
str_var2: str = "second"
|
||||||
|
dict1: dict = {1: 2}
|
||||||
|
dict2: dict = {3: 4}
|
||||||
|
|
||||||
|
app = rx.App(state=VarOperationState)
|
||||||
|
|
||||||
|
@app.add_page
|
||||||
|
def index():
|
||||||
|
return rx.vstack(
|
||||||
|
# INT INT
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.int_var1 + VarOperationState.int_var2,
|
||||||
|
id="int_add_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.int_var1 * VarOperationState.int_var2,
|
||||||
|
id="int_mult_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.int_var1 - VarOperationState.int_var2,
|
||||||
|
id="int_sub_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.int_var1**VarOperationState.int_var2,
|
||||||
|
id="int_exp_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.int_var1 / VarOperationState.int_var2,
|
||||||
|
id="int_div_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.int_var1 // VarOperationState.int_var3,
|
||||||
|
id="int_floor_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.int_var1 % VarOperationState.int_var2,
|
||||||
|
id="int_mod_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.int_var1 | VarOperationState.int_var2,
|
||||||
|
id="int_or_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.int_var1 > VarOperationState.int_var2).to_string(),
|
||||||
|
id="int_gt_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.int_var1 < VarOperationState.int_var2).to_string(),
|
||||||
|
id="int_lt_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.int_var1 >= VarOperationState.int_var2).to_string(),
|
||||||
|
id="int_gte_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.int_var1 <= VarOperationState.int_var2).to_string(),
|
||||||
|
id="int_lte_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.int_var1 & VarOperationState.int_var2,
|
||||||
|
id="int_and_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.int_var1 | VarOperationState.int_var2).to_string(),
|
||||||
|
id="int_or_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.int_var1 == VarOperationState.int_var2).to_string(),
|
||||||
|
id="int_eq_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.int_var1 != VarOperationState.int_var2).to_string(),
|
||||||
|
id="int_neq_int",
|
||||||
|
),
|
||||||
|
# INT FLOAT OR FLOAT INT
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.float_var1 + VarOperationState.int_var2,
|
||||||
|
id="float_add_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.float_var1 * VarOperationState.int_var2,
|
||||||
|
id="float_mult_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.float_var1 - VarOperationState.int_var2,
|
||||||
|
id="float_sub_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.float_var1**VarOperationState.int_var2,
|
||||||
|
id="float_exp_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.float_var1 / VarOperationState.int_var2,
|
||||||
|
id="float_div_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.float_var1 // VarOperationState.int_var3,
|
||||||
|
id="float_floor_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.float_var1 % VarOperationState.int_var2,
|
||||||
|
id="float_mod_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.float_var1 > VarOperationState.int_var2).to_string(),
|
||||||
|
id="float_gt_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.float_var1 < VarOperationState.int_var2).to_string(),
|
||||||
|
id="float_lt_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(
|
||||||
|
VarOperationState.float_var1 >= VarOperationState.int_var2
|
||||||
|
).to_string(),
|
||||||
|
id="float_gte_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(
|
||||||
|
VarOperationState.float_var1 <= VarOperationState.int_var2
|
||||||
|
).to_string(),
|
||||||
|
id="float_lte_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(
|
||||||
|
VarOperationState.float_var1 == VarOperationState.int_var2
|
||||||
|
).to_string(),
|
||||||
|
id="float_eq_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(
|
||||||
|
VarOperationState.float_var1 != VarOperationState.int_var2
|
||||||
|
).to_string(),
|
||||||
|
id="float_neq_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.float_var1 & VarOperationState.int_var2).to_string(),
|
||||||
|
id="float_and_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.float_var1 | VarOperationState.int_var2).to_string(),
|
||||||
|
id="float_or_int",
|
||||||
|
),
|
||||||
|
# INT, DICT
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.int_var1 | VarOperationState.dict1).to_string(),
|
||||||
|
id="int_or_dict",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.int_var1 & VarOperationState.dict1).to_string(),
|
||||||
|
id="int_and_dict",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.int_var1 == VarOperationState.dict1).to_string(),
|
||||||
|
id="int_eq_dict",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.int_var1 != VarOperationState.dict1).to_string(),
|
||||||
|
id="int_neq_dict",
|
||||||
|
),
|
||||||
|
# FLOAT FLOAT
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.float_var1 + VarOperationState.float_var2,
|
||||||
|
id="float_add_float",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.float_var1 * VarOperationState.float_var2,
|
||||||
|
id="float_mult_float",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.float_var1 - VarOperationState.float_var2,
|
||||||
|
id="float_sub_float",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.float_var1**VarOperationState.float_var2,
|
||||||
|
id="float_exp_float",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.float_var1 / VarOperationState.float_var2,
|
||||||
|
id="float_div_float",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.float_var1 // VarOperationState.float_var2,
|
||||||
|
id="float_floor_float",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.float_var1 % VarOperationState.float_var2,
|
||||||
|
id="float_mod_float",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(
|
||||||
|
VarOperationState.float_var1 > VarOperationState.float_var2
|
||||||
|
).to_string(),
|
||||||
|
id="float_gt_float",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(
|
||||||
|
VarOperationState.float_var1 < VarOperationState.float_var2
|
||||||
|
).to_string(),
|
||||||
|
id="float_lt_float",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(
|
||||||
|
VarOperationState.float_var1 >= VarOperationState.float_var2
|
||||||
|
).to_string(),
|
||||||
|
id="float_gte_float",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(
|
||||||
|
VarOperationState.float_var1 <= VarOperationState.float_var2
|
||||||
|
).to_string(),
|
||||||
|
id="float_lte_float",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(
|
||||||
|
VarOperationState.float_var1 == VarOperationState.float_var2
|
||||||
|
).to_string(),
|
||||||
|
id="float_eq_float",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(
|
||||||
|
VarOperationState.float_var1 != VarOperationState.float_var2
|
||||||
|
).to_string(),
|
||||||
|
id="float_neq_float",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(
|
||||||
|
VarOperationState.float_var1 & VarOperationState.float_var2
|
||||||
|
).to_string(),
|
||||||
|
id="float_and_float",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(
|
||||||
|
VarOperationState.float_var1 | VarOperationState.float_var2
|
||||||
|
).to_string(),
|
||||||
|
id="float_or_float",
|
||||||
|
),
|
||||||
|
# FLOAT STR
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.float_var1 | VarOperationState.str_var1,
|
||||||
|
id="float_or_str",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.float_var1 & VarOperationState.str_var1,
|
||||||
|
id="float_and_str",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(
|
||||||
|
VarOperationState.float_var1 == VarOperationState.str_var1
|
||||||
|
).to_string(),
|
||||||
|
id="float_eq_str",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(
|
||||||
|
VarOperationState.float_var1 != VarOperationState.str_var1
|
||||||
|
).to_string(),
|
||||||
|
id="float_neq_str",
|
||||||
|
),
|
||||||
|
# FLOAT LIST
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.float_var1 | VarOperationState.list1).to_string(),
|
||||||
|
id="float_or_list",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.float_var1 & VarOperationState.list1).to_string(),
|
||||||
|
id="float_and_list",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.float_var1 == VarOperationState.list1).to_string(),
|
||||||
|
id="float_eq_list",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.float_var1 != VarOperationState.list1).to_string(),
|
||||||
|
id="float_neq_list",
|
||||||
|
),
|
||||||
|
# FLOAT DICT
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.float_var1 | VarOperationState.dict1).to_string(),
|
||||||
|
id="float_or_dict",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.float_var1 & VarOperationState.dict1).to_string(),
|
||||||
|
id="float_and_dict",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.float_var1 == VarOperationState.dict1).to_string(),
|
||||||
|
id="float_eq_dict",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.float_var1 != VarOperationState.dict1).to_string(),
|
||||||
|
id="float_neq_dict",
|
||||||
|
),
|
||||||
|
# STR STR
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.str_var1 + VarOperationState.str_var2,
|
||||||
|
id="str_add_str",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.str_var1 > VarOperationState.str_var2).to_string(),
|
||||||
|
id="str_gt_str",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.str_var1 < VarOperationState.str_var2).to_string(),
|
||||||
|
id="str_lt_str",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.str_var1 >= VarOperationState.str_var2).to_string(),
|
||||||
|
id="str_gte_str",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.str_var1 <= VarOperationState.str_var2).to_string(),
|
||||||
|
id="str_lte_str",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(
|
||||||
|
VarOperationState.float_var1 == VarOperationState.float_var2
|
||||||
|
).to_string(),
|
||||||
|
id="str_eq_str",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(
|
||||||
|
VarOperationState.float_var1 != VarOperationState.float_var2
|
||||||
|
).to_string(),
|
||||||
|
id="str_neq_str",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.str_var1.contains("fir").to_string(),
|
||||||
|
id="str_contains",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.str_var1 | VarOperationState.str_var1, id="str_or_str"
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.str_var1 & VarOperationState.str_var2,
|
||||||
|
id="str_and_str",
|
||||||
|
),
|
||||||
|
# STR, INT
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.str_var1 * VarOperationState.int_var2,
|
||||||
|
id="str_mult_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.str_var1 & VarOperationState.int_var2,
|
||||||
|
id="str_and_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.str_var1 | VarOperationState.int_var2, id="str_or_int"
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.str_var1 == VarOperationState.int_var1).to_string(),
|
||||||
|
id="str_eq_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.str_var1 != VarOperationState.int_var1).to_string(),
|
||||||
|
id="str_neq_int",
|
||||||
|
),
|
||||||
|
# STR, LIST
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.str_var1 | VarOperationState.list1, id="str_or_list"
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.str_var1 & VarOperationState.list1).to_string(),
|
||||||
|
id="str_and_list",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.str_var1 == VarOperationState.list1).to_string(),
|
||||||
|
id="str_eq_list",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.str_var1 != VarOperationState.list1).to_string(),
|
||||||
|
id="str_neq_list",
|
||||||
|
),
|
||||||
|
# STR, DICT
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.str_var1 | VarOperationState.dict1, id="str_or_dict"
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.str_var1 & VarOperationState.dict1).to_string(),
|
||||||
|
id="str_and_dict",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.str_var1 == VarOperationState.dict1).to_string(),
|
||||||
|
id="str_eq_dict",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.str_var1 != VarOperationState.dict1).to_string(),
|
||||||
|
id="str_neq_dict",
|
||||||
|
),
|
||||||
|
# LIST, LIST
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.list1 + VarOperationState.list2).to_string(),
|
||||||
|
id="list_add_list",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.list1 & VarOperationState.list2).to_string(),
|
||||||
|
id="list_and_list",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.list1 | VarOperationState.list2).to_string(),
|
||||||
|
id="list_or_list",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.list1 > VarOperationState.list2).to_string(),
|
||||||
|
id="list_gt_list",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.list1 < VarOperationState.list2).to_string(),
|
||||||
|
id="list_lt_list",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.list1 >= VarOperationState.list2).to_string(),
|
||||||
|
id="list_gte_list",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.list1 <= VarOperationState.list2).to_string(),
|
||||||
|
id="list_lte_list",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.list1 == VarOperationState.list2).to_string(),
|
||||||
|
id="list_eq_list",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.list1 != VarOperationState.list2).to_string(),
|
||||||
|
id="list_neq_list",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.list1.contains(1).to_string(), id="list_contains"
|
||||||
|
),
|
||||||
|
rx.text(VarOperationState.list1.reverse().to_string(), id="list_reverse"),
|
||||||
|
# LIST, INT
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.list1 * VarOperationState.int_var2).to_string(),
|
||||||
|
id="list_mult_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.list1 | VarOperationState.int_var1).to_string(),
|
||||||
|
id="list_or_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.list1 & VarOperationState.int_var1).to_string(),
|
||||||
|
id="list_and_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.list1 == VarOperationState.int_var1).to_string(),
|
||||||
|
id="list_eq_int",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.list1 != VarOperationState.int_var1).to_string(),
|
||||||
|
id="list_neq_int",
|
||||||
|
),
|
||||||
|
# LIST, DICT
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.list1 | VarOperationState.dict1).to_string(),
|
||||||
|
id="list_or_dict",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.list1 & VarOperationState.dict1).to_string(),
|
||||||
|
id="list_and_dict",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.list1 == VarOperationState.dict1).to_string(),
|
||||||
|
id="list_eq_dict",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.list1 != VarOperationState.dict1).to_string(),
|
||||||
|
id="list_neq_dict",
|
||||||
|
),
|
||||||
|
# DICT, DICT
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.dict1 | VarOperationState.dict2).to_string(),
|
||||||
|
id="dict_or_dict",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.dict1 & VarOperationState.dict2).to_string(),
|
||||||
|
id="dict_and_dict",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.dict1 == VarOperationState.dict2).to_string(),
|
||||||
|
id="dict_eq_dict",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
(VarOperationState.dict1 != VarOperationState.dict2).to_string(),
|
||||||
|
id="dict_neq_dict",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
VarOperationState.dict1.contains(1).to_string(), id="dict_contains"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
app.compile()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def var_operations(tmp_path_factory) -> Generator[AppHarness, None, None]:
|
||||||
|
"""Start VarOperations app at tmp_path via AppHarness.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tmp_path_factory: pytest tmp_path_factory fixture
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
running AppHarness instance
|
||||||
|
"""
|
||||||
|
with AppHarness.create(
|
||||||
|
root=tmp_path_factory.mktemp("var_operations"),
|
||||||
|
app_source=VarOperations, # type: ignore
|
||||||
|
) as harness:
|
||||||
|
assert harness.app_instance is not None, "app is not running"
|
||||||
|
yield harness
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def driver(var_operations: AppHarness):
|
||||||
|
"""Get an instance of the browser open to the var operations app.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
var_operations: harness for VarOperations app
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
WebDriver instance.
|
||||||
|
"""
|
||||||
|
driver = var_operations.frontend()
|
||||||
|
try:
|
||||||
|
assert var_operations.poll_for_clients()
|
||||||
|
yield driver
|
||||||
|
finally:
|
||||||
|
driver.quit()
|
||||||
|
|
||||||
|
|
||||||
|
def test_var_operations(driver, var_operations: AppHarness):
|
||||||
|
"""Test that the var operations produce the right results.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
driver: selenium WebDriver open to the app
|
||||||
|
var_operations: AppHarness for the var operations app
|
||||||
|
"""
|
||||||
|
assert var_operations.app_instance is not None, "app is not running"
|
||||||
|
# INT INT
|
||||||
|
assert driver.find_element(By.ID, "int_add_int").text == "15"
|
||||||
|
assert driver.find_element(By.ID, "int_mult_int").text == "50"
|
||||||
|
assert driver.find_element(By.ID, "int_sub_int").text == "5"
|
||||||
|
assert driver.find_element(By.ID, "int_exp_int").text == "100000"
|
||||||
|
assert driver.find_element(By.ID, "int_div_int").text == "2"
|
||||||
|
assert driver.find_element(By.ID, "int_floor_int").text == "1"
|
||||||
|
assert driver.find_element(By.ID, "int_mod_int").text == "0"
|
||||||
|
assert driver.find_element(By.ID, "int_gt_int").text == "true"
|
||||||
|
assert driver.find_element(By.ID, "int_lt_int").text == "false"
|
||||||
|
assert driver.find_element(By.ID, "int_gte_int").text == "true"
|
||||||
|
assert driver.find_element(By.ID, "int_lte_int").text == "false"
|
||||||
|
assert driver.find_element(By.ID, "int_and_int").text == "5"
|
||||||
|
assert driver.find_element(By.ID, "int_or_int").text == "10"
|
||||||
|
assert driver.find_element(By.ID, "int_eq_int").text == "false"
|
||||||
|
assert driver.find_element(By.ID, "int_neq_int").text == "true"
|
||||||
|
|
||||||
|
# INT FLOAT OR FLOAT INT
|
||||||
|
assert driver.find_element(By.ID, "float_add_int").text == "15.5"
|
||||||
|
assert driver.find_element(By.ID, "float_mult_int").text == "52.5"
|
||||||
|
assert driver.find_element(By.ID, "float_sub_int").text == "5.5"
|
||||||
|
assert driver.find_element(By.ID, "float_exp_int").text == "127628.15625"
|
||||||
|
assert driver.find_element(By.ID, "float_div_int").text == "2.1"
|
||||||
|
assert driver.find_element(By.ID, "float_floor_int").text == "1"
|
||||||
|
assert driver.find_element(By.ID, "float_mod_int").text == "0.5"
|
||||||
|
assert driver.find_element(By.ID, "float_gt_int").text == "true"
|
||||||
|
assert driver.find_element(By.ID, "float_lt_int").text == "false"
|
||||||
|
assert driver.find_element(By.ID, "float_gte_int").text == "true"
|
||||||
|
assert driver.find_element(By.ID, "float_lte_int").text == "false"
|
||||||
|
assert driver.find_element(By.ID, "float_eq_int").text == "false"
|
||||||
|
assert driver.find_element(By.ID, "float_neq_int").text == "true"
|
||||||
|
assert driver.find_element(By.ID, "float_and_int").text == "5"
|
||||||
|
assert driver.find_element(By.ID, "float_or_int").text == "10.5"
|
||||||
|
|
||||||
|
# INT, DICT
|
||||||
|
assert driver.find_element(By.ID, "int_or_dict").text == "10"
|
||||||
|
assert driver.find_element(By.ID, "int_and_dict").text == '{"1":2}'
|
||||||
|
assert driver.find_element(By.ID, "int_eq_dict").text == "false"
|
||||||
|
assert driver.find_element(By.ID, "int_neq_dict").text == "true"
|
||||||
|
|
||||||
|
# FLOAT FLOAT
|
||||||
|
assert driver.find_element(By.ID, "float_add_float").text == "16"
|
||||||
|
assert driver.find_element(By.ID, "float_mult_float").text == "57.75"
|
||||||
|
assert driver.find_element(By.ID, "float_sub_float").text == "5"
|
||||||
|
assert driver.find_element(By.ID, "float_exp_float").text == "413562.49323606625"
|
||||||
|
assert driver.find_element(By.ID, "float_div_float").text == "1.9090909090909092"
|
||||||
|
assert driver.find_element(By.ID, "float_floor_float").text == "1"
|
||||||
|
assert driver.find_element(By.ID, "float_mod_float").text == "5"
|
||||||
|
assert driver.find_element(By.ID, "float_gt_float").text == "true"
|
||||||
|
assert driver.find_element(By.ID, "float_lt_float").text == "false"
|
||||||
|
assert driver.find_element(By.ID, "float_gte_float").text == "true"
|
||||||
|
assert driver.find_element(By.ID, "float_lte_float").text == "false"
|
||||||
|
assert driver.find_element(By.ID, "float_eq_float").text == "false"
|
||||||
|
assert driver.find_element(By.ID, "float_neq_float").text == "true"
|
||||||
|
assert driver.find_element(By.ID, "float_and_float").text == "5.5"
|
||||||
|
assert driver.find_element(By.ID, "float_or_float").text == "10.5"
|
||||||
|
|
||||||
|
# FLOAT STR
|
||||||
|
assert driver.find_element(By.ID, "float_or_str").text == "10.5"
|
||||||
|
assert driver.find_element(By.ID, "float_and_str").text == "first"
|
||||||
|
assert driver.find_element(By.ID, "float_eq_str").text == "false"
|
||||||
|
assert driver.find_element(By.ID, "float_neq_str").text == "true"
|
||||||
|
|
||||||
|
# FLOAT,LIST
|
||||||
|
assert driver.find_element(By.ID, "float_or_list").text == "10.5"
|
||||||
|
assert driver.find_element(By.ID, "float_and_list").text == "[1,2]"
|
||||||
|
assert driver.find_element(By.ID, "float_eq_list").text == "false"
|
||||||
|
assert driver.find_element(By.ID, "float_neq_list").text == "true"
|
||||||
|
|
||||||
|
# FLOAT, DICT
|
||||||
|
assert driver.find_element(By.ID, "float_or_dict").text == "10.5"
|
||||||
|
assert driver.find_element(By.ID, "float_and_dict").text == '{"1":2}'
|
||||||
|
assert driver.find_element(By.ID, "float_eq_dict").text == "false"
|
||||||
|
assert driver.find_element(By.ID, "float_neq_dict").text == "true"
|
||||||
|
|
||||||
|
# STR STR
|
||||||
|
assert driver.find_element(By.ID, "str_add_str").text == "firstsecond"
|
||||||
|
assert driver.find_element(By.ID, "str_gt_str").text == "false"
|
||||||
|
assert driver.find_element(By.ID, "str_lt_str").text == "true"
|
||||||
|
assert driver.find_element(By.ID, "str_gte_str").text == "false"
|
||||||
|
assert driver.find_element(By.ID, "str_lte_str").text == "true"
|
||||||
|
assert driver.find_element(By.ID, "str_eq_str").text == "false"
|
||||||
|
assert driver.find_element(By.ID, "str_neq_str").text == "true"
|
||||||
|
assert driver.find_element(By.ID, "str_and_str").text == "second"
|
||||||
|
assert driver.find_element(By.ID, "str_or_str").text == "first"
|
||||||
|
assert driver.find_element(By.ID, "str_contains").text == "true"
|
||||||
|
|
||||||
|
# STR INT
|
||||||
|
assert (
|
||||||
|
driver.find_element(By.ID, "str_mult_int").text == "firstfirstfirstfirstfirst"
|
||||||
|
)
|
||||||
|
assert driver.find_element(By.ID, "str_and_int").text == "5"
|
||||||
|
assert driver.find_element(By.ID, "str_or_int").text == "first"
|
||||||
|
assert driver.find_element(By.ID, "str_eq_int").text == "false"
|
||||||
|
assert driver.find_element(By.ID, "str_neq_int").text == "true"
|
||||||
|
|
||||||
|
# STR, LIST
|
||||||
|
assert driver.find_element(By.ID, "str_and_list").text == "[1,2]"
|
||||||
|
assert driver.find_element(By.ID, "str_or_list").text == "first"
|
||||||
|
assert driver.find_element(By.ID, "str_eq_list").text == "false"
|
||||||
|
assert driver.find_element(By.ID, "str_neq_list").text == "true"
|
||||||
|
|
||||||
|
# STR, DICT
|
||||||
|
|
||||||
|
assert driver.find_element(By.ID, "str_or_dict").text == "first"
|
||||||
|
assert driver.find_element(By.ID, "str_and_dict").text == '{"1":2}'
|
||||||
|
assert driver.find_element(By.ID, "str_eq_dict").text == "false"
|
||||||
|
assert driver.find_element(By.ID, "str_neq_dict").text == "true"
|
||||||
|
|
||||||
|
# LIST,LIST
|
||||||
|
assert driver.find_element(By.ID, "list_add_list").text == "[1,2,3,4]"
|
||||||
|
assert driver.find_element(By.ID, "list_gt_list").text == "false"
|
||||||
|
assert driver.find_element(By.ID, "list_lt_list").text == "true"
|
||||||
|
assert driver.find_element(By.ID, "list_gte_list").text == "false"
|
||||||
|
assert driver.find_element(By.ID, "list_lte_list").text == "true"
|
||||||
|
assert driver.find_element(By.ID, "list_eq_list").text == "false"
|
||||||
|
assert driver.find_element(By.ID, "list_neq_list").text == "true"
|
||||||
|
assert driver.find_element(By.ID, "list_and_list").text == "[3,4]"
|
||||||
|
assert driver.find_element(By.ID, "list_or_list").text == "[1,2]"
|
||||||
|
assert driver.find_element(By.ID, "list_contains").text == "true"
|
||||||
|
assert driver.find_element(By.ID, "list_reverse").text == "[2,1]"
|
||||||
|
|
||||||
|
# LIST INT
|
||||||
|
assert driver.find_element(By.ID, "list_mult_int").text == "[1,2,1,2,1,2,1,2,1,2]"
|
||||||
|
assert driver.find_element(By.ID, "list_or_int").text == "[1,2]"
|
||||||
|
assert driver.find_element(By.ID, "list_and_int").text == "10"
|
||||||
|
assert driver.find_element(By.ID, "list_eq_int").text == "false"
|
||||||
|
assert driver.find_element(By.ID, "list_neq_int").text == "true"
|
||||||
|
|
||||||
|
# LIST DICT
|
||||||
|
assert driver.find_element(By.ID, "list_and_dict").text == '{"1":2}'
|
||||||
|
assert driver.find_element(By.ID, "list_or_dict").text == "[1,2]"
|
||||||
|
assert driver.find_element(By.ID, "list_eq_dict").text == "false"
|
||||||
|
assert driver.find_element(By.ID, "list_neq_dict").text == "true"
|
||||||
|
|
||||||
|
# DICT, DICT
|
||||||
|
assert driver.find_element(By.ID, "dict_or_dict").text == '{"1":2}'
|
||||||
|
assert driver.find_element(By.ID, "dict_and_dict").text == '{"3":4}'
|
||||||
|
assert driver.find_element(By.ID, "dict_eq_dict").text == "false"
|
||||||
|
assert driver.find_element(By.ID, "dict_neq_dict").text == "true"
|
||||||
|
assert driver.find_element(By.ID, "dict_contains").text == "true"
|
@ -530,3 +530,19 @@ export const getRefValues = (refs) => {
|
|||||||
// getAttribute is used by RangeSlider because it doesn't assign value
|
// getAttribute is used by RangeSlider because it doesn't assign value
|
||||||
return refs.map((ref) => ref.current.value || ref.current.getAttribute("aria-valuenow"));
|
return refs.map((ref) => ref.current.value || ref.current.getAttribute("aria-valuenow"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spread two arrays or two objects.
|
||||||
|
* @param first The first array or object.
|
||||||
|
* @param second The second array or object.
|
||||||
|
* @returns The final merged array or object.
|
||||||
|
*/
|
||||||
|
export const spreadArraysOrObjects = (first, second) => {
|
||||||
|
if (Array.isArray(first) && Array.isArray(second)) {
|
||||||
|
return [...first, ...second];
|
||||||
|
} else if (typeof first === 'object' && typeof second === 'object') {
|
||||||
|
return { ...first, ...second };
|
||||||
|
} else {
|
||||||
|
throw new Error('Both parameters must be either arrays or objects.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -24,6 +24,7 @@ DEFAULT_IMPORTS: imports.ImportDict = {
|
|||||||
ImportVar(tag="uploadFiles"),
|
ImportVar(tag="uploadFiles"),
|
||||||
ImportVar(tag="E"),
|
ImportVar(tag="E"),
|
||||||
ImportVar(tag="isTrue"),
|
ImportVar(tag="isTrue"),
|
||||||
|
ImportVar(tag="spreadArraysOrObjects"),
|
||||||
ImportVar(tag="preventDefault"),
|
ImportVar(tag="preventDefault"),
|
||||||
ImportVar(tag="refs"),
|
ImportVar(tag="refs"),
|
||||||
ImportVar(tag="getRefValue"),
|
ImportVar(tag="getRefValue"),
|
||||||
|
251
reflex/vars.py
251
reflex/vars.py
@ -38,6 +38,35 @@ if TYPE_CHECKING:
|
|||||||
# Set of unique variable names.
|
# Set of unique variable names.
|
||||||
USED_VARIABLES = set()
|
USED_VARIABLES = set()
|
||||||
|
|
||||||
|
# Supported operators for all types.
|
||||||
|
ALL_OPS = ["==", "!=", "!==", "===", "&&", "||"]
|
||||||
|
# Delimiters used between function args or operands.
|
||||||
|
DELIMITERS = [","]
|
||||||
|
# Mapping of valid operations for different type combinations.
|
||||||
|
OPERATION_MAPPING = {
|
||||||
|
(int, int): {
|
||||||
|
"+",
|
||||||
|
"-",
|
||||||
|
"/",
|
||||||
|
"//",
|
||||||
|
"*",
|
||||||
|
"%",
|
||||||
|
"**",
|
||||||
|
">",
|
||||||
|
"<",
|
||||||
|
"<=",
|
||||||
|
">=",
|
||||||
|
"|",
|
||||||
|
"&",
|
||||||
|
},
|
||||||
|
(int, str): {"*"},
|
||||||
|
(int, list): {"*"},
|
||||||
|
(str, str): {"+", ">", "<", "<=", ">="},
|
||||||
|
(float, float): {"+", "-", "/", "//", "*", "%", "**", ">", "<", "<=", ">="},
|
||||||
|
(float, int): {"+", "-", "/", "//", "*", "%", "**", ">", "<", "<=", ">="},
|
||||||
|
(list, list): {"+", ">", "<", "<=", ">="},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_unique_variable_name() -> str:
|
def get_unique_variable_name() -> str:
|
||||||
"""Get a unique variable name.
|
"""Get a unique variable name.
|
||||||
@ -383,6 +412,7 @@ class Var(ABC):
|
|||||||
type_: Type | None = None,
|
type_: Type | None = None,
|
||||||
flip: bool = False,
|
flip: bool = False,
|
||||||
fn: str | None = None,
|
fn: str | None = None,
|
||||||
|
invoke_fn: bool = False,
|
||||||
) -> Var:
|
) -> Var:
|
||||||
"""Perform an operation on a var.
|
"""Perform an operation on a var.
|
||||||
|
|
||||||
@ -392,32 +422,100 @@ class Var(ABC):
|
|||||||
type_: The type of the operation result.
|
type_: The type of the operation result.
|
||||||
flip: Whether to flip the order of the operation.
|
flip: Whether to flip the order of the operation.
|
||||||
fn: A function to apply to the operation.
|
fn: A function to apply to the operation.
|
||||||
|
invoke_fn: Whether to invoke the function.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The operation result.
|
The operation result.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
TypeError: If the operation between two operands is invalid.
|
||||||
|
ValueError: If flip is set to true and value of operand is not provided
|
||||||
"""
|
"""
|
||||||
# Wrap strings in quotes.
|
|
||||||
if isinstance(other, str):
|
if isinstance(other, str):
|
||||||
other = Var.create(json.dumps(other))
|
other = Var.create(json.dumps(other))
|
||||||
else:
|
else:
|
||||||
other = Var.create(other)
|
other = Var.create(other)
|
||||||
if type_ is None:
|
|
||||||
type_ = self.type_
|
type_ = type_ or self.type_
|
||||||
if other is None:
|
|
||||||
name = f"{op}{self.full_name}"
|
if other is None and flip:
|
||||||
|
raise ValueError(
|
||||||
|
"flip_operands cannot be set to True if the value of 'other' operand is not provided"
|
||||||
|
)
|
||||||
|
|
||||||
|
left_operand, right_operand = (other, self) if flip else (self, other)
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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
|
||||||
|
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"{fn}({operation_name})"
|
||||||
|
else:
|
||||||
|
# apply operator to operands (left operand <operator> right_operand)
|
||||||
|
operation_name = f"{left_operand.full_name} {op} {right_operand.full_name}" # type: ignore
|
||||||
|
operation_name = format.wrap(operation_name, "(")
|
||||||
else:
|
else:
|
||||||
props = (other, self) if flip else (self, other)
|
# apply operator to left operand (<operator> left_operand)
|
||||||
name = f"{props[0].full_name} {op} {props[1].full_name}"
|
operation_name = f"{op}{self.full_name}"
|
||||||
if fn is None:
|
# apply function to operands
|
||||||
name = format.wrap(name, "(")
|
if fn is not None:
|
||||||
if fn is not None:
|
operation_name = (
|
||||||
name = f"{fn}({name})"
|
f"{fn}({operation_name})"
|
||||||
|
if not invoke_fn
|
||||||
|
else f"{self.full_name}.{fn}()"
|
||||||
|
)
|
||||||
|
|
||||||
return BaseVar(
|
return BaseVar(
|
||||||
name=name,
|
name=operation_name,
|
||||||
type_=type_,
|
type_=type_,
|
||||||
is_local=self.is_local,
|
is_local=self.is_local,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_valid_operation(
|
||||||
|
operand1_type: Type, operand2_type: Type, operator: str
|
||||||
|
) -> bool:
|
||||||
|
"""Check if an operation between two operands is valid.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
operand1_type: Type of the operand
|
||||||
|
operand2_type: Type of the second operand
|
||||||
|
operator: The operator.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Whether operation is valid or not
|
||||||
|
|
||||||
|
"""
|
||||||
|
if operator in ALL_OPS or operator in DELIMITERS:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# bools are subclasses of ints
|
||||||
|
pair = tuple(
|
||||||
|
sorted(
|
||||||
|
[
|
||||||
|
int if operand1_type == bool else operand1_type,
|
||||||
|
int if operand2_type == bool else operand2_type,
|
||||||
|
],
|
||||||
|
key=lambda x: x.__name__,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return pair in OPERATION_MAPPING and operator in OPERATION_MAPPING[pair]
|
||||||
|
|
||||||
def compare(self, op: str, other: Var) -> Var:
|
def compare(self, op: str, other: Var) -> Var:
|
||||||
"""Compare two vars with inequalities.
|
"""Compare two vars with inequalities.
|
||||||
|
|
||||||
@ -537,16 +635,26 @@ class Var(ABC):
|
|||||||
"""
|
"""
|
||||||
return self.compare("<=", other)
|
return self.compare("<=", other)
|
||||||
|
|
||||||
def __add__(self, other: Var) -> Var:
|
def __add__(self, other: Var, flip=False) -> Var:
|
||||||
"""Add two vars.
|
"""Add two vars.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
other: The other var to add.
|
other: The other var to add.
|
||||||
|
flip: Whether to flip operands.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A var representing the sum.
|
A var representing the sum.
|
||||||
"""
|
"""
|
||||||
return self.operation("+", other)
|
other_type = other.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
|
||||||
|
and types.get_base_class(other_type) == list
|
||||||
|
):
|
||||||
|
return self.operation(",", other, fn="spreadArraysOrObjects", flip=flip)
|
||||||
|
return self.operation("+", other, flip=flip)
|
||||||
|
|
||||||
def __radd__(self, other: Var) -> Var:
|
def __radd__(self, other: Var) -> Var:
|
||||||
"""Add two vars.
|
"""Add two vars.
|
||||||
@ -557,7 +665,7 @@ class Var(ABC):
|
|||||||
Returns:
|
Returns:
|
||||||
A var representing the sum.
|
A var representing the sum.
|
||||||
"""
|
"""
|
||||||
return self.operation("+", other, flip=True)
|
return self.__add__(other=other, flip=True)
|
||||||
|
|
||||||
def __sub__(self, other: Var) -> Var:
|
def __sub__(self, other: Var) -> Var:
|
||||||
"""Subtract two vars.
|
"""Subtract two vars.
|
||||||
@ -581,15 +689,39 @@ class Var(ABC):
|
|||||||
"""
|
"""
|
||||||
return self.operation("-", other, flip=True)
|
return self.operation("-", other, flip=True)
|
||||||
|
|
||||||
def __mul__(self, other: Var) -> Var:
|
def __mul__(self, other: Var, flip=True) -> Var:
|
||||||
"""Multiply two vars.
|
"""Multiply two vars.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
other: The other var to multiply.
|
other: The other var to multiply.
|
||||||
|
flip: Whether to flip operands
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A var representing the product.
|
A var representing the product.
|
||||||
"""
|
"""
|
||||||
|
other_type = other.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 [
|
||||||
|
(int, str),
|
||||||
|
(str, int),
|
||||||
|
]:
|
||||||
|
return self.operation(other=other, fn="repeat", invoke_fn=True)
|
||||||
|
|
||||||
|
# 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 [
|
||||||
|
(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()"
|
||||||
|
return BaseVar(
|
||||||
|
name=name,
|
||||||
|
type_=str,
|
||||||
|
is_local=self.is_local,
|
||||||
|
)
|
||||||
|
|
||||||
return self.operation("*", other)
|
return self.operation("*", other)
|
||||||
|
|
||||||
def __rmul__(self, other: Var) -> Var:
|
def __rmul__(self, other: Var) -> Var:
|
||||||
@ -601,7 +733,7 @@ class Var(ABC):
|
|||||||
Returns:
|
Returns:
|
||||||
A var representing the product.
|
A var representing the product.
|
||||||
"""
|
"""
|
||||||
return self.operation("*", other, flip=True)
|
return self.__mul__(other=other, flip=True)
|
||||||
|
|
||||||
def __pow__(self, other: Var) -> Var:
|
def __pow__(self, other: Var) -> Var:
|
||||||
"""Raise a var to a power.
|
"""Raise a var to a power.
|
||||||
@ -684,10 +816,29 @@ class Var(ABC):
|
|||||||
"""Perform a logical and.
|
"""Perform a logical and.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
other: The other var to perform the logical and with.
|
other: The other var to perform the logical AND with.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A var representing the logical and.
|
A var representing the logical AND.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
This method provides behavior specific to JavaScript, where it returns the JavaScript
|
||||||
|
equivalent code (using the '&&' operator) of a logical AND operation.
|
||||||
|
In JavaScript, the
|
||||||
|
logical OR operator '&&' is used for Boolean logic, and this method emulates that behavior
|
||||||
|
by returning the equivalent code as a Var instance.
|
||||||
|
|
||||||
|
In Python, logical AND 'and' operates differently, evaluating expressions immediately, making
|
||||||
|
it challenging to override the behavior entirely.
|
||||||
|
Therefore, this method leverages the
|
||||||
|
bitwise AND '__and__' operator for custom JavaScript-like behavior.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> var1 = Var.create(True)
|
||||||
|
>>> var2 = Var.create(False)
|
||||||
|
>>> js_code = var1 & var2
|
||||||
|
>>> print(js_code.full_name)
|
||||||
|
'(true && false)'
|
||||||
"""
|
"""
|
||||||
return self.operation("&&", other, type_=bool)
|
return self.operation("&&", other, type_=bool)
|
||||||
|
|
||||||
@ -695,10 +846,29 @@ class Var(ABC):
|
|||||||
"""Perform a logical and.
|
"""Perform a logical and.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
other: The other var to perform the logical and with.
|
other: The other var to perform the logical AND with.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A var representing the logical and.
|
A var representing the logical AND.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
This method provides behavior specific to JavaScript, where it returns the JavaScript
|
||||||
|
equivalent code (using the '&&' operator) of a logical AND operation.
|
||||||
|
In JavaScript, the
|
||||||
|
logical OR operator '&&' is used for Boolean logic, and this method emulates that behavior
|
||||||
|
by returning the equivalent code as a Var instance.
|
||||||
|
|
||||||
|
In Python, logical AND 'and' operates differently, evaluating expressions immediately, making
|
||||||
|
it challenging to override the behavior entirely.
|
||||||
|
Therefore, this method leverages the
|
||||||
|
bitwise AND '__rand__' operator for custom JavaScript-like behavior.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> var1 = Var.create(True)
|
||||||
|
>>> var2 = Var.create(False)
|
||||||
|
>>> js_code = var1 & var2
|
||||||
|
>>> print(js_code.full_name)
|
||||||
|
'(false && true)'
|
||||||
"""
|
"""
|
||||||
return self.operation("&&", other, type_=bool, flip=True)
|
return self.operation("&&", other, type_=bool, flip=True)
|
||||||
|
|
||||||
@ -710,6 +880,23 @@ class Var(ABC):
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A var representing the logical or.
|
A var representing the logical or.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
This method provides behavior specific to JavaScript, where it returns the JavaScript
|
||||||
|
equivalent code (using the '||' operator) of a logical OR operation. In JavaScript, the
|
||||||
|
logical OR operator '||' is used for Boolean logic, and this method emulates that behavior
|
||||||
|
by returning the equivalent code as a Var instance.
|
||||||
|
|
||||||
|
In Python, logical OR 'or' operates differently, evaluating expressions immediately, making
|
||||||
|
it challenging to override the behavior entirely. Therefore, this method leverages the
|
||||||
|
bitwise OR '__or__' operator for custom JavaScript-like behavior.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> var1 = Var.create(True)
|
||||||
|
>>> var2 = Var.create(False)
|
||||||
|
>>> js_code = var1 | var2
|
||||||
|
>>> print(js_code.full_name)
|
||||||
|
'(true || false)'
|
||||||
"""
|
"""
|
||||||
return self.operation("||", other, type_=bool)
|
return self.operation("||", other, type_=bool)
|
||||||
|
|
||||||
@ -721,6 +908,23 @@ class Var(ABC):
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A var representing the logical or.
|
A var representing the logical or.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
This method provides behavior specific to JavaScript, where it returns the JavaScript
|
||||||
|
equivalent code (using the '||' operator) of a logical OR operation. In JavaScript, the
|
||||||
|
logical OR operator '||' is used for Boolean logic, and this method emulates that behavior
|
||||||
|
by returning the equivalent code as a Var instance.
|
||||||
|
|
||||||
|
In Python, logical OR 'or' operates differently, evaluating expressions immediately, making
|
||||||
|
it challenging to override the behavior entirely. Therefore, this method leverages the
|
||||||
|
bitwise OR '__or__' operator for custom JavaScript-like behavior.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> var1 = Var.create(True)
|
||||||
|
>>> var2 = Var.create(False)
|
||||||
|
>>> js_code = var1 | var2
|
||||||
|
>>> print(js_code)
|
||||||
|
'false || true'
|
||||||
"""
|
"""
|
||||||
return self.operation("||", other, type_=bool, flip=True)
|
return self.operation("||", other, type_=bool, flip=True)
|
||||||
|
|
||||||
@ -752,13 +956,16 @@ class Var(ABC):
|
|||||||
raise TypeError(
|
raise TypeError(
|
||||||
f"Var {self.full_name} of type {self.type_} does not support contains check."
|
f"Var {self.full_name} of type {self.type_} does not support contains check."
|
||||||
)
|
)
|
||||||
|
method = (
|
||||||
|
"hasOwnProperty" if types.get_base_class(self.type_) == dict else "includes"
|
||||||
|
)
|
||||||
if isinstance(other, str):
|
if isinstance(other, str):
|
||||||
other = Var.create(json.dumps(other), is_string=True)
|
other = Var.create(json.dumps(other), is_string=True)
|
||||||
elif not isinstance(other, Var):
|
elif not isinstance(other, Var):
|
||||||
other = Var.create(other)
|
other = Var.create(other)
|
||||||
if types._issubclass(self.type_, Dict):
|
if types._issubclass(self.type_, Dict):
|
||||||
return BaseVar(
|
return BaseVar(
|
||||||
name=f"{self.full_name}.has({other.full_name})",
|
name=f"{self.full_name}.{method}({other.full_name})",
|
||||||
type_=bool,
|
type_=bool,
|
||||||
is_local=self.is_local,
|
is_local=self.is_local,
|
||||||
)
|
)
|
||||||
|
@ -277,6 +277,7 @@ def test_basic_operations(TestObj):
|
|||||||
)
|
)
|
||||||
assert str(abs(v(1))) == "{Math.abs(1)}"
|
assert str(abs(v(1))) == "{Math.abs(1)}"
|
||||||
assert str(v([1, 2, 3]).length()) == "{[1, 2, 3].length}"
|
assert str(v([1, 2, 3]).length()) == "{[1, 2, 3].length}"
|
||||||
|
assert str(v([1, 2]) + v([3, 4])) == "{spreadArraysOrObjects([1, 2] , [3, 4])}"
|
||||||
|
|
||||||
# Tests for reverse operation
|
# Tests for reverse operation
|
||||||
assert str(v([1, 2, 3]).reverse()) == "{[...[1, 2, 3]].reverse()}"
|
assert str(v([1, 2, 3]).reverse()) == "{[...[1, 2, 3]].reverse()}"
|
||||||
@ -338,14 +339,17 @@ def test_str_contains(var, expected):
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_dict_contains(var, expected):
|
def test_dict_contains(var, expected):
|
||||||
assert str(var.contains(1)) == f"{{{expected}.has(1)}}"
|
assert str(var.contains(1)) == f"{{{expected}.hasOwnProperty(1)}}"
|
||||||
assert str(var.contains("1")) == f'{{{expected}.has("1")}}'
|
assert str(var.contains("1")) == f'{{{expected}.hasOwnProperty("1")}}'
|
||||||
assert str(var.contains(v(1))) == f"{{{expected}.has(1)}}"
|
assert str(var.contains(v(1))) == f"{{{expected}.hasOwnProperty(1)}}"
|
||||||
assert str(var.contains(v("1"))) == f'{{{expected}.has("1")}}'
|
assert str(var.contains(v("1"))) == f'{{{expected}.hasOwnProperty("1")}}'
|
||||||
other_state_var = BaseVar(name="other", state="state", type_=str)
|
other_state_var = BaseVar(name="other", state="state", type_=str)
|
||||||
other_var = BaseVar(name="other", type_=str)
|
other_var = BaseVar(name="other", type_=str)
|
||||||
assert str(var.contains(other_state_var)) == f"{{{expected}.has(state.other)}}"
|
assert (
|
||||||
assert str(var.contains(other_var)) == f"{{{expected}.has(other)}}"
|
str(var.contains(other_state_var))
|
||||||
|
== f"{{{expected}.hasOwnProperty(state.other)}}"
|
||||||
|
)
|
||||||
|
assert str(var.contains(other_var)) == f"{{{expected}.hasOwnProperty(other)}}"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@ -784,3 +788,405 @@ def test_unsupported_default_contains():
|
|||||||
err.value.args[0]
|
err.value.args[0]
|
||||||
== "'in' operator not supported for Var types, use Var.contains() instead."
|
== "'in' operator not supported for Var types, use Var.contains() instead."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"operand1_var,operand2_var,operators",
|
||||||
|
[
|
||||||
|
(
|
||||||
|
Var.create(10),
|
||||||
|
Var.create(5),
|
||||||
|
[
|
||||||
|
"+",
|
||||||
|
"-",
|
||||||
|
"/",
|
||||||
|
"//",
|
||||||
|
"*",
|
||||||
|
"%",
|
||||||
|
"**",
|
||||||
|
">",
|
||||||
|
"<",
|
||||||
|
"<=",
|
||||||
|
">=",
|
||||||
|
"|",
|
||||||
|
"&",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Var.create(10.5),
|
||||||
|
Var.create(5),
|
||||||
|
["+", "-", "/", "//", "*", "%", "**", ">", "<", "<=", ">="],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Var.create(5),
|
||||||
|
Var.create(True),
|
||||||
|
[
|
||||||
|
"+",
|
||||||
|
"-",
|
||||||
|
"/",
|
||||||
|
"//",
|
||||||
|
"*",
|
||||||
|
"%",
|
||||||
|
"**",
|
||||||
|
">",
|
||||||
|
"<",
|
||||||
|
"<=",
|
||||||
|
">=",
|
||||||
|
"|",
|
||||||
|
"&",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Var.create(10.5),
|
||||||
|
Var.create(5.5),
|
||||||
|
["+", "-", "/", "//", "*", "%", "**", ">", "<", "<=", ">="],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Var.create(10.5),
|
||||||
|
Var.create(True),
|
||||||
|
["+", "-", "/", "//", "*", "%", "**", ">", "<", "<=", ">="],
|
||||||
|
),
|
||||||
|
(Var.create("10"), Var.create("5"), ["+", ">", "<", "<=", ">="]),
|
||||||
|
(Var.create([10, 20]), Var.create([5, 6]), ["+", ">", "<", "<=", ">="]),
|
||||||
|
(Var.create([10, 20]), Var.create(5), ["*"]),
|
||||||
|
(Var.create([10, 20]), Var.create(True), ["*"]),
|
||||||
|
(
|
||||||
|
Var.create(True),
|
||||||
|
Var.create(True),
|
||||||
|
[
|
||||||
|
"+",
|
||||||
|
"-",
|
||||||
|
"/",
|
||||||
|
"//",
|
||||||
|
"*",
|
||||||
|
"%",
|
||||||
|
"**",
|
||||||
|
">",
|
||||||
|
"<",
|
||||||
|
"<=",
|
||||||
|
">=",
|
||||||
|
"|",
|
||||||
|
"&",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_valid_var_operations(operand1_var: Var, operand2_var, operators: List[str]):
|
||||||
|
"""Test that operations do not raise a TypeError.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
operand1_var: left operand.
|
||||||
|
operand2_var: right operand.
|
||||||
|
operators: list of supported operators.
|
||||||
|
"""
|
||||||
|
for operator in operators:
|
||||||
|
operand1_var.operation(op=operator, other=operand2_var)
|
||||||
|
operand1_var.operation(op=operator, other=operand2_var, flip=True)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"operand1_var,operand2_var,operators",
|
||||||
|
[
|
||||||
|
(
|
||||||
|
Var.create(10),
|
||||||
|
Var.create(5),
|
||||||
|
[
|
||||||
|
"^",
|
||||||
|
"<<",
|
||||||
|
">>",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Var.create(10.5),
|
||||||
|
Var.create(5),
|
||||||
|
[
|
||||||
|
"|",
|
||||||
|
"^",
|
||||||
|
"<<",
|
||||||
|
">>",
|
||||||
|
"&",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Var.create(10.5),
|
||||||
|
Var.create(True),
|
||||||
|
[
|
||||||
|
"|",
|
||||||
|
"^",
|
||||||
|
"<<",
|
||||||
|
">>",
|
||||||
|
"&",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Var.create(10.5),
|
||||||
|
Var.create(5.5),
|
||||||
|
[
|
||||||
|
"|",
|
||||||
|
"^",
|
||||||
|
"<<",
|
||||||
|
">>",
|
||||||
|
"&",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Var.create("10"),
|
||||||
|
Var.create("5"),
|
||||||
|
[
|
||||||
|
"-",
|
||||||
|
"/",
|
||||||
|
"//",
|
||||||
|
"*",
|
||||||
|
"%",
|
||||||
|
"**",
|
||||||
|
"|",
|
||||||
|
"^",
|
||||||
|
"<<",
|
||||||
|
">>",
|
||||||
|
"&",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Var.create([10, 20]),
|
||||||
|
Var.create([5, 6]),
|
||||||
|
[
|
||||||
|
"-",
|
||||||
|
"/",
|
||||||
|
"//",
|
||||||
|
"*",
|
||||||
|
"%",
|
||||||
|
"**",
|
||||||
|
"|",
|
||||||
|
"^",
|
||||||
|
"<<",
|
||||||
|
">>",
|
||||||
|
"&",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Var.create([10, 20]),
|
||||||
|
Var.create(5),
|
||||||
|
[
|
||||||
|
"+",
|
||||||
|
"-",
|
||||||
|
"/",
|
||||||
|
"//",
|
||||||
|
"%",
|
||||||
|
"**",
|
||||||
|
">",
|
||||||
|
"<",
|
||||||
|
"<=",
|
||||||
|
">=",
|
||||||
|
"|",
|
||||||
|
"^",
|
||||||
|
"<<",
|
||||||
|
">>",
|
||||||
|
"&",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Var.create([10, 20]),
|
||||||
|
Var.create(True),
|
||||||
|
[
|
||||||
|
"+",
|
||||||
|
"-",
|
||||||
|
"/",
|
||||||
|
"//",
|
||||||
|
"%",
|
||||||
|
"**",
|
||||||
|
">",
|
||||||
|
"<",
|
||||||
|
"<=",
|
||||||
|
">=",
|
||||||
|
"|",
|
||||||
|
"^",
|
||||||
|
"<<",
|
||||||
|
">>",
|
||||||
|
"&",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Var.create([10, 20]),
|
||||||
|
Var.create("5"),
|
||||||
|
[
|
||||||
|
"+",
|
||||||
|
"-",
|
||||||
|
"/",
|
||||||
|
"//",
|
||||||
|
"*",
|
||||||
|
"%",
|
||||||
|
"**",
|
||||||
|
">",
|
||||||
|
"<",
|
||||||
|
"<=",
|
||||||
|
">=",
|
||||||
|
"|",
|
||||||
|
"^",
|
||||||
|
"<<",
|
||||||
|
">>",
|
||||||
|
"&",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Var.create([10, 20]),
|
||||||
|
Var.create({"key": "value"}),
|
||||||
|
[
|
||||||
|
"+",
|
||||||
|
"-",
|
||||||
|
"/",
|
||||||
|
"//",
|
||||||
|
"*",
|
||||||
|
"%",
|
||||||
|
"**",
|
||||||
|
">",
|
||||||
|
"<",
|
||||||
|
"<=",
|
||||||
|
">=",
|
||||||
|
"|",
|
||||||
|
"^",
|
||||||
|
"<<",
|
||||||
|
">>",
|
||||||
|
"&",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Var.create([10, 20]),
|
||||||
|
Var.create(5.5),
|
||||||
|
[
|
||||||
|
"+",
|
||||||
|
"-",
|
||||||
|
"/",
|
||||||
|
"//",
|
||||||
|
"*",
|
||||||
|
"%",
|
||||||
|
"**",
|
||||||
|
">",
|
||||||
|
"<",
|
||||||
|
"<=",
|
||||||
|
">=",
|
||||||
|
"|",
|
||||||
|
"^",
|
||||||
|
"<<",
|
||||||
|
">>",
|
||||||
|
"&",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Var.create({"key": "value"}),
|
||||||
|
Var.create({"another_key": "another_value"}),
|
||||||
|
[
|
||||||
|
"+",
|
||||||
|
"-",
|
||||||
|
"/",
|
||||||
|
"//",
|
||||||
|
"*",
|
||||||
|
"%",
|
||||||
|
"**",
|
||||||
|
">",
|
||||||
|
"<",
|
||||||
|
"<=",
|
||||||
|
">=",
|
||||||
|
"|",
|
||||||
|
"^",
|
||||||
|
"<<",
|
||||||
|
">>",
|
||||||
|
"&",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Var.create({"key": "value"}),
|
||||||
|
Var.create(5),
|
||||||
|
[
|
||||||
|
"+",
|
||||||
|
"-",
|
||||||
|
"/",
|
||||||
|
"//",
|
||||||
|
"*",
|
||||||
|
"%",
|
||||||
|
"**",
|
||||||
|
">",
|
||||||
|
"<",
|
||||||
|
"<=",
|
||||||
|
">=",
|
||||||
|
"|",
|
||||||
|
"^",
|
||||||
|
"<<",
|
||||||
|
">>",
|
||||||
|
"&",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Var.create({"key": "value"}),
|
||||||
|
Var.create(True),
|
||||||
|
[
|
||||||
|
"+",
|
||||||
|
"-",
|
||||||
|
"/",
|
||||||
|
"//",
|
||||||
|
"*",
|
||||||
|
"%",
|
||||||
|
"**",
|
||||||
|
">",
|
||||||
|
"<",
|
||||||
|
"<=",
|
||||||
|
">=",
|
||||||
|
"|",
|
||||||
|
"^",
|
||||||
|
"<<",
|
||||||
|
">>",
|
||||||
|
"&",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Var.create({"key": "value"}),
|
||||||
|
Var.create(5.5),
|
||||||
|
[
|
||||||
|
"+",
|
||||||
|
"-",
|
||||||
|
"/",
|
||||||
|
"//",
|
||||||
|
"*",
|
||||||
|
"%",
|
||||||
|
"**",
|
||||||
|
">",
|
||||||
|
"<",
|
||||||
|
"<=",
|
||||||
|
">=",
|
||||||
|
"|",
|
||||||
|
"^",
|
||||||
|
"<<",
|
||||||
|
">>",
|
||||||
|
"&",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Var.create({"key": "value"}),
|
||||||
|
Var.create("5"),
|
||||||
|
[
|
||||||
|
"+",
|
||||||
|
"-",
|
||||||
|
"/",
|
||||||
|
"//",
|
||||||
|
"*",
|
||||||
|
"%",
|
||||||
|
"**",
|
||||||
|
">",
|
||||||
|
"<",
|
||||||
|
"<=",
|
||||||
|
">=",
|
||||||
|
"|",
|
||||||
|
"^",
|
||||||
|
"<<",
|
||||||
|
">>",
|
||||||
|
"&",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_invalid_var_operations(operand1_var: Var, operand2_var, operators: List[str]):
|
||||||
|
for operator in operators:
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
operand1_var.operation(op=operator, other=operand2_var)
|
||||||
|
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
operand1_var.operation(op=operator, other=operand2_var, flip=True)
|
||||||
|
Loading…
Reference in New Issue
Block a user