rules icon indicating copy to clipboard operation
rules copied to clipboard

[python] does durable_rules swallow exceptions?

Open GergelyMolnar opened this issue 6 years ago • 2 comments

Having the

There's this mockup with and without try/except block there are the same two distinct errors in each:

  • ZeroDivisionError
  • TypeError
with ruleset('swallow_error'):
    @when_all(
        (m.foo == True) &
        (m.bar != None)
    )
    def catch_do(c):
        bar_val = str(c.m.bar)
        ugly = 10/0
        # c.s.ret = {}
        c.s.ret.update({1: {'path': f'{c.m.foo}', 'value': bar_val}})

with ruleset('expose_error'):
    @when_all(
        (m.foo == True) &
        (m.bar != None)
    )
    def catch_do(c):
        try:
            bar_val = str(c.m.bar)
            ugly = 10 / 0
            # c.s.ret = {}
            c.s.ret.update({1: {'path': f'{c.m.foo}', 'value': bar_val}})
        except Exception as e:
            print(f"{type(e).__name__}: {e.args[0]}, {e.args[1:]}")

jj = {'foo': True, 'bar': 'Lorem ipsum'}

Calling post() on each ruleset

answ1 = post('swallow_error', jj)
answ2 = post('expose_error', jj)

will return:

ZeroDivisionError: division by zero, ()

Process finished with exit code 0

Also executing with

answ1 = post('swallow_error', jj)
print(f"answ1.ret: {answ1['ret']}")
answ2 = post('expose_error', jj)

Returns

Traceback (most recent call last):
  File "/.../durarules.py", line 1237, in <module>
    print(f"answ1.ret: {answ1['ret']}")
KeyError: 'ret'

Process finished with exit code 1

Which is thrown after durable_rules finished running.

In my actual code nested try/except block revealed multiple TypeError exceptions (eg. trying to use dict.update() as c.s.var.update({...})) of which durable rules happily ignored, causing some head-scratching.

GergelyMolnar avatar Feb 06 '20 14:02 GergelyMolnar

Hi, thanks for asking the question. The action execution is not guaranteed to be synchronous with event Post or Fact assert/retract. Exceptions are caught and set in the context state.

with ruleset('flow1'):
    
    @when_all(m.action == 'start')
    def first(c):
        raise Exception('Unhandled Exception!')

    # when the exception property exists
    @when_all(+s.exception)
    def second(c):
        print('flow1-> expected {0}'.format(c.s.exception))
        c.s.exception = None
        
post('flow1', { 'action': 'start' })

jruizgit avatar Feb 13 '20 06:02 jruizgit

Thank you! That explains it. So I have two options, either wrap each consequent function-body in a try/except block or rely on the c.s.exception value. These give plenty of freedom. Ta

GergelyMolnar avatar Feb 13 '20 11:02 GergelyMolnar