Tips for debugging Traits¶
Re-raising exceptions in change handlers¶
Traits will typically log (instead of raise) exceptions when an exception is
encountered in a trait-change handler. This behavior is often preferred in
applications, since you usually want to avoid critical failures in
applications. However, when debugging these errors, the
logging.Logger.exception
only displays the tip of the traceback. For example,
the following code changes a constant
:
from traits.api import HasTraits, Int
class Curmudgeon(HasTraits):
constant = Int(1)
def _constant_changed(self):
raise ValueError()
c = Curmudgeon()
c.constant = 42
The constant
trait-change handler raises an exception that is caught and
logged:
Exception occurred in traits notification handler.
Please check the log file for details.
Exception occurred in traits notification handler for object:
<__main__.Curmudgeon object at 0x107603050>, trait: constant, old value: 0, new value: 42
...
File "curmudgeon.py", line 12, in _constant_changed
raise ValueError()
ValueError
This logged exception, however, only contains the tip of the traceback. This makes debugging a bit difficult. You can force exceptions to be re-raised by adding a custom exception handler:
from traits.api import push_exception_handler
push_exception_handler(reraise_exceptions=True)
(For example, you could add this to the top of the original code block.)
Re-running the original code example with this custom handler will now raise the following traceback:
Traceback (most recent call last):
File "curmudgeon.py", line 15, in <module>
c.constant = 42
...
File "curmudgeon.py", line 12, in _constant_changed
raise ValueError()
ValueError
Notice that this traceback has information about where we changed
constant
. Note: This is a toy example; use Constant
from
traits.api
if you actually want a constant trait.
Tracing Traits Change Events¶
Occasionally it is necessary to find the chain of event dispatches in traits
classes. To help with debugging, a record_events()
context manager is provided
in traits.util.event_tracer
. Trait change events taking place inside the
context block will be recorded in a change event container (see example below)
and can be saved to files (a file for each thread) for further inspection.
Example:
from traits.api import *
from traits.util.event_tracer import record_events
class MyModel(HasTraits):
number = Float(2.0)
list_of_numbers = List(Float())
count = Int(0)
@on_trait_change('number')
def _add_number_to_list(self, value):
self.list_of_numbers.append(value)
@on_trait_change('list_of_numbers[]')
def _count_items(self):
self.count = len(self.list_on_numbers)
def add_to_number(self, value):
self.number += value
my_model = MyModel()
with record_events() as change_event_container:
my_model.number = 4.7
my_model.number = 3
# save files locally
change_event_container.save_to_directory('./')
Running the above example will write a file named MAinThread.trace in the local folder. The file contents will be similar to the lines below:
2014-03-21 14:11:20.779000 -> 'number' changed from 2.0 to 4.7 in 'MyModel'
2014-03-21 14:11:20.779000 CALLING: '_add_number_to_list' in example.py
2014-03-21 14:11:20.780000 ---> 'list_of_numbers_items' changed from <undefined> to <traits.trait_handlers.TraitListEvent object at 0x03C85AF0> in 'MyModel'
2014-03-21 14:11:20.780000 CALLING: 'handle_list_items_special' in C:\Users\itziakos\Projects\traits\traits\traits_listener.py
2014-03-21 14:11:20.780000 -----> 'list_of_numbers_items' changed from [] to [4.7] in 'MyModel'
2014-03-21 14:11:20.780000 CALLING: '_count_items' in exampler.py
2014-03-21 14:11:20.780000 -------> 'trait_added' changed from <undefined> to 'list_on_numbers' in 'MyModel'
2014-03-21 14:11:20.780000 CALLING: '_trait_added_changed' in C:\Users\itziakos\Projects\traits\traits\has_traits.py
2014-03-21 14:11:20.780000 <------- EXIT: '_trait_added_changed'
2014-03-21 14:11:20.780000 <----- EXIT: '_count_items' [EXCEPTION: 'MyModel' object has no attribute 'list_on_numbers']
2014-03-21 14:11:20.780000 <--- EXIT: 'handle_list_items_special'
2014-03-21 14:11:20.781000 <- EXIT: '_add_number_to_list'
2014-03-21 14:11:20.781000 -> 'number' changed from 4.7 to 3.0 in 'MyModel'
2014-03-21 14:11:20.781000 CALLING: '_add_number_to_list' in example.py
2014-03-21 14:11:20.781000 ---> 'list_of_numbers_items' changed from <undefined> to <traits.trait_handlers.TraitListEvent object at 0x03C85A30> in 'MyModel'
2014-03-21 14:11:20.781000 CALLING: 'handle_list_items_special' in C:\Users\itziakos\Projects\traits\traits\traits_listener.py
2014-03-21 14:11:20.781000 -----> 'list_of_numbers_items' changed from [] to [3.0] in 'MyModel'
2014-03-21 14:11:20.781000 CALLING: '_count_items' in example.py
2014-03-21 14:11:20.781000 <----- EXIT: '_count_items' [EXCEPTION: 'MyModel' object has no attribute 'list_on_numbers']
2014-03-21 14:11:20.782000 <--- EXIT: 'handle_list_items_special'
2014-03-21 14:11:20.782000 <- EXIT: '_add_number_to_list'