Skip to content

Commit 146d305

Browse files
authored
Merge pull request #315 from linkml/eval_utils_docs
fixed eval expression doctests and switched to pytest
2 parents 27990c1 + ed36311 commit 146d305

File tree

2 files changed

+61
-91
lines changed

2 files changed

+61
-91
lines changed

linkml_runtime/utils/eval_utils.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@
1717

1818
def eval_conditional(*conds: List[Tuple[bool, Any]]) -> Any:
1919
"""
20-
>>> cond(x < 25 : 'low', x > 25 : 'high', True: 'low')
20+
Evaluate a collection of expression,value tuples, returing the first value whose expression is true
21+
22+
>>> x= 40
23+
>>> eval_conditional((x < 25, 'low'), (x > 25, 'high'), (True, 'low'))
24+
'high'
25+
2126
:param subj:
2227
:return:
2328
"""
@@ -58,10 +63,9 @@ def eval_expr(expr: str, **kwargs) -> Any:
5863
5964
Nulls:
6065
61-
- If a variable is enclosed in {}s then entire expression will eval to None if variable is unset
66+
- If a variable is enclosed in {}s then entire expression will eval to None if any variable is unset
6267
63-
>>> eval_expr('{x} + {y}', x=None, y=2)
64-
None
68+
>>> assert eval_expr('{x} + {y}', x=None, y=2) is None
6569
6670
Functions:
6771
@@ -92,9 +96,6 @@ def eval_expr(expr: str, **kwargs) -> Any:
9296

9397

9498

95-
96-
97-
9899
def eval_(node, bindings={}):
99100
if isinstance(node, ast.Num):
100101
return node.n

tests/test_utils/test_eval_utils.py

Lines changed: 53 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import unittest
21
from dataclasses import dataclass
32
from typing import List, Dict
43

4+
import pytest
5+
56
from linkml_runtime.utils.eval_utils import eval_expr
67

78

@@ -24,90 +25,58 @@ class Container:
2425
person_index: Dict[str, Person] = None
2526

2627

27-
class EvalUtilsTestCase(unittest.TestCase):
28-
"""
29-
Tests for linkml_runtime.utils.eval_utils
30-
"""
31-
32-
def test_eval_expressions(self):
33-
"""
34-
Tests evaluation of expressions using eval_expr
35-
"""
36-
x = eval_expr("1 + 2")
37-
self.assertEqual(x, 3)
38-
self.assertEqual(eval_expr("1 + 2 + 3"), 6)
39-
x = eval_expr("{z} + 2", z=1)
40-
self.assertEqual(x, 3)
41-
self.assertIsNone(eval_expr('{x} + {y}', x=5, y=None))
42-
x = eval_expr("'x' + 'y'")
43-
assert x == 'xy'
44-
#x = eval_expr("'{x}' + '{y}'", x='a', y='b')
45-
#self.assertEqual(x, 'ab')
46-
self.assertEqual(eval_expr("['a','b'] + ['c','d']"), ['a', 'b', 'c', 'd'])
47-
self.assertEqual(eval_expr("{x} + {y}", x=['a', 'b'], y=['c', 'd']), ['a', 'b', 'c', 'd'])
48-
self.assertEqual(eval_expr("{'a': 1}"), {'a': 1})
49-
self.assertEqual(eval_expr("max([1, 5, 2])"), 5)
50-
self.assertEqual(eval_expr("max({x})", x=[1, 5, 2]), 5)
51-
self.assertEqual(eval_expr("True"), True)
52-
self.assertEqual(eval_expr("False"), False)
53-
self.assertEqual(eval_expr("1 + 1 == 3"), False)
54-
self.assertEqual(eval_expr("1 < 2"), True)
55-
self.assertEqual(eval_expr("1 <= 1"), True)
56-
self.assertEqual(eval_expr("1 >= 1"), True)
57-
self.assertEqual(eval_expr("2 > 1"), True)
58-
self.assertEqual(eval_expr("'EQ' if {x} == {y} else 'NEQ'", x=1, y=1), 'EQ')
59-
self.assertEqual(eval_expr("'EQ' if {x} == {y} else 'NEQ'", x=1, y=2), 'NEQ')
60-
self.assertEqual(eval_expr("'NOT_NULL' if x else 'NULL'", x=1), 'NOT_NULL')
61-
self.assertEqual(eval_expr("'NOT_NULL' if x else 'NULL'", x=None), 'NULL')
62-
self.assertEqual(eval_expr("'EQ' if {x} == {y} else 'NEQ'", x=1, y=2), 'NEQ')
63-
case = "case(({x} < 25, 'LOW'), ({x} > 75, 'HIGH'), (True, 'MEDIUM'))"
64-
self.assertEqual(eval_expr(case, x=10), 'LOW')
65-
self.assertEqual(eval_expr(case, x=100), 'HIGH')
66-
self.assertEqual(eval_expr(case, x=50), 'MEDIUM')
67-
self.assertEqual(eval_expr('x', x='a'), 'a')
68-
self.assertEqual(eval_expr('x+y', x=1, y=2), 3)
69-
# todo
70-
self.assertEqual(eval_expr('x["a"] + y', x={'a': 1}, y=2), 3)
71-
self.assertEqual(eval_expr('x["a"]["b"] + y', x={'a': {'b': 1}}, y=2), 3)
72-
p = Person(name='x', aliases=['a', 'b', 'c'], address=Address(street='1 x street'))
73-
self.assertEqual(eval_expr('p.name', p=p), 'x')
74-
self.assertEqual(eval_expr('p.address.street', p=p), '1 x street')
75-
self.assertEqual(eval_expr('len(p.aliases)', p=p), 3)
76-
self.assertEqual(eval_expr('p.aliases', p=p), p.aliases)
77-
p2 = Person(name='x2', aliases=['a2', 'b2', 'c2'], address=Address(street='2 x street'))
78-
c = Container(persons=[p, p2])
79-
x = eval_expr('c.persons.name', c=c)
80-
self.assertEqual(x, ['x', 'x2'])
81-
x = eval_expr('c.persons.address.street', c=c)
82-
self.assertEqual(x, ['1 x street', '2 x street'])
83-
x = eval_expr('strlen(c.persons.address.street)', c=c)
84-
self.assertEqual(x, [10, 10])
85-
c = Container(person_index={p.name: p, p2.name: p2})
86-
x = eval_expr('c.person_index.name', c=c)
87-
#print(x)
88-
self.assertEqual(x, ['x', 'x2'])
89-
x = eval_expr('c.person_index.address.street', c=c)
90-
self.assertEqual(x, ['1 x street', '2 x street'])
91-
x = eval_expr('strlen(c.person_index.name)', c=c)
92-
self.assertEqual(x, [1, 2])
93-
#self.assertEqual('x', eval_expr('"x" if True else "y"'))
94-
95-
def test_no_eval_prohibited(self):
96-
"""
97-
Ensure that certain patterns cannot be evaluated
28+
def test_eval_expressions():
29+
assert eval_expr("1 + 2") == 3
30+
assert eval_expr("1 + 2 + 3") == 6
31+
assert eval_expr("{z} + 2", z=1) == 3
32+
assert eval_expr('{x} + {y}', x=5, y=None) is None
33+
assert eval_expr("'x' + 'y'") == 'xy'
34+
assert eval_expr("['a','b'] + ['c','d']") == ['a', 'b', 'c', 'd']
35+
assert eval_expr("{x} + {y}", x=['a', 'b'], y=['c', 'd']) == ['a', 'b', 'c', 'd']
36+
assert eval_expr("{'a': 1}") == {'a': 1}
37+
assert eval_expr("max([1, 5, 2])") == 5
38+
assert eval_expr("max({x})", x=[1, 5, 2]) == 5
39+
assert eval_expr("True") is True
40+
assert eval_expr("False") is False
41+
assert eval_expr("1 + 1 == 3") is False
42+
assert eval_expr("1 < 2") is True
43+
assert eval_expr("1 <= 1") is True
44+
assert eval_expr("1 >= 1") is True
45+
assert eval_expr("2 > 1") is True
46+
assert eval_expr("'EQ' if {x} == {y} else 'NEQ'", x=1, y=1) == 'EQ'
47+
assert eval_expr("'EQ' if {x} == {y} else 'NEQ'", x=1, y=2) == 'NEQ'
48+
assert eval_expr("'NOT_NULL' if x else 'NULL'", x=1) == 'NOT_NULL'
49+
assert eval_expr("'NOT_NULL' if x else 'NULL'", x=None) == 'NULL'
50+
assert eval_expr("'EQ' if {x} == {y} else 'NEQ'", x=1, y=2) == 'NEQ'
51+
case = "case(({x} < 25, 'LOW'), ({x} > 75, 'HIGH'), (True, 'MEDIUM'))"
52+
assert eval_expr(case, x=10) == 'LOW'
53+
assert eval_expr(case, x=100) == 'HIGH'
54+
assert eval_expr(case, x=50) == 'MEDIUM'
55+
assert eval_expr('x', x='a') == 'a'
56+
assert eval_expr('x+y', x=1, y=2) == 3
57+
assert eval_expr('x["a"] + y', x={'a': 1}, y=2) == 3
58+
assert eval_expr('x["a"]["b"] + y', x={'a': {'b': 1}}, y=2) == 3
59+
p = Person(name='x', aliases=['a', 'b', 'c'], address=Address(street='1 x street'))
60+
assert eval_expr('p.name', p=p) == 'x'
61+
assert eval_expr('p.address.street', p=p) == '1 x street'
62+
assert eval_expr('len(p.aliases)', p=p) == 3
63+
assert eval_expr('p.aliases', p=p) == p.aliases
64+
p2 = Person(name='x2', aliases=['a2', 'b2', 'c2'], address=Address(street='2 x street'))
65+
c = Container(persons=[p, p2])
66+
assert eval_expr('c.persons.name', c=c) == ['x', 'x2']
67+
assert eval_expr('c.persons.address.street', c=c) == ['1 x street', '2 x street']
68+
assert eval_expr('strlen(c.persons.address.street)', c=c) == [10, 10]
69+
c = Container(person_index={p.name: p, p2.name: p2})
70+
assert eval_expr('c.person_index.name', c=c) == ['x', 'x2']
71+
assert eval_expr('c.person_index.address.street', c=c) == ['1 x street', '2 x street']
72+
assert eval_expr('strlen(c.person_index.name)', c=c) == [1, 2]
9873

99-
See `<https://stackoverflow.com/questions/2371436/evaluating-a-mathematical-expression-in-a-string>`_
100-
"""
101-
with self.assertRaises(NotImplementedError):
102-
eval_expr("__import__('os').listdir()")
10374

104-
def test_funcs(self):
105-
"""
106-
Not yet implemented
107-
"""
108-
with self.assertRaises(NotImplementedError):
109-
eval_expr("my_func([1,2,3])")
75+
def test_no_eval_prohibited():
76+
with pytest.raises(NotImplementedError):
77+
eval_expr("__import__('os').listdir()")
11078

11179

112-
if __name__ == '__main__':
113-
unittest.main()
80+
def test_funcs():
81+
with pytest.raises(NotImplementedError):
82+
eval_expr("my_func([1,2,3])")

0 commit comments

Comments
 (0)