Skip to content

Commit 45a9360

Browse files
committed
Add try-except-finally operation.
1 parent b4a80dd commit 45a9360

File tree

5 files changed

+79
-13
lines changed

5 files changed

+79
-13
lines changed

.dockerignore

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
**/__pycache__
2-
**/.git
3-
**/playground
4-
**/build
1+
.git/**/*
2+
.git/
3+
playground/**/*
4+
playground/

pythonflow/core.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import operator
2222
import uuid
2323

24+
from .util import _noop_callback
25+
2426

2527
class Graph:
2628
"""
@@ -301,12 +303,9 @@ def evaluate(self, context, callback=None):
301303
args = [partial(arg) for arg in self.args]
302304
kwargs = {key: partial(value) for key, value in self.kwargs.items()}
303305
# Evaluate the operation
304-
if callback:
305-
with callback(self, context):
306-
context[self] = value = self._evaluate(*args, **kwargs)
307-
else:
306+
callback = callback or _noop_callback
307+
with callback(self, context):
308308
context[self] = value = self._evaluate(*args, **kwargs)
309-
310309
return value
311310

312311
def _evaluate(self, *args, **kwargs):
@@ -333,6 +332,9 @@ def evaluate_operation(cls, operation, context, **kwargs):
333332
return slice(*[partial(getattr(operation, attr)) for attr in ['start', 'stop', 'step']])
334333
return operation
335334

335+
def __bool__(self):
336+
return True
337+
336338
def __hash__(self):
337339
return id(self)
338340

pythonflow/operations.py

+36-4
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616

1717
import functools
1818
import logging
19+
import sys
1920

2021
from .core import opmethod, Operation, func_op
22+
from .util import _noop_callback
2123

2224

2325
class placeholder(Operation): # pylint: disable=C0103,R0903
@@ -52,13 +54,43 @@ def evaluate(self, context, callback=None):
5254
predicate, x, y = self.args # pylint: disable=E0632,C0103
5355
# Evaluate the predicate and pick the right operation
5456
predicate = self.evaluate_operation(predicate, context)
55-
if callback:
56-
with callback(self, context):
57-
context[self] = value = self.evaluate_operation(x if predicate else y, context)
58-
else:
57+
callback = callback or _noop_callback
58+
with callback(self, context):
5959
context[self] = value = self.evaluate_operation(x if predicate else y, context)
6060
return value
6161

62+
63+
class try_(Operation): # pylint: disable=C0103,W0223
64+
"""
65+
Try to evaluate `operation`, fall back to operations
66+
"""
67+
def __init__(self, operation, except_=None, finally_=None, **kwargs):
68+
except_ = except_ or []
69+
super(try_, self).__init__(operation, except_, finally_, **kwargs)
70+
71+
def evaluate(self, context, callback=None):
72+
# Evaluate all dependencies first
73+
self.evaluate_dependencies(context)
74+
75+
operation, except_, finally_ = self.args # pylint: disable=E0632,C0103
76+
callback = callback or _noop_callback
77+
with callback(self, context):
78+
try:
79+
context[self] = value = self.evaluate_operation(operation, context)
80+
return value
81+
except:
82+
# Check the exceptions
83+
_, ex, _ = sys.exc_info()
84+
for type_, alternative in except_:
85+
if isinstance(ex, type_):
86+
context[self] = value = self.evaluate_operation(alternative, context)
87+
return value
88+
raise
89+
finally:
90+
if finally_:
91+
self.evaluate_operation(finally_, context)
92+
93+
6294
@opmethod
6395
def identity(value):
6496
"""

pythonflow/util.py

+5
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,8 @@ def __call__(self, operation, context):
111111

112112
def __str__(self):
113113
return "\n".join(['%s: %s' % item for item in self.get_slow_operations(10).items()])
114+
115+
116+
@contextlib.contextmanager
117+
def _noop_callback(*_):
118+
yield

tests/test_pythonflow.py

+27
Original file line numberDiff line numberDiff line change
@@ -362,3 +362,30 @@ def test_slice():
362362
c = a[b:]
363363

364364
assert len(graph(c)) == 99
365+
366+
367+
def test_bool():
368+
with pf.Graph() as graph:
369+
a = pf.placeholder()
370+
371+
assert a
372+
373+
374+
@pytest.mark.parametrize('context, expected', [
375+
({'a': 1, 'b': 0}, 'zero-division'),
376+
({'a': 1, 'b': 2}, 0.5),
377+
])
378+
def test_try(context, expected):
379+
finally_reached = []
380+
381+
with pf.Graph() as graph:
382+
a = pf.placeholder('a')
383+
b = pf.placeholder('b')
384+
c = pf.try_(
385+
a / b,
386+
[(ZeroDivisionError, 'zero-division')],
387+
pf.func_op(lambda: finally_reached.append('done'))
388+
)
389+
390+
assert graph(c, context) == expected
391+
assert finally_reached

0 commit comments

Comments
 (0)