traits icon indicating copy to clipboard operation
traits copied to clipboard

Explain why exceptions don't bubble up in observers

Open achabotl opened this issue 1 year ago • 0 comments

In the sample below exceptions that are raised through observe cannot be caught in unit tests. It's expected behavior, but it may be surprising to some.

import unittest

from traits.api import HasTraits, Int, observe


class Person(HasTraits):
    age = Int()

    @observe("age")
    def update_age(self, event):
        if self.age > 15:
            print("Raising an exception")
            raise ValueError("I do not want to get old")


class TestPerson(unittest.TestCase):
    def test_update_age(self):
        # given
        steve = Person(age=10)

        # when / then
        with self.assertRaises(ValueError) as err:
            steve.age = 18

        self.assertIn("I do not want to get old", str(err.exception))

The following explanation from @mdickinson could be cleaned up and integrated in the docs:

There are mechanisms that allow exceptions in observe handlers to be propagated; that's sometimes useful during testing. But raising an exception in an observer, while it might be tempting, isn't really a terribly useful thing to do - there's no sensible place for that exception to be propagated to. IOW, an exception raised in an observer is almost always a coding bug. In particular, you should never try to use observers for validation of an assignment to a trait - that doesn't work, because the assignment has already happened.

For testing and debugging, see https://docs.enthought.com/traits/traits_user_manual/debugging.html#re-raising-exceptions-in-change-handlers, and in particular push_exception_handler. Note that there are two versions of push_exception_handler: one for on_trait_change and one for observe, so you need to use both if you want to catch all exceptions. But beware doing this in tests by default, since now your test logic isn't necessarily exercising the same code paths that your business logic is. It's quite possible to end up with broken business logic that passes test cases as a result.

Internal discussion thread: https://enthought.slack.com/archives/C024RRPHD/p1667297991245249

achabotl avatar Nov 01 '22 14:11 achabotl