Repository: bokeh/bokeh

Issue:
Using on_event with string names in Python2.7
As discovered in #7893, trying to set `on_event` with a string (i.e. `"tap"` instead of `events.Tap`) currently fails with:

``` python
=================================== FAILURES ===================================
 Test_standalone_docs_json_and_render_items.test_log_warning_if_python_event_callback 
self = <bokeh.embed.tests.test_util.Test_standalone_docs_json_and_render_items object at 0x7f6f7a9a5950>
caplog = <_pytest.logging.LogCaptureFixture object at 0x7f6f7a9a57d0>
    def test_log_warning_if_python_event_callback(self, caplog):
        d = Document()
        m1 = EmbedTestUtilModel()
        c1 = _GoodEventCallback()
        d.add_root(m1)
    
>       m1.on_event('tap', c1)
bokeh/embed/tests/test_util.py:155: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
self = EmbedTestUtilModel(id='b0037dfa-87fa-48d7-82ea-df2b14d2aff4', ...)
event = 'tap'
callbacks = (<bokeh.embed.tests.test_util._GoodEventCallback object at 0x7f6f7a9a5d90>,)
    def on_event(self, event, *callbacks):
>       if not isinstance(event, str) and issubclass(event, Event):
E       TypeError: issubclass() arg 1 must be a class
bokeh/util/callback_manager.py:31: TypeError
```

We should:
- [ ] Add tests that check setting `on_event` with a string
- [ ] Fix `bokeh/util/callback_manager.py` to accept strings in Python2.7


Discussion:
Hrmm well now I am confused, there are already lots of tests in `bokeh/util/tests/test_callback_manager.py` that already use string event names and also pass on py 2.7 currently
well it's unicode if the test is in the `test_util.py` file, checking for `six.string_types` fixes

Resolving patch:
diff --git a/bokeh/util/callback_manager.py b/bokeh/util/callback_manager.py
--- a/bokeh/util/callback_manager.py
+++ b/bokeh/util/callback_manager.py
@@ -4,6 +4,8 @@
 '''
 from __future__ import absolute_import
 
+from six import string_types
+
 from ..events import Event
 from ..util.future import get_param_info, format_signature, signature
 
@@ -28,7 +30,7 @@ def __init__(self, *args, **kw):
         self._event_callbacks = dict()
 
     def on_event(self, event, *callbacks):
-        if not isinstance(event, str) and issubclass(event, Event):
+        if not isinstance(event, string_types) and issubclass(event, Event):
             event = event.event_name
 
         for callback in callbacks:


Test patch:
diff --git a/bokeh/util/tests/test_callback_manager.py b/bokeh/util/tests/test_callback_manager.py
--- a/bokeh/util/tests/test_callback_manager.py
+++ b/bokeh/util/tests/test_callback_manager.py
@@ -44,169 +44,169 @@ def _partially_good_property(w, x, y, z):
 def _just_fine_property(w, x, y, z='default'):
     pass
 
-def test_property_creation():
-    m = cbm.PropertyCallbackManager()
-    assert len(m._callbacks) == 0
-
-def test_property_on_change_good_method():
-    m = cbm.PropertyCallbackManager()
-    good = _GoodPropertyCallback()
-    m.on_change('foo', good.method)
-    assert len(m._callbacks) == 1
-    assert m._callbacks['foo'] == [good.method]
-
-def test_property_on_change_good_partial_function():
-    m = cbm.PropertyCallbackManager()
-    p = partial(_partially_good_property, 'foo')
-    m.on_change('bar', p)
-    assert len(m._callbacks) == 1
-
-
-def test_property_on_change_good_partial_method():
-    m = cbm.PropertyCallbackManager()
-    good = _GoodPropertyCallback()
-    p = partial(good.partially_good, 'foo')
-    m.on_change('bar', p)
-    assert len(m._callbacks) == 1
-
-def test_property_on_change_good_extra_kwargs_function():
-    m = cbm.PropertyCallbackManager()
-    m.on_change('bar', _just_fine_property)
-    assert len(m._callbacks) == 1
-
-def test_property_on_change_good_extra_kwargs_method():
-    m = cbm.PropertyCallbackManager()
-    good = _GoodPropertyCallback()
-    m.on_change('bar', good.just_fine)
-    assert len(m._callbacks) == 1
-
-def test_property_on_change_good_functor():
-    m = cbm.PropertyCallbackManager()
-    good = _GoodPropertyCallback()
-    m.on_change('foo', good)
-    assert len(m._callbacks) == 1
-    assert m._callbacks['foo'] == [good]
-
-def test_property_on_change_good_function():
-    m = cbm.PropertyCallbackManager()
-    m.on_change('foo', _good_property)
-    assert len(m._callbacks) == 1
-    assert m._callbacks['foo'] == [_good_property]
-
-def test_property_on_change_good_lambda():
-    m = cbm.PropertyCallbackManager()
-    good = lambda x, y, z: x
-    m.on_change('foo', good)
-    assert len(m._callbacks) == 1
-    assert m._callbacks['foo'] == [good]
-
-def test_property_on_change_good_closure():
-    def good(x, y, z):
-        pass
-    m = cbm.PropertyCallbackManager()
-    m.on_change('foo', good)
-    assert len(m._callbacks) == 1
-    assert len(m._callbacks['foo']) == 1
-
-def test_property_on_change_bad_method():
-    m = cbm.PropertyCallbackManager()
-    bad = _BadPropertyCallback()
-    with pytest.raises(ValueError):
-        m.on_change('foo', bad.method)
-    assert len(m._callbacks) == 1
-    assert len(m._callbacks['foo']) == 0
-
-def test_property_on_change_bad_functor():
-    m = cbm.PropertyCallbackManager()
-    bad = _BadPropertyCallback()
-    with pytest.raises(ValueError):
-        m.on_change('foo', bad)
-    assert len(m._callbacks) == 1
-    assert len(m._callbacks['foo']) == 0
-
-def test_property_on_change_bad_function():
-    m = cbm.PropertyCallbackManager()
-    with pytest.raises(ValueError):
-        m.on_change('foo', _bad_property)
-    assert len(m._callbacks) == 1
-    assert len(m._callbacks['foo']) == 0
-
-def test_property_on_change_bad_lambda():
-    m = cbm.PropertyCallbackManager()
-    with pytest.raises(ValueError):
-        m.on_change('foo', lambda x, y: x)
-    assert len(m._callbacks) == 1
-    assert len(m._callbacks['foo']) == 0
-
-def test_property_on_change_bad_closure():
-    def bad(x, y):
-        pass
-    m = cbm.PropertyCallbackManager()
-    with pytest.raises(ValueError):
-        m.on_change('foo', bad)
-    assert len(m._callbacks) == 1
-    assert len(m._callbacks['foo']) == 0
-
-def test_property_on_change_same_attr_twice_multiple_calls():
-    def good1(x, y, z):
-        pass
+class TestPropertyCallbackManager(object):
+
+    def test_creation(self):
+        m = cbm.PropertyCallbackManager()
+        assert len(m._callbacks) == 0
+
+    def test_on_change_good_method(self):
+        m = cbm.PropertyCallbackManager()
+        good = _GoodPropertyCallback()
+        m.on_change('foo', good.method)
+        assert len(m._callbacks) == 1
+        assert m._callbacks['foo'] == [good.method]
+
+    def test_on_change_good_partial_function(self):
+        m = cbm.PropertyCallbackManager()
+        p = partial(_partially_good_property, 'foo')
+        m.on_change('bar', p)
+        assert len(m._callbacks) == 1
+
+    def test_on_change_good_partial_method(self):
+        m = cbm.PropertyCallbackManager()
+        good = _GoodPropertyCallback()
+        p = partial(good.partially_good, 'foo')
+        m.on_change('bar', p)
+        assert len(m._callbacks) == 1
+
+    def test_on_change_good_extra_kwargs_function(self):
+        m = cbm.PropertyCallbackManager()
+        m.on_change('bar', _just_fine_property)
+        assert len(m._callbacks) == 1
+
+    def test_on_change_good_extra_kwargs_method(self):
+        m = cbm.PropertyCallbackManager()
+        good = _GoodPropertyCallback()
+        m.on_change('bar', good.just_fine)
+        assert len(m._callbacks) == 1
+
+    def test_on_change_good_functor(self):
+        m = cbm.PropertyCallbackManager()
+        good = _GoodPropertyCallback()
+        m.on_change('foo', good)
+        assert len(m._callbacks) == 1
+        assert m._callbacks['foo'] == [good]
+
+    def test_on_change_good_function(self):
+        m = cbm.PropertyCallbackManager()
+        m.on_change('foo', _good_property)
+        assert len(m._callbacks) == 1
+        assert m._callbacks['foo'] == [_good_property]
+
+    def test_on_change_good_lambda(self):
+        m = cbm.PropertyCallbackManager()
+        good = lambda x, y, z: x
+        m.on_change('foo', good)
+        assert len(m._callbacks) == 1
+        assert m._callbacks['foo'] == [good]
+
+    def test_on_change_good_closure(self):
+        def good(x, y, z):
+            pass
+        m = cbm.PropertyCallbackManager()
+        m.on_change('foo', good)
+        assert len(m._callbacks) == 1
+        assert len(m._callbacks['foo']) == 1
+
+    def test_on_change_bad_method(self):
+        m = cbm.PropertyCallbackManager()
+        bad = _BadPropertyCallback()
+        with pytest.raises(ValueError):
+            m.on_change('foo', bad.method)
+        assert len(m._callbacks) == 1
+        assert len(m._callbacks['foo']) == 0
+
+    def test_on_change_bad_functor(self):
+        m = cbm.PropertyCallbackManager()
+        bad = _BadPropertyCallback()
+        with pytest.raises(ValueError):
+            m.on_change('foo', bad)
+        assert len(m._callbacks) == 1
+        assert len(m._callbacks['foo']) == 0
+
+    def test_on_change_bad_function(self):
+        m = cbm.PropertyCallbackManager()
+        with pytest.raises(ValueError):
+            m.on_change('foo', _bad_property)
+        assert len(m._callbacks) == 1
+        assert len(m._callbacks['foo']) == 0
+
+    def test_on_change_bad_lambda(self):
+        m = cbm.PropertyCallbackManager()
+        with pytest.raises(ValueError):
+            m.on_change('foo', lambda x, y: x)
+        assert len(m._callbacks) == 1
+        assert len(m._callbacks['foo']) == 0
+
+    def test_on_change_bad_closure(self):
+        def bad(x, y):
+            pass
+        m = cbm.PropertyCallbackManager()
+        with pytest.raises(ValueError):
+            m.on_change('foo', bad)
+        assert len(m._callbacks) == 1
+        assert len(m._callbacks['foo']) == 0
+
+    def test_on_change_same_attr_twice_multiple_calls(self):
+        def good1(x, y, z):
+            pass
+
+        def good2(x, y, z):
+            pass
+        m1 = cbm.PropertyCallbackManager()
+        m1.on_change('foo', good1)
+        m1.on_change('foo', good2)
+        assert len(m1._callbacks) == 1
+        assert m1._callbacks['foo'] == [good1, good2]
+
+    def test_on_change_same_attr_twice_one_call(self):
+        def good1(x, y, z):
+            pass
+
+        def good2(x, y, z):
+            pass
+        m2 = cbm.PropertyCallbackManager()
+        m2.on_change('foo', good1, good2)
+        assert len(m2._callbacks) == 1
+        assert m2._callbacks['foo'] == [good1, good2]
+
+    def test_on_change_different_attrs(self):
+        def good1(x, y, z):
+            pass
+
+        def good2(x, y, z):
+            pass
+        m1 = cbm.PropertyCallbackManager()
+        m1.on_change('foo', good1)
+        m1.on_change('bar', good2)
+        assert len(m1._callbacks) == 2
+        assert m1._callbacks['foo'] == [good1]
+        assert m1._callbacks['bar'] == [good2]
+
+    def test_trigger(self):
+        m = cbm.PropertyCallbackManager()
+        good = _GoodPropertyCallback()
+        m.on_change('foo', good.method)
+        m.trigger('foo', 42, 43)
+        assert good.last_name == 'foo'
+        assert good.last_old == 42
+        assert good.last_new == 43
+
+    def test_trigger_with_two_callbacks(self):
+        m = cbm.PropertyCallbackManager()
+        good1 = _GoodPropertyCallback()
+        good2 = _GoodPropertyCallback()
+        m.on_change('foo', good1.method)
+        m.on_change('foo', good2.method)
+        m.trigger('foo', 42, 43)
+        assert good1.last_name == 'foo'
+        assert good1.last_old == 42
+        assert good1.last_new == 43
+        assert good2.last_name == 'foo'
+        assert good2.last_old == 42
+        assert good2.last_new == 43
 
-    def good2(x, y, z):
-        pass
-    m1 = cbm.PropertyCallbackManager()
-    m1.on_change('foo', good1)
-    m1.on_change('foo', good2)
-    assert len(m1._callbacks) == 1
-    assert m1._callbacks['foo'] == [good1, good2]
-
-def test_property_on_change_same_attr_twice_one_call():
-    def good1(x, y, z):
-        pass
-
-    def good2(x, y, z):
-        pass
-    m2 = cbm.PropertyCallbackManager()
-    m2.on_change('foo', good1, good2)
-    assert len(m2._callbacks) == 1
-    assert m2._callbacks['foo'] == [good1, good2]
-
-def test_property_on_change_different_attrs():
-    def good1(x, y, z):
-        pass
-
-    def good2(x, y, z):
-        pass
-    m1 = cbm.PropertyCallbackManager()
-    m1.on_change('foo', good1)
-    m1.on_change('bar', good2)
-    assert len(m1._callbacks) == 2
-    assert m1._callbacks['foo'] == [good1]
-    assert m1._callbacks['bar'] == [good2]
-
-def test_property_trigger():
-    m = cbm.PropertyCallbackManager()
-    good = _GoodPropertyCallback()
-    m.on_change('foo', good.method)
-    m.trigger('foo', 42, 43)
-    assert good.last_name == 'foo'
-    assert good.last_old == 42
-    assert good.last_new == 43
-
-def test_property_trigger_with_two_callbacks():
-    m = cbm.PropertyCallbackManager()
-    good1 = _GoodPropertyCallback()
-    good2 = _GoodPropertyCallback()
-    m.on_change('foo', good1.method)
-    m.on_change('foo', good2.method)
-    m.trigger('foo', 42, 43)
-    assert good1.last_name == 'foo'
-    assert good1.last_old == 42
-    assert good1.last_new == 43
-    assert good2.last_name == 'foo'
-    assert good2.last_old == 42
-    assert good2.last_new == 43
-
-# Tests for the EventCallbackManager
 
 class _GoodEventCallback(object):
 
@@ -241,125 +241,134 @@ def _partially_good_event(arg, event):
 def _partially_bad_event(event):
     pass
 
-def test_event_creation():
-    m = cbm.EventCallbackManager()
-    assert len(m._event_callbacks) == 0
-
-def test_event_on_change_good_method():
-    m = cbm.EventCallbackManager()
-    m.subscribed_events = []
-    good = _GoodEventCallback()
-    m.on_event('foo', good.method)
-    assert len(m._event_callbacks) == 1
-    assert m._event_callbacks['foo'] == [good.method]
-
-def test_event_on_change_good_partial_function():
-    m = cbm.EventCallbackManager()
-    p = partial(_partially_good_event, 'foo')
-    m.subscribed_events = []
-    m.on_event('foo', p)
-    assert len(m._event_callbacks) == 1
-    assert m._event_callbacks['foo'] == [p]
-
-def test_event_on_change_bad_partial_function():
-    m = cbm.EventCallbackManager()
-    p = partial(_partially_bad_event, 'foo')
-    m.subscribed_events = []
-    with pytest.raises(ValueError):
+class TestEventCallbackManager(object):
+
+    def test_creation(self):
+        m = cbm.EventCallbackManager()
+        assert len(m._event_callbacks) == 0
+
+    def test_on_change_good_method(self):
+        m = cbm.EventCallbackManager()
+        m.subscribed_events = []
+        good = _GoodEventCallback()
+        m.on_event('foo', good.method)
+        assert len(m._event_callbacks) == 1
+        assert m._event_callbacks['foo'] == [good.method]
+
+    def test_on_change_good_partial_function(self):
+        m = cbm.EventCallbackManager()
+        p = partial(_partially_good_event, 'foo')
+        m.subscribed_events = []
         m.on_event('foo', p)
-    assert len(m._event_callbacks) == 0
-
-def test_event_on_change_good_partial_method():
-    m = cbm.EventCallbackManager()
-    m.subscribed_events = []
-    good = _GoodEventCallback()
-    p = partial(good.partially_good, 'foo')
-    m.on_event('foo', p)
-    assert len(m._event_callbacks) == 1
-
-def test_event_on_change_good_functor():
-    m = cbm.EventCallbackManager()
-    m.subscribed_events = []
-    good = _GoodEventCallback()
-    m.on_event('foo', good)
-    assert len(m._event_callbacks) == 1
-    assert m._event_callbacks['foo'] == [good]
-
-def test_event_on_change_good_function():
-    m = cbm.EventCallbackManager()
-    m.subscribed_events = []
-    m.on_event('foo', _good_event)
-    assert len(m._event_callbacks) == 1
-    assert m._event_callbacks['foo'] == [_good_event]
-
-def test_event_on_change_good_lambda():
-    m = cbm.EventCallbackManager()
-    m.subscribed_events = []
-    good = lambda event: event
-    m.on_event('foo', good)
-    assert len(m._event_callbacks) == 1
-    assert m._event_callbacks['foo'] == [good]
-
-def test_event_on_change_good_closure():
-    def good(event):
-        pass
-    m = cbm.EventCallbackManager()
-    m.subscribed_events = []
-    m.on_event('foo', good)
-    assert len(m._event_callbacks) == 1
-    assert len(m._event_callbacks['foo']) == 1
-
-def test_event_on_change_bad_method():
-    m = cbm.EventCallbackManager()
-    m.subscribed_events = []
-    bad = _BadEventCallback()
-    with pytest.raises(ValueError):
-        m.on_event('foo', bad.method)
-    assert len(m._event_callbacks) == 0
-
-def test_event_on_change_bad_functor():
-    m = cbm.EventCallbackManager()
-    m.subscribed_events = []
-    bad = _BadEventCallback()
-    with pytest.raises(ValueError):
-        m.on_event('foo', bad)
-    assert len(m._event_callbacks) == 0
-
-def test_event_on_change_bad_function():
-    m = cbm.EventCallbackManager()
-    m.subscribed_events = []
-    with pytest.raises(ValueError):
-        m.on_event('foo', _bad_event)
-    assert len(m._event_callbacks) == 0
-
-def test_event_on_change_bad_lambda():
-    m = cbm.EventCallbackManager()
-    m.subscribed_events = []
-    with pytest.raises(ValueError):
-        m.on_event('foo', lambda x, y: x)
-    assert len(m._event_callbacks) == 0
-
-def test_event_on_change_bad_closure():
-    def bad(event, y):
-        pass
-    m = cbm.EventCallbackManager()
-    m.subscribed_events = []
-    with pytest.raises(ValueError):
-        m.on_event('foo', bad)
-    assert len(m._event_callbacks) == 0
-
-def test_event_on_change_with_two_callbacks():
-    m = cbm.EventCallbackManager()
-    m.subscribed_events = []
-    good1 = _GoodEventCallback()
-    good2 = _GoodEventCallback()
-    m.on_event('foo', good1.method)
-    m.on_event('foo', good2.method)
-
-def test_event_on_change_with_two_callbacks_one_bad():
-    m = cbm.EventCallbackManager()
-    m.subscribed_events = []
-    good = _GoodEventCallback()
-    bad = _BadEventCallback()
-    with pytest.raises(ValueError):
-        m.on_event('foo', good.method, bad.method)
+        assert len(m._event_callbacks) == 1
+        assert m._event_callbacks['foo'] == [p]
+
+    def test_on_change_bad_partial_function(self):
+        m = cbm.EventCallbackManager()
+        p = partial(_partially_bad_event, 'foo')
+        m.subscribed_events = []
+        with pytest.raises(ValueError):
+            m.on_event('foo', p)
+        assert len(m._event_callbacks) == 0
+
+    def test_on_change_good_partial_method(self):
+        m = cbm.EventCallbackManager()
+        m.subscribed_events = []
+        good = _GoodEventCallback()
+        p = partial(good.partially_good, 'foo')
+        m.on_event('foo', p)
+        assert len(m._event_callbacks) == 1
+
+    def test_on_change_good_functor(self):
+        m = cbm.EventCallbackManager()
+        m.subscribed_events = []
+        good = _GoodEventCallback()
+        m.on_event('foo', good)
+        assert len(m._event_callbacks) == 1
+        assert m._event_callbacks['foo'] == [good]
+
+    def test_on_change_good_function(self):
+        m = cbm.EventCallbackManager()
+        m.subscribed_events = []
+        m.on_event('foo', _good_event)
+        assert len(m._event_callbacks) == 1
+        assert m._event_callbacks['foo'] == [_good_event]
+
+    def test_on_change_unicode_event_name(self):
+        m = cbm.EventCallbackManager()
+        m.subscribed_events = []
+        m.on_event(u'foo', _good_event)
+        assert len(m._event_callbacks) == 1
+        assert m._event_callbacks['foo'] == [_good_event]
+
+    def test_on_change_good_lambda(self):
+        m = cbm.EventCallbackManager()
+        m.subscribed_events = []
+        good = lambda event: event
+        m.on_event('foo', good)
+        assert len(m._event_callbacks) == 1
+        assert m._event_callbacks['foo'] == [good]
+
+    def test_on_change_good_closure(self):
+        def good(event):
+            pass
+        m = cbm.EventCallbackManager()
+        m.subscribed_events = []
+        m.on_event('foo', good)
+        assert len(m._event_callbacks) == 1
+        assert len(m._event_callbacks['foo']) == 1
+
+    def test_on_change_bad_method(self):
+        m = cbm.EventCallbackManager()
+        m.subscribed_events = []
+        bad = _BadEventCallback()
+        with pytest.raises(ValueError):
+            m.on_event('foo', bad.method)
+        assert len(m._event_callbacks) == 0
+
+    def test_on_change_bad_functor(self):
+        m = cbm.EventCallbackManager()
+        m.subscribed_events = []
+        bad = _BadEventCallback()
+        with pytest.raises(ValueError):
+            m.on_event('foo', bad)
+        assert len(m._event_callbacks) == 0
+
+    def test_on_change_bad_function(self):
+        m = cbm.EventCallbackManager()
+        m.subscribed_events = []
+        with pytest.raises(ValueError):
+            m.on_event('foo', _bad_event)
+        assert len(m._event_callbacks) == 0
+
+    def test_on_change_bad_lambda(self):
+        m = cbm.EventCallbackManager()
+        m.subscribed_events = []
+        with pytest.raises(ValueError):
+            m.on_event('foo', lambda x, y: x)
+        assert len(m._event_callbacks) == 0
+
+    def test_on_change_bad_closure(self):
+        def bad(event, y):
+            pass
+        m = cbm.EventCallbackManager()
+        m.subscribed_events = []
+        with pytest.raises(ValueError):
+            m.on_event('foo', bad)
+        assert len(m._event_callbacks) == 0
+
+    def test_on_change_with_two_callbacks(self):
+        m = cbm.EventCallbackManager()
+        m.subscribed_events = []
+        good1 = _GoodEventCallback()
+        good2 = _GoodEventCallback()
+        m.on_event('foo', good1.method)
+        m.on_event('foo', good2.method)
+
+    def test_on_change_with_two_callbacks_one_bad(self):
+        m = cbm.EventCallbackManager()
+        m.subscribed_events = []
+        good = _GoodEventCallback()
+        bad = _BadEventCallback()
+        with pytest.raises(ValueError):
+            m.on_event('foo', good.method, bad.method)