예제 #1
0
    def test_notifier_can_be_removed(self):
        def filter_func(name, trait):
            return name.startswith("num")

        instance = DummyParent()
        handler = mock.Mock()
        call_add_or_remove_notifiers(
            object=instance,
            graph=create_graph(create_observer(filter=filter_func)),
            handler=handler,
        )

        # sanity check
        instance.number += 1
        self.assertEqual(handler.call_count, 1)
        handler.reset_mock()

        # when
        call_add_or_remove_notifiers(
            object=instance,
            graph=create_graph(create_observer(filter=filter_func)),
            handler=handler,
            remove=True,
        )

        # then
        instance.number += 1
        self.assertEqual(handler.call_count, 0)
예제 #2
0
    def test_maintain_notifier_for_default(self):
        # Dynamic defaults are not computed when hooking up the notifiers.
        # By when the default is defined, the maintainer will then hook up
        # the child observer.

        foo = ClassWithDefault()
        graph = create_graph(
            create_observer(name="instance", notify=True),
            create_observer(name="value1", notify=True),
        )
        handler = mock.Mock()
        call_add_or_remove_notifiers(
            object=foo,
            graph=graph,
            handler=handler,
        )

        # sanity check test setup.
        self.assertNotIn("instance", foo.__dict__)

        # when
        foo.instance   # this triggers the default to be computed and set

        # then
        # setting the default does not trigger notifications
        self.assertEqual(handler.call_count, 0)

        # when
        foo.instance.value1 += 1

        # then
        # the notifier for value1 has been hooked up by the maintainer
        self.assertEqual(handler.call_count, 1)
예제 #3
0
    def test_maintainer_keep_notify_flag(self):
        # Test the maintainer will maintain the notify flag for the root
        # observer in the subgraph.
        instance = DummyHasTraitsClass()
        notifier = DummyNotifier()
        graph = create_graph(
            self.observer,
            DummyObserver(
                notify=False,
                notifier=notifier,
            ),
        )
        handler = mock.Mock()
        call_add_or_remove_notifiers(
            object=instance,
            handler=handler,
            target=instance,
            graph=graph,
            remove=False,
        )

        # when
        instance.add_trait("good_name", Str())

        # then
        # notify flag is set to false, so there are no notifiers added.
        notifiers = instance._trait("good_name", 2)._notifiers(True)
        self.assertNotIn(notifier, notifiers)
예제 #4
0
    def test_filter_metadata(self):
        class Person(HasTraits):
            n_jobs = Int(status="public")
            n_children = Int()  # no metadata

        observer = FilteredTraitObserver(
            filter=MetadataFilter(metadata_name="status", ),
            notify=True,
        )
        person = Person()
        handler = mock.Mock()
        call_add_or_remove_notifiers(
            object=person,
            graph=create_graph(observer),
            handler=handler,
        )

        # when
        person.n_jobs += 1

        # then
        self.assertEqual(handler.call_count, 1)
        handler.reset_mock()

        # when
        person.n_children += 1

        # then
        self.assertEqual(handler.call_count, 0)
예제 #5
0
    def test_add_trait_remove_trait_then_add_trait_again(self):
        # Test a scenario when a trait is added, then removed, then added back.

        # given
        # trait is optional. It will be added later.
        graph = create_graph(
            create_observer(name="new_value", notify=True, optional=True),
        )
        handler = mock.Mock()
        foo = ClassWithInstance()
        call_add_or_remove_notifiers(
            object=foo, graph=graph, handler=handler, remove=False)

        foo.add_trait("new_value", Int())
        foo.new_value += 1
        handler.assert_called_once()
        handler.reset_mock()

        # when
        # remove the trait and then add it back
        foo.remove_trait("new_value")
        foo.add_trait("new_value", Int())

        # then
        # the handler is now back! The trait was not defined on the class,
        # so the last 'add_trait' fires a trait_added event.
        foo.new_value += 1
        handler.assert_called_once()
예제 #6
0
    def test_maintain_notifier_change_to_new_value(self):
        # Test when the container object is changed, the notifiers are
        # maintained downstream.

        foo = ClassWithInstance()
        graph = create_graph(
            create_observer(name="instance", notify=True),
            create_observer(name="value1", notify=True),
        )
        handler = mock.Mock()
        call_add_or_remove_notifiers(
            object=foo,
            graph=graph,
            handler=handler,
        )

        # sanity check
        foo.instance = ClassWithTwoValue()
        foo.instance.value1 += 1
        self.assertEqual(handler.call_count, 2)

        # when
        old_instance = foo.instance
        foo.instance = ClassWithTwoValue()
        handler.reset_mock()
        old_instance.value1 += 1

        # then
        self.assertEqual(handler.call_count, 0)

        # when
        foo.instance.value1 += 1

        # then
        self.assertEqual(handler.call_count, 1)
예제 #7
0
    def test_maintain_notifier_change_to_none(self):
        # Instance may accept None, maintainer should accomodate that
        # and skip it for the next observer.

        class UnassumingObserver(DummyObserver):
            def iter_observables(self, object):
                if object is None:
                    raise ValueError("This observer cannot handle None.")
                yield from ()

        foo = ClassWithInstance()
        graph = create_graph(
            create_observer(name="instance", notify=True),
            UnassumingObserver(),
        )
        handler = mock.Mock()
        call_add_or_remove_notifiers(
            object=foo,
            graph=graph,
            handler=handler,
        )

        foo.instance = ClassWithTwoValue()

        try:
            foo.instance = None
        except Exception:
            self.fail("Setting instance back to None should not fail.")
예제 #8
0
    def test_add_notifier_atomic(self):
        class BadNotifier(DummyNotifier):
            def add_to(self, observable):
                raise ZeroDivisionError()

        observable = DummyObservable()
        good_observer = DummyObserver(
            notify=True,
            observables=[observable],
            next_objects=[mock.Mock()],
            notifier=DummyNotifier(),
            maintainer=DummyNotifier(),
        )
        bad_observer = DummyObserver(
            notify=True,
            observables=[observable],
            notifier=BadNotifier(),
            maintainer=DummyNotifier(),
        )
        graph = create_graph(
            good_observer,
            bad_observer,
        )

        # when
        with self.assertRaises(ZeroDivisionError):
            call_add_or_remove_notifiers(
                object=mock.Mock(),
                graph=graph,
            )

        # then
        self.assertEqual(observable.notifiers, [])
예제 #9
0
    def test_notifier_extended_trait_change(self):

        foo = ClassWithInstance()
        graph = create_graph(
            create_observer(name="instance", notify=True),
            create_observer(name="value1", notify=True),
        )
        handler = mock.Mock()
        call_add_or_remove_notifiers(
            object=foo,
            graph=graph,
            handler=handler,
        )
        self.assertIsNone(foo.instance)

        # when
        foo.instance = ClassWithTwoValue()

        # then
        ((event, ), _), = handler.call_args_list
        self.assertEqual(event.object, foo)
        self.assertEqual(event.name, "instance")
        self.assertEqual(event.old, None)
        self.assertEqual(event.new, foo.instance)

        # when
        handler.reset_mock()
        foo.instance.value1 += 1

        # then
        ((event, ), _), = handler.call_args_list
        self.assertEqual(event.object, foo.instance)
        self.assertEqual(event.name, "value1")
        self.assertEqual(event.old, 0)
        self.assertEqual(event.new, 1)
예제 #10
0
    def test_notify_filter_values_changed(self):
        instance = DummyParent()

        # Observes number and number2
        observer = create_observer(
            filter=lambda name, trait: type(trait.trait_type) is Int,
        )
        handler = mock.Mock()
        call_add_or_remove_notifiers(
            object=instance,
            graph=create_graph(observer),
            handler=handler,
        )

        # when
        instance.number += 1

        # then
        self.assertEqual(handler.call_count, 1)
        handler.reset_mock()

        # when
        instance.number2 += 1

        # then
        self.assertEqual(handler.call_count, 1)
예제 #11
0
    def test_maintain_notifier_for_removed(self):
        # Test removing downstream notifier by observing a nested dict
        # inside another dict
        instance = ClassWithDict(dict_of_dict={"1": {"2": 2}})
        graph = create_graph(
            create_observer(notify=False, optional=False),
            create_observer(notify=True, optional=False),
        )

        handler = mock.Mock()
        call_add_or_remove_notifiers(
            object=instance.dict_of_dict,
            graph=graph,
            handler=handler,
        )

        # sanity check test setup
        inner_dict = instance.dict_of_dict["1"]
        inner_dict["3"] = 3
        self.assertEqual(handler.call_count, 1)
        ((event, ), _), = handler.call_args_list
        self.assertEqual(event.added, {"3": 3})
        self.assertEqual(event.removed, {})
        handler.reset_mock()

        # when
        # Change the content to something else
        instance.dict_of_dict["1"] = {}

        # the inner dict is not inside the instance.dict_of_dict any more
        inner_dict["4"] = 4

        # then
        self.assertEqual(handler.call_count, 0)
예제 #12
0
    def test_maintain_notifier_for_added(self):
        # Test adding downstream notifier by observing a nested dict
        # inside another dict

        instance = ClassWithDict()
        graph = create_graph(
            create_observer(notify=False, optional=False),
            create_observer(notify=True, optional=False),
        )

        handler = mock.Mock()
        call_add_or_remove_notifiers(
            object=instance.dict_of_dict,
            graph=graph,
            handler=handler,
        )

        # when
        instance.dict_of_dict.update({"1": {"2": 2}})

        # then
        # ``notify`` is set to False for mutations on the outer dict
        self.assertEqual(handler.call_count, 0)

        # when
        del instance.dict_of_dict["1"]["2"]

        # then
        # ``notify`` is set to True for mutations on the inner dict
        self.assertEqual(handler.call_count, 1)
        ((event, ), _), = handler.call_args_list
        self.assertEqual(event.added, {})
        self.assertEqual(event.removed, {"2": 2})
예제 #13
0
    def test_observe_remove_notifiers_remove_trait_added(self):
        graph = create_graph(
            create_observer(name="value", notify=True, optional=True),
        )
        handler = mock.Mock()
        foo = ClassWithInstance()

        # when
        # The following should cancel each other
        call_add_or_remove_notifiers(
            object=foo, graph=graph, handler=handler, remove=False)
        call_add_or_remove_notifiers(
            object=foo, graph=graph, handler=handler, remove=True)

        # when
        foo.add_trait("value", Int())

        # then
        self.assertEqual(handler.call_count, 0)

        # when
        foo.value += 1

        # then
        self.assertEqual(handler.call_count, 0)
예제 #14
0
    def test_get_maintainer_excuse_old_value_with_no_notifiers(self):
        # The "instance" trait has a default that has not been
        # materialized prior to the user setting a new value to the trait.
        # There isn't an old: Uninitialized -> new: Default value change event.
        # Instead, there is a old: Default -> new value event.
        # The old default value in this event won't have any notifiers
        # to be removed, therefore we need to excuse the NotifierNotFound
        # in the maintainer when it tries to remove notifiers from the old
        # value.

        foo = ClassWithDefault()
        graph = create_graph(
            create_observer(name="instance", notify=True),
            create_observer(name="value1", notify=True),
        )
        handler = mock.Mock()
        call_add_or_remove_notifiers(
            object=foo,
            graph=graph,
            handler=handler,
        )

        try:
            foo.instance = ClassWithTwoValue()
        except Exception:
            self.fail(
                "Reassigning the instance value should not fail."
            )
예제 #15
0
    def test_remove_atomic(self):
        # Test atomicity
        notifier = DummyNotifier()
        maintainer = DummyNotifier()
        observable1 = DummyObservable()
        observable1.notifiers = [
            notifier,
            maintainer,
        ]
        old_observable1_notifiers = observable1.notifiers.copy()
        observable2 = DummyObservable()
        observable2.notifiers = [maintainer]
        old_observable2_notifiers = observable2.notifiers.copy()
        observable3 = DummyObservable()
        observable3.notifiers = [
            notifier,
            maintainer,
        ]
        old_observable3_notifiers = observable3.notifiers.copy()

        observer = DummyObserver(
            notify=True,
            observables=[
                observable1,
                observable2,
                observable3,
            ],
            notifier=notifier,
            maintainer=maintainer,
        )
        graph = create_graph(
            observer,
            DummyObserver(),  # Need a child graph to get maintainer in
        )

        # when
        with self.assertRaises(NotifierNotFound):
            call_add_or_remove_notifiers(
                object=mock.Mock(),
                graph=graph,
                remove=True,
            )

        # then
        # as if nothing has happened, the order might not be maintained though!
        self.assertCountEqual(
            observable1.notifiers,
            old_observable1_notifiers,
        )
        self.assertCountEqual(
            observable2.notifiers,
            old_observable2_notifiers,
        )
        self.assertCountEqual(
            observable3.notifiers,
            old_observable3_notifiers,
        )
예제 #16
0
    def test_notifier_trait_added_distinguished(self):
        # Add two observers, both will have their own additional trait_added
        # observer. When one is removed, the other one is not affected.
        graph1 = create_graph(
            create_observer(name="some_value1", notify=True, optional=True),
        )
        graph2 = create_graph(
            create_observer(name="some_value2", notify=True, optional=True),
        )

        handler = mock.Mock()
        foo = ClassWithInstance()
        # Add two observers
        call_add_or_remove_notifiers(
            object=foo, graph=graph1, handler=handler, remove=False)
        call_add_or_remove_notifiers(
            object=foo, graph=graph2, handler=handler, remove=False)

        # when
        # Now remove the second observer
        call_add_or_remove_notifiers(
            object=foo, graph=graph2, handler=handler, remove=True)

        # the first one should still respond to trait_added event
        foo.add_trait("some_value1", Int())
        foo.some_value1 += 1

        # then
        self.assertEqual(handler.call_count, 1)
        handler.reset_mock()

        # when
        # the second observer has been removed
        foo.add_trait("some_value2", Int())
        foo.some_value2 += 1

        # then
        self.assertEqual(handler.call_count, 0)
예제 #17
0
    def test_trait_added_removed(self):

        instance = Dummy()
        integer_observer = create_observer(
            filter=lambda name, trait: type(trait.trait_type) is Int,
            notify=True,
        )
        handler = mock.Mock()
        call_add_or_remove_notifiers(
            object=instance,
            graph=create_graph(integer_observer),
            handler=handler,
        )

        # Add two traits.
        # If the maintainer from TraitAddedObserver did not restrict its
        # action to just the added trait, when 'count' is added, the previously
        # added 'another_number' would have received a second notifier again.
        # Then it would require two *remove* actions in order to clean up
        # notifiers on 'another_number'.
        instance.add_trait("another_number", Int())
        instance.add_trait("count", Int())

        # when
        call_add_or_remove_notifiers(
            object=instance,
            graph=create_graph(integer_observer),
            handler=handler,
            remove=True,
        )

        # then
        instance.another_number += 1
        self.assertEqual(handler.call_count, 0)
        instance.count += 1
        self.assertEqual(handler.call_count, 0)
    def test_optional_observers(self):
        # ListItemObserver.optional is true, meaning it will ignore
        # incompatible incoming object.
        instance = ClassWithList()

        graph = create_graph(ListItemObserver(notify=True, optional=True), )
        handler = mock.Mock()
        call_add_or_remove_notifiers(
            object=instance.not_a_trait_list,
            graph=graph,
            handler=handler,
        )

        instance.not_a_trait_list = CustomList()
        instance.not_a_trait_list.append(1)

        self.assertEqual(handler.call_count, 0)
예제 #19
0
    def test_notify_set_change(self):
        instance = ClassWithSet(values=set())
        graph = create_graph(create_observer(notify=True), )
        handler = mock.Mock()
        call_add_or_remove_notifiers(
            object=instance.values,
            graph=graph,
            handler=handler,
        )

        # when
        instance.values.add(1)

        # then
        ((event, ), _), = handler.call_args_list
        self.assertEqual(event.added, set([1]))
        self.assertEqual(event.removed, set())
예제 #20
0
    def test_optional_trait_added(self):
        graph = create_graph(
            create_observer(name="value", notify=True, optional=True),
        )
        handler = mock.Mock()

        not_an_has_traits_instance = mock.Mock()

        # does not complain because optional is set to true
        try:
            call_add_or_remove_notifiers(
                object=not_an_has_traits_instance,
                graph=graph,
                handler=handler,
            )
        except Exception:
            self.fail("Optional flag should have been propagated.")
예제 #21
0
    def test_maintain_notifier(self):
        # Test maintaining downstream notifier by
        # observing list of list

        instance = ClassWithListOfList()

        graph = create_graph(
            ListItemObserver(notify=False, optional=False),
            ListItemObserver(notify=True, optional=False),
        )
        handler = mock.Mock()
        call_add_or_remove_notifiers(
            object=instance.list_of_list,
            graph=graph,
            handler=handler,
        )

        # when
        instance.list_of_list.append([])

        # then
        # the first ListItemObserver has notify=False
        self.assertEqual(handler.call_count, 0)

        # but the second ListItemObserver is given to the nested list
        nested_list = instance.list_of_list[0]

        # when
        nested_list.append(1)

        # then
        ((event, ), _), = handler.call_args_list
        self.assertIs(event.object, nested_list)
        self.assertEqual(event.added, [1])
        self.assertEqual(event.removed, [])
        self.assertEqual(event.index, 0)
        handler.reset_mock()

        # when
        # the list is removed, it is not observed
        instance.list_of_list.pop()
        nested_list.append(1)

        # then
        self.assertEqual(handler.call_count, 0)
예제 #22
0
    def test_notify_custom_trait_dict_change(self):
        # Test using DictItemObserver for changes on a subclass of TraitDict
        # that isn't TraitDictObject
        instance = ClassWithDict(custom_trait_dict=CustomTraitDict())
        graph = create_graph(create_observer(notify=True), )
        handler = mock.Mock()
        call_add_or_remove_notifiers(
            object=instance.custom_trait_dict,
            graph=graph,
            handler=handler,
        )

        # when
        instance.custom_trait_dict.update({"1": 1})

        # then
        ((event, ), _), = handler.call_args_list
        self.assertEqual(event.added, {"1": 1})
        self.assertEqual(event.removed, {})
예제 #23
0
    def test_trait_added_filtered_matched(self):

        instance = DummyParent()
        integer_observer = create_observer(
            filter=lambda name, trait: type(trait.trait_type) is Int,
            notify=True,
        )
        handler = mock.Mock()
        call_add_or_remove_notifiers(
            object=instance,
            graph=create_graph(integer_observer),
            handler=handler,
        )

        # when
        instance.add_trait("another_number", Int())
        instance.another_number += 1

        # then
        self.assertEqual(handler.call_count, 1)
    def test_notifier_list_change(self):

        instance = ClassWithList(values=[])
        graph = create_graph(ListItemObserver(notify=True, optional=False), )
        handler = mock.Mock()
        call_add_or_remove_notifiers(
            object=instance.values,
            graph=graph,
            handler=handler,
        )

        # when
        instance.values.append(1)

        # then
        ((event, ), _), = handler.call_args_list
        self.assertIs(event.object, instance.values)
        self.assertEqual(event.added, [1])
        self.assertEqual(event.removed, [])
        self.assertEqual(event.index, 0)
예제 #25
0
    def test_remove_trait_then_add_trait_again(self):
        # Test a scenario where a trait exists when the observer is hooked,
        # but then the trait is removed, and then added back again, the
        # observer is gone, because the CTrait is gone and the trait_added
        # event is not fired for something already defined on the class.

        # given
        # the trait exists, we can set optional to false.
        graph = create_graph(
            create_observer(name="value1", notify=True, optional=False),
        )
        handler = mock.Mock()
        foo = ClassWithTwoValue()
        call_add_or_remove_notifiers(
            object=foo, graph=graph, handler=handler, remove=False)

        # sanity check, the handler is called when the trait changes.
        foo.value1 += 1
        handler.assert_called_once()
        handler.reset_mock()

        # when
        # remove the trait
        foo.remove_trait("value1")

        # then
        # the handler is gone with the instance trait.
        foo.value1 += 1
        handler.assert_not_called()

        # when
        # Add the trait back...
        foo.add_trait("value1", Int())

        # then
        # won't bring the handler back, because the 'value1' is defined as a
        # class trait, trait_added is not fired when it is added.
        foo.value1 += 1
        handler.assert_not_called()
예제 #26
0
    def test_observe_respond_to_trait_added(self):
        graph = create_graph(
            create_observer(name="value", notify=True, optional=True),
        )
        handler = mock.Mock()
        foo = ClassWithInstance()

        # when
        # does not complain because optional is set to true
        call_add_or_remove_notifiers(object=foo, graph=graph, handler=handler)

        # when
        foo.add_trait("value", Int())

        # then
        self.assertEqual(handler.call_count, 0)

        # when
        foo.value += 1

        # then
        self.assertEqual(handler.call_count, 1)
예제 #27
0
    def test_maintainer_trait_added(self):
        # Test the maintainer is added for the trait_added event.
        instance = DummyHasTraitsClass()
        notifier = DummyNotifier()
        maintainer = DummyNotifier()
        graph = create_graph(
            self.observer,
            DummyObserver(
                notify=True,
                notifier=notifier,
                maintainer=maintainer,
            ),
            DummyObserver(),  # to get maintainer in
        )
        call_add_or_remove_notifiers(
            object=instance,
            handler=instance.dummy_method,
            target=instance,
            graph=graph,
            remove=False,
        )

        # when
        instance.add_trait("good_name", Str())

        # then
        # the maintainer will have added a notifier because notify flag
        # is set to true on the single observer being maintained.
        notifiers = instance._trait("good_name", 2)._notifiers(True)
        self.assertIn(notifier, notifiers)
        self.assertIn(maintainer, notifiers)

        # when
        instance.add_trait("bad_name", Str())

        # then
        notifiers = instance._trait("bad_name", 2)._notifiers(True)
        self.assertNotIn(notifier, notifiers)
        self.assertNotIn(maintainer, notifiers)
    def test_notifier_custom_trait_list_change(self):
        # Test compatibility with any extension of TraitList, not just
        # TraitListObject
        instance = ClassWithList()
        instance.custom_trait_list = CustomTraitList()
        graph = create_graph(ListItemObserver(notify=True, optional=False), )
        handler = mock.Mock()
        call_add_or_remove_notifiers(
            object=instance.custom_trait_list,
            graph=graph,
            handler=handler,
        )

        # when
        instance.custom_trait_list.append(1)

        # then
        ((event, ), _), = handler.call_args_list
        self.assertIs(event.object, instance.custom_trait_list)
        self.assertEqual(event.added, [1])
        self.assertEqual(event.removed, [])
        self.assertEqual(event.index, 0)
예제 #29
0
    def test_trait_added_match_func_correct(self):
        # Test the match function supplied to TraitAddedObserver is consistent
        # with the filter.
        instance = DummyParent()
        integer_observer = create_observer(
            filter=lambda name, trait: type(trait.trait_type) is Int,
            notify=True,
        )
        handler = mock.Mock()
        call_add_or_remove_notifiers(
            object=instance,
            graph=create_graph(integer_observer),
            handler=handler,
        )

        # when
        # This trait does not satisfy the filter
        instance.add_trait("another_number", Float())
        instance.another_number += 1

        # then
        self.assertEqual(handler.call_count, 0)
    def test_maintain_notifier(self):
        # Test maintaining downstream notifier

        class ChildObserver(DummyObserver):

            def iter_observables(self, object):
                yield object

        instance = ClassWithSet()
        instance.values = set()

        notifier = DummyNotifier()
        child_observer = ChildObserver(notifier=notifier)
        graph = create_graph(
            create_observer(notify=False, optional=False),
            child_observer,
        )

        handler = mock.Mock()
        call_add_or_remove_notifiers(
            object=instance.values,
            graph=graph,
            handler=handler,
        )

        # when
        observable = DummyObservable()
        instance.values.add(observable)

        # then
        self.assertEqual(observable.notifiers, [notifier])

        # when
        instance.values.remove(observable)

        # then
        self.assertEqual(observable.notifiers, [])