From 43683b7c16e8f9f53edce40089fec11ad77791bb Mon Sep 17 00:00:00 2001 From: Talley Lambert Date: Mon, 3 Jun 2024 09:09:58 -0400 Subject: [PATCH] feat: add containers to expressions --- src/app_model/expressions/_expressions.py | 48 +++++++++++++++++++++++ tests/test_context/test_expressions.py | 12 +++--- 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/src/app_model/expressions/_expressions.py b/src/app_model/expressions/_expressions.py index e765ec9..04841c6 100644 --- a/src/app_model/expressions/_expressions.py +++ b/src/app_model/expressions/_expressions.py @@ -475,6 +475,45 @@ def __init__(self, test: Expr, body: Expr, orelse: Expr, **kwargs: Any) -> None: ) +class Tuple(Expr, ast.Tuple): + """A tuple expression. + + `elts` is a list of expressions. + """ + + def __init__( + self, elts: Sequence[Expr], ctx: ast.expr_context = LOAD, **kwargs: Any + ) -> None: + kwargs["ctx"] = ctx + super().__init__(elts=[Expr._cast(e) for e in elts], **kwargs) + + +class List(Expr, ast.List): + """A tuple expression. + + `elts` is a list of expressions. + """ + + def __init__( + self, elts: Sequence[Expr], ctx: ast.expr_context = LOAD, **kwargs: Any + ) -> None: + kwargs["ctx"] = ctx + super().__init__(elts=[Expr._cast(e) for e in elts], **kwargs) + + +class Set(Expr, ast.Set): + """A tuple expression. + + `elts` is a list of expressions. + """ + + def __init__( + self, elts: Sequence[Expr], ctx: ast.expr_context = LOAD, **kwargs: Any + ) -> None: + kwargs["ctx"] = ctx + super().__init__(elts=[Expr._cast(e) for e in elts], **kwargs) + + class ExprTransformer(ast.NodeTransformer): """Transformer that converts an ast.expr into an `Expr`. @@ -568,6 +607,15 @@ def __str__(self) -> str: def visit_Name(self, node: ast.Name) -> None: self.write(node.id) + def visit_Tuple(self, node: ast.Tuple) -> None: + self.write(f'({", ".join(map(str, node.elts))})') + + def visit_Set(self, node: ast.Set) -> None: + self.write("{" + ", ".join(map(str, node.elts)) + "}") + + def visit_List(self, node: ast.List) -> None: + self.write(f'[{", ".join(map(str, node.elts))}]') + def visit_ContextKey(self, node: ContextKey) -> None: return self.visit_Name(node) diff --git a/tests/test_context/test_expressions.py b/tests/test_context/test_expressions.py index a62d69f..2f4a787 100644 --- a/tests/test_context/test_expressions.py +++ b/tests/test_context/test_expressions.py @@ -162,6 +162,9 @@ def test_iter_names(): "1", "3.14", "True", + "1 in {1, 2, 3}", + "1 in [1, 2, 3]", + "1 in (1, 2, 3)", "False", "None", "hieee", @@ -183,10 +186,7 @@ def test_iter_names(): "my.attribute", # Attribute "__import__(something)", # Call 'print("hi")', - "(1,)", # tuples not yet supported '{"key": "val"}', # dicts not yet supported - '{"hi"}', # set constant - "[]", # lists constant "mylist[0]", # Index "mylist[0:1]", # Slice 'f"a"', # JoinedStr @@ -228,13 +228,13 @@ def test_safe_eval(): assert safe_eval(expr, {"x": 1}) == 3 assert safe_eval(True) is True assert safe_eval(False) is False + assert safe_eval("[1,2,3]") == [1, 2, 3] + assert safe_eval("(1,2,3)") == (1, 2, 3) + assert safe_eval("{1,2,3}") == {1, 2, 3} with pytest.raises(SyntaxError, match="Type 'Call' not supported"): safe_eval("func(x)") - with pytest.raises(SyntaxError, match="Type 'Set' not supported"): - safe_eval("{1,2,3}") - def test_eval_kwargs(): expr = parse_expression("a + b")