Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent observer from propagating exceptions #71

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion atom/src/member.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1077,7 +1077,9 @@ Member::notify( CAtom* atom, PyObject* args, PyObject* kwargs )
callable = *it;
}
if( !callable( argsptr, kwargsptr ) )
return false;
{
PyErr_Print();
}
}
}
return true;
Expand Down
14 changes: 0 additions & 14 deletions atom/src/modifyguard.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,6 @@ class ModifyGuard

~ModifyGuard()
{
// If an exception occurred we store it and restore it after the
// modification have been done as otherwise it can (Python 3) cause
// boolean tests (PyObject_IsTrue) to fail for wrong reasons.
bool exception_set = false;
PyObject *type, *value, *traceback;
if( PyErr_Occurred() ){
PyErr_Fetch(&type, &value, &traceback);
exception_set = true;
}

if( m_owner.get_modify_guard() == this )
{
m_owner.set_modify_guard( 0 );
Expand All @@ -51,10 +41,6 @@ class ModifyGuard
delete *it;
}
}

// Restore previous exception if one was set.
if( exception_set )
PyErr_Restore(type, value, traceback);
}

void add_task( ModifyTask* task ) { m_tasks.push_back( task ); }
Expand Down
5 changes: 3 additions & 2 deletions atom/src/observerpool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
|----------------------------------------------------------------------------*/
#include "observerpool.h"


namespace
{

Expand Down Expand Up @@ -220,7 +219,9 @@ ObserverPool::notify( PyObjectPtr& topic, PyObjectPtr& args, PyObjectPtr& kwargs
if( obs_it->is_true() )
{
if( !obs_it->operator()( args, kwargs ) )
return false;
{
PyErr_Print();
}
}
else
{
Expand Down
1 change: 0 additions & 1 deletion atom/src/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ namespace utils
#define STR_CHECK_EXACT( obj ) PyString_CheckExact( obj ) || PyUnicode_CheckExact( obj )
#endif


inline bool
basestring_check( PyObject* obj )
{
Expand Down
68 changes: 65 additions & 3 deletions tests/test_observe.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from atom.api import Atom, Int, List, Value, Event, Signal, observe


def test_static_observers():
def test_static_observers(capsys):
"""Test using static observers.

"""
Expand Down Expand Up @@ -72,8 +72,9 @@ def react(self, change):
del ot.ext
assert not ext2.has_observer('val', ot.react)

with pytest.raises(TypeError):
ot.ext = 12
assert not capsys.readouterr()[1]
ot.ext = 12
assert capsys.readouterr()[1]


def test_dynamic_observers():
Expand Down Expand Up @@ -230,5 +231,66 @@ def react(self, change):
with pytest.raises(TypeError):
observe(['a.b.c'])


def test_handling_of_exceptions_in_observers(capsys):
"""Test that we properly print exception occuring in observers.

"""
class Observer(object):

def __init__(self):
self.count = 0
self.exc_cls = ValueError

def react(self, change):
self.count += 1
raise self.exc_cls()

class NotifTest(Atom):

val = Int()

count = Int()

exc_cls = Value(ValueError)

def _observe_val(self, change):
self.count += 1
raise self.exc_cls()

ob = Observer()
nt = NotifTest()
nt.observe('val', ob.react)

assert not capsys.readouterr()[1]

# Check both static and dynamic notifiers are called even when raising
nt.val = 1
assert ob.count == 1
assert nt.count == 1

# Check we did print to stderr
assert capsys.readouterr()[1]

class MyException(Exception):

def __str__(self):
raise ValueError()

ob.exc_cls = MyException
nt.exc_cls = MyException

assert not capsys.readouterr()[1]

# Check both static and dynamic notifiers are called even when raising
nt.val += 1
assert ob.count == 2
assert nt.count == 2

# Check we did print to stderr
err = capsys.readouterr()[1]
print(err)
assert err

# XXX add a test catching the SystemError of Python 3
# For the time being I known how to write one only using enaml