From ed36311ad4d3b131a9e000ad77ba130095974868 Mon Sep 17 00:00:00 2001 From: "Mark A. Miller" Date: Fri, 22 Mar 2024 17:36:06 -0400 Subject: [PATCH] fixed eval expression doctests and switched to pytest --- linkml_runtime/utils/eval_utils.py | 15 +-- tests/test_utils/test_eval_utils.py | 137 +++++++++++----------------- 2 files changed, 61 insertions(+), 91 deletions(-) diff --git a/linkml_runtime/utils/eval_utils.py b/linkml_runtime/utils/eval_utils.py index 60c8d70a..962325f7 100644 --- a/linkml_runtime/utils/eval_utils.py +++ b/linkml_runtime/utils/eval_utils.py @@ -17,7 +17,12 @@ def eval_conditional(*conds: List[Tuple[bool, Any]]) -> Any: """ - >>> cond(x < 25 : 'low', x > 25 : 'high', True: 'low') + Evaluate a collection of expression,value tuples, returing the first value whose expression is true + + >>> x= 40 + >>> eval_conditional((x < 25, 'low'), (x > 25, 'high'), (True, 'low')) + 'high' + :param subj: :return: """ @@ -58,10 +63,9 @@ def eval_expr(expr: str, **kwargs) -> Any: Nulls: - - If a variable is enclosed in {}s then entire expression will eval to None if variable is unset + - If a variable is enclosed in {}s then entire expression will eval to None if any variable is unset - >>> eval_expr('{x} + {y}', x=None, y=2) - None + >>> assert eval_expr('{x} + {y}', x=None, y=2) is None Functions: @@ -92,9 +96,6 @@ def eval_expr(expr: str, **kwargs) -> Any: - - - def eval_(node, bindings={}): if isinstance(node, ast.Num): return node.n diff --git a/tests/test_utils/test_eval_utils.py b/tests/test_utils/test_eval_utils.py index 4bc906cf..500d79ac 100644 --- a/tests/test_utils/test_eval_utils.py +++ b/tests/test_utils/test_eval_utils.py @@ -1,7 +1,8 @@ -import unittest from dataclasses import dataclass from typing import List, Dict +import pytest + from linkml_runtime.utils.eval_utils import eval_expr @@ -24,90 +25,58 @@ class Container: person_index: Dict[str, Person] = None -class EvalUtilsTestCase(unittest.TestCase): - """ - Tests for linkml_runtime.utils.eval_utils - """ - - def test_eval_expressions(self): - """ - Tests evaluation of expressions using eval_expr - """ - x = eval_expr("1 + 2") - self.assertEqual(x, 3) - self.assertEqual(eval_expr("1 + 2 + 3"), 6) - x = eval_expr("{z} + 2", z=1) - self.assertEqual(x, 3) - self.assertIsNone(eval_expr('{x} + {y}', x=5, y=None)) - x = eval_expr("'x' + 'y'") - assert x == 'xy' - #x = eval_expr("'{x}' + '{y}'", x='a', y='b') - #self.assertEqual(x, 'ab') - self.assertEqual(eval_expr("['a','b'] + ['c','d']"), ['a', 'b', 'c', 'd']) - self.assertEqual(eval_expr("{x} + {y}", x=['a', 'b'], y=['c', 'd']), ['a', 'b', 'c', 'd']) - self.assertEqual(eval_expr("{'a': 1}"), {'a': 1}) - self.assertEqual(eval_expr("max([1, 5, 2])"), 5) - self.assertEqual(eval_expr("max({x})", x=[1, 5, 2]), 5) - self.assertEqual(eval_expr("True"), True) - self.assertEqual(eval_expr("False"), False) - self.assertEqual(eval_expr("1 + 1 == 3"), False) - self.assertEqual(eval_expr("1 < 2"), True) - self.assertEqual(eval_expr("1 <= 1"), True) - self.assertEqual(eval_expr("1 >= 1"), True) - self.assertEqual(eval_expr("2 > 1"), True) - self.assertEqual(eval_expr("'EQ' if {x} == {y} else 'NEQ'", x=1, y=1), 'EQ') - self.assertEqual(eval_expr("'EQ' if {x} == {y} else 'NEQ'", x=1, y=2), 'NEQ') - self.assertEqual(eval_expr("'NOT_NULL' if x else 'NULL'", x=1), 'NOT_NULL') - self.assertEqual(eval_expr("'NOT_NULL' if x else 'NULL'", x=None), 'NULL') - self.assertEqual(eval_expr("'EQ' if {x} == {y} else 'NEQ'", x=1, y=2), 'NEQ') - case = "case(({x} < 25, 'LOW'), ({x} > 75, 'HIGH'), (True, 'MEDIUM'))" - self.assertEqual(eval_expr(case, x=10), 'LOW') - self.assertEqual(eval_expr(case, x=100), 'HIGH') - self.assertEqual(eval_expr(case, x=50), 'MEDIUM') - self.assertEqual(eval_expr('x', x='a'), 'a') - self.assertEqual(eval_expr('x+y', x=1, y=2), 3) - # todo - self.assertEqual(eval_expr('x["a"] + y', x={'a': 1}, y=2), 3) - self.assertEqual(eval_expr('x["a"]["b"] + y', x={'a': {'b': 1}}, y=2), 3) - p = Person(name='x', aliases=['a', 'b', 'c'], address=Address(street='1 x street')) - self.assertEqual(eval_expr('p.name', p=p), 'x') - self.assertEqual(eval_expr('p.address.street', p=p), '1 x street') - self.assertEqual(eval_expr('len(p.aliases)', p=p), 3) - self.assertEqual(eval_expr('p.aliases', p=p), p.aliases) - p2 = Person(name='x2', aliases=['a2', 'b2', 'c2'], address=Address(street='2 x street')) - c = Container(persons=[p, p2]) - x = eval_expr('c.persons.name', c=c) - self.assertEqual(x, ['x', 'x2']) - x = eval_expr('c.persons.address.street', c=c) - self.assertEqual(x, ['1 x street', '2 x street']) - x = eval_expr('strlen(c.persons.address.street)', c=c) - self.assertEqual(x, [10, 10]) - c = Container(person_index={p.name: p, p2.name: p2}) - x = eval_expr('c.person_index.name', c=c) - #print(x) - self.assertEqual(x, ['x', 'x2']) - x = eval_expr('c.person_index.address.street', c=c) - self.assertEqual(x, ['1 x street', '2 x street']) - x = eval_expr('strlen(c.person_index.name)', c=c) - self.assertEqual(x, [1, 2]) - #self.assertEqual('x', eval_expr('"x" if True else "y"')) - - def test_no_eval_prohibited(self): - """ - Ensure that certain patterns cannot be evaluated +def test_eval_expressions(): + assert eval_expr("1 + 2") == 3 + assert eval_expr("1 + 2 + 3") == 6 + assert eval_expr("{z} + 2", z=1) == 3 + assert eval_expr('{x} + {y}', x=5, y=None) is None + assert eval_expr("'x' + 'y'") == 'xy' + assert eval_expr("['a','b'] + ['c','d']") == ['a', 'b', 'c', 'd'] + assert eval_expr("{x} + {y}", x=['a', 'b'], y=['c', 'd']) == ['a', 'b', 'c', 'd'] + assert eval_expr("{'a': 1}") == {'a': 1} + assert eval_expr("max([1, 5, 2])") == 5 + assert eval_expr("max({x})", x=[1, 5, 2]) == 5 + assert eval_expr("True") is True + assert eval_expr("False") is False + assert eval_expr("1 + 1 == 3") is False + assert eval_expr("1 < 2") is True + assert eval_expr("1 <= 1") is True + assert eval_expr("1 >= 1") is True + assert eval_expr("2 > 1") is True + assert eval_expr("'EQ' if {x} == {y} else 'NEQ'", x=1, y=1) == 'EQ' + assert eval_expr("'EQ' if {x} == {y} else 'NEQ'", x=1, y=2) == 'NEQ' + assert eval_expr("'NOT_NULL' if x else 'NULL'", x=1) == 'NOT_NULL' + assert eval_expr("'NOT_NULL' if x else 'NULL'", x=None) == 'NULL' + assert eval_expr("'EQ' if {x} == {y} else 'NEQ'", x=1, y=2) == 'NEQ' + case = "case(({x} < 25, 'LOW'), ({x} > 75, 'HIGH'), (True, 'MEDIUM'))" + assert eval_expr(case, x=10) == 'LOW' + assert eval_expr(case, x=100) == 'HIGH' + assert eval_expr(case, x=50) == 'MEDIUM' + assert eval_expr('x', x='a') == 'a' + assert eval_expr('x+y', x=1, y=2) == 3 + assert eval_expr('x["a"] + y', x={'a': 1}, y=2) == 3 + assert eval_expr('x["a"]["b"] + y', x={'a': {'b': 1}}, y=2) == 3 + p = Person(name='x', aliases=['a', 'b', 'c'], address=Address(street='1 x street')) + assert eval_expr('p.name', p=p) == 'x' + assert eval_expr('p.address.street', p=p) == '1 x street' + assert eval_expr('len(p.aliases)', p=p) == 3 + assert eval_expr('p.aliases', p=p) == p.aliases + p2 = Person(name='x2', aliases=['a2', 'b2', 'c2'], address=Address(street='2 x street')) + c = Container(persons=[p, p2]) + assert eval_expr('c.persons.name', c=c) == ['x', 'x2'] + assert eval_expr('c.persons.address.street', c=c) == ['1 x street', '2 x street'] + assert eval_expr('strlen(c.persons.address.street)', c=c) == [10, 10] + c = Container(person_index={p.name: p, p2.name: p2}) + assert eval_expr('c.person_index.name', c=c) == ['x', 'x2'] + assert eval_expr('c.person_index.address.street', c=c) == ['1 x street', '2 x street'] + assert eval_expr('strlen(c.person_index.name)', c=c) == [1, 2] - See ``_ - """ - with self.assertRaises(NotImplementedError): - eval_expr("__import__('os').listdir()") - def test_funcs(self): - """ - Not yet implemented - """ - with self.assertRaises(NotImplementedError): - eval_expr("my_func([1,2,3])") +def test_no_eval_prohibited(): + with pytest.raises(NotImplementedError): + eval_expr("__import__('os').listdir()") -if __name__ == '__main__': - unittest.main() +def test_funcs(): + with pytest.raises(NotImplementedError): + eval_expr("my_func([1,2,3])") \ No newline at end of file