Add contains, reverse operations for Var (#1679)

This commit is contained in:
Martin Xu 2023-08-28 14:59:50 -07:00 committed by GitHub
parent 6bfce48b0c
commit 2e1aea9713
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 223 additions and 1 deletions

View File

@ -685,6 +685,76 @@ class Var(ABC):
"""
return self.operation("||", other, type_=bool, flip=True)
def __contains__(self, _: Any) -> Var:
"""Override the 'in' operator to alert the user that it is not supported.
Raises:
TypeError: the operation is not supported
"""
raise TypeError(
"'in' operator not supported for Var types, use Var.contains() instead."
)
def contains(self, other: Any) -> Var:
"""Check if a var contains the object `other`.
Args:
other: The object to check.
Raises:
TypeError: If the var is not a valid type: dict, list, tuple or str.
Returns:
A var representing the contain check.
"""
if self.type_ is None or not (
types._issubclass(self.type_, Union[dict, list, tuple, str])
):
raise TypeError(
f"Var {self.full_name} of type {self.type_} does not support contains check."
)
if isinstance(other, str):
other = Var.create(json.dumps(other), is_string=True)
elif not isinstance(other, Var):
other = Var.create(other)
if types._issubclass(self.type_, Dict):
return BaseVar(
name=f"{self.full_name}.has({other.full_name})",
type_=bool,
is_local=self.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
):
raise TypeError(
f"'in <string>' requires string as left operand, not {other.type_}"
)
return BaseVar(
name=f"{self.full_name}.includes({other.full_name})",
type_=bool,
is_local=self.is_local,
)
def reverse(self) -> Var:
"""Reverse a list var.
Raises:
TypeError: If the var is not a list.
Returns:
A var with the reversed list.
"""
if self.type_ is None or not types._issubclass(self.type_, list):
raise TypeError(f"Cannot reverse non-list var {self.full_name}.")
return BaseVar(
name=f"[...{self.full_name}].reverse()",
type_=self.type_,
is_local=self.is_local,
)
def foreach(self, fn: Callable) -> Var:
"""Return a list of components. after doing a foreach on this var.

View File

@ -1,3 +1,4 @@
import json
import typing
from typing import Dict, List, Set, Tuple
@ -239,7 +240,11 @@ def test_create_type_error():
def v(value) -> Var:
val = Var.create(value, is_local=False)
val = (
Var.create(json.dumps(value), is_string=True, is_local=False)
if isinstance(value, str)
else Var.create(value, is_local=False)
)
assert val is not None
return val
@ -273,6 +278,75 @@ def test_basic_operations(TestObj):
assert str(abs(v(1))) == "{Math.abs(1)}"
assert str(v([1, 2, 3]).length()) == "{[1, 2, 3].length}"
# 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()}'
assert (
str(BaseVar(name="foo", state="state", type_=list).reverse())
== "{[...state.foo].reverse()}"
)
assert str(BaseVar(name="foo", type_=list).reverse()) == "{[...foo].reverse()}"
@pytest.mark.parametrize(
"var, expected",
[
(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"),
(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"),
],
)
def test_list_tuple_contains(var, expected):
assert str(var.contains(1)) == f"{{{expected}.includes(1)}}"
assert str(var.contains("1")) == f'{{{expected}.includes("1")}}'
assert str(var.contains(v(1))) == f"{{{expected}.includes(1)}}"
assert str(var.contains(v("1"))) == f'{{{expected}.includes("1")}}'
other_state_var = BaseVar(name="other", state="state", type_=str)
other_var = BaseVar(name="other", type_=str)
assert str(var.contains(other_state_var)) == f"{{{expected}.includes(state.other)}}"
assert str(var.contains(other_var)) == f"{{{expected}.includes(other)}}"
@pytest.mark.parametrize(
"var, expected",
[
(v("123"), json.dumps("123")),
(BaseVar(name="foo", state="state", type_=str), "state.foo"),
(BaseVar(name="foo", 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)
assert str(var.contains(other_state_var)) == f"{{{expected}.includes(state.other)}}"
assert str(var.contains(other_var)) == f"{{{expected}.includes(other)}}"
@pytest.mark.parametrize(
"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"),
],
)
def test_dict_contains(var, expected):
assert str(var.contains(1)) == f"{{{expected}.has(1)}}"
assert str(var.contains("1")) == f'{{{expected}.has("1")}}'
assert str(var.contains(v(1))) == f"{{{expected}.has(1)}}"
assert str(var.contains(v("1"))) == f'{{{expected}.has("1")}}'
other_state_var = BaseVar(name="other", state="state", type_=str)
other_var = BaseVar(name="other", type_=str)
assert str(var.contains(other_state_var)) == f"{{{expected}.has(state.other)}}"
assert str(var.contains(other_var)) == f"{{{expected}.has(other)}}"
@pytest.mark.parametrize(
"var",
@ -632,3 +706,81 @@ def test_get_local_storage_raise_error(key):
)
def test_fstrings(out, expected):
assert 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),
],
)
def test_unsupported_types_for_reverse(var):
"""Test that unsupported types for reverse throw a type error.
Args:
var: The base var.
"""
with pytest.raises(TypeError) as err:
var.reverse()
assert err.value.args[0] == f"Cannot reverse non-list var 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),
],
)
def test_unsupported_types_for_contains(var):
"""Test that unsupported types for contains throw a type error.
Args:
var: The base var.
"""
with pytest.raises(TypeError) as err:
assert var.contains(1)
assert (
err.value.args[0]
== f"Var var of type {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),
],
)
def test_unsupported_types_for_string_contains(other):
with pytest.raises(TypeError) as err:
assert BaseVar(name="var", type_=str).contains(other)
assert (
err.value.args[0]
== f"'in <string>' requires string as left operand, not {other.type_}"
)
def test_unsupported_default_contains():
with pytest.raises(TypeError) as err:
assert 1 in BaseVar(name="var", type_=str)
assert (
err.value.args[0]
== "'in' operator not supported for Var types, use Var.contains() instead."
)