prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN 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
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `bokeh/util/callback_manager.py`
  Content:
  ```
  1 ''' Provides ``PropertyCallbackManager`` and ``EventCallbackManager``
  2 mixin classes for adding ``on_change`` and ``on_event`` callback
  3 interfaces to classes.
  4 '''
  5 from __future__ import absolute_import
  6 
  7 from ..events import Event
  8 from ..util.future import get_param_info, format_signature, signature
  9 
  10 def _check_callback(callback, fargs, what="Callback functions"):
  11     '''Bokeh-internal function to check callback signature'''
  12     sig = signature(callback)
  13     formatted_args = format_signature(sig)
  14     error_msg = what + " must have signature func(%s), got func%s"
  15 
  16     all_names, default_values = get_param_info(sig)
  17 
  18     if len(all_names) - len(default_values) != len(fargs):
  19         raise ValueError(error_msg % (", ".join(fargs), formatted_args))
  20 
  21 class EventCallbackManager(object):
  22     ''' A mixin class to provide an interface for registering and
  23     triggering event callbacks on the Python side.
  24 
  25     '''
  26     def __init__(self, *args, **kw):
  27         super(EventCallbackManager, self).__init__(*args, **kw)
  28         self._event_callbacks = dict()
  29 
  30     def on_event(self, event, *callbacks):
  31         if not isinstance(event, str) and issubclass(event, Event):
  32             event = event.event_name
  33 
  34         for callback in callbacks:
  35             _check_callback(callback, ('event',), what='Event callback')
  36 
  37         if event not in self._event_callbacks:
  38             self._event_callbacks[event] = [cb for cb in callbacks]
  39         else:
  40             self._event_callbacks[event].extend(callbacks)
  41 
  42         if event not in self.subscribed_events:
  43             self.subscribed_events.append(event)
  44 
  45     def _trigger_event(self, event):
  46         for callback in self._event_callbacks.get(event.event_name,[]):
  47             if event._model_id is not None and self._id == event._model_id:
  48                 callback(event)
  49 
  50     def _update_event_callbacks(self):
  51         if self.document is None:
  52             return
  53 
  54         for key in self._event_callbacks:
  55             self.document._subscribed_models[key].add(self)
  56 
  57 
  58 class PropertyCallbackManager(object):
  59     ''' A mixin class to provide an interface for registering and
  60     triggering callbacks.
  61 
  62     '''
  63 
  64     def __init__(self, *args, **kw):
  65         super(PropertyCallbackManager, self).__init__(*args, **kw)
  66         self._callbacks = dict()
  67 
  68     def on_change(self, attr, *callbacks):
  69         ''' Add a callback on this object to trigger when ``attr`` changes.
  70 
  71         Args:
  72             attr (str) : an attribute name on this object
  73             callback (callable) : a callback function to register
  74 
  75         Returns:
  76             None
  77 
  78         '''
  79         if len(callbacks) == 0:
  80             raise ValueError("on_change takes an attribute name and one or more callbacks, got only one parameter")
  81 
  82         _callbacks = self._callbacks.setdefault(attr, [])
  83         for callback in callbacks:
  84 
  85             if callback in _callbacks:
  86                 continue
  87 
  88             _check_callback(callback, ('attr', 'old', 'new'))
  89 
  90             _callbacks.append(callback)
  91 
  92     def remove_on_change(self, attr, *callbacks):
  93         ''' Remove a callback from this object '''
  94         if len(callbacks) == 0:
  95             raise ValueError("remove_on_change takes an attribute name and one or more callbacks, got only one parameter")
  96         _callbacks = self._callbacks.setdefault(attr, [])
  97         for callback in callbacks:
  98             _callbacks.remove(callback)
  99 
  100     def trigger(self, attr, old, new, hint=None, setter=None):
  101         ''' Trigger callbacks for ``attr`` on this object.
  102 
  103         Args:
  104             attr (str) :
  105             old (object) :
  106             new (object) :
  107 
  108         Returns:
  109             None
  110 
  111         '''
  112         def invoke():
  113             callbacks = self._callbacks.get(attr)
  114             if callbacks:
  115                 for callback in callbacks:
  116                     callback(attr, old, new)
  117         if hasattr(self, '_document') and self._document is not None:
  118             self._document._notify_change(self, attr, old, new, hint, setter, invoke)
  119         else:
  120             invoke()
  121
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: 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 @@
           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:
  
verification_info: {"golden_diff": "diff --git a/bokeh/util/callback_manager.py b/bokeh/util/callback_manager.py\n--- a/bokeh/util/callback_manager.py\n+++ b/bokeh/util/callback_manager.py\n@@ -4,6 +4,8 @@\n '''\n from __future__ import absolute_import\n \n+from six import string_types\n+\n from ..events import Event\n from ..util.future import get_param_info, format_signature, signature\n \n@@ -28,7 +30,7 @@\n         self._event_callbacks = dict()\n \n     def on_event(self, event, *callbacks):\n-        if not isinstance(event, str) and issubclass(event, Event):\n+        if not isinstance(event, string_types) and issubclass(event, Event):\n             event = event.event_name\n \n         for callback in callbacks:\n", "issue": "Using on_event with string names in Python2.7\nAs discovered in #7893, trying to set `on_event` with a string (i.e. `\"tap\"` instead of `events.Tap`) currently fails with:\r\n\r\n``` python\r\n=================================== FAILURES ===================================\r\n Test_standalone_docs_json_and_render_items.test_log_warning_if_python_event_callback \r\nself = <bokeh.embed.tests.test_util.Test_standalone_docs_json_and_render_items object at 0x7f6f7a9a5950>\r\ncaplog = <_pytest.logging.LogCaptureFixture object at 0x7f6f7a9a57d0>\r\n    def test_log_warning_if_python_event_callback(self, caplog):\r\n        d = Document()\r\n        m1 = EmbedTestUtilModel()\r\n        c1 = _GoodEventCallback()\r\n        d.add_root(m1)\r\n    \r\n>       m1.on_event('tap', c1)\r\nbokeh/embed/tests/test_util.py:155: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\nself = EmbedTestUtilModel(id='b0037dfa-87fa-48d7-82ea-df2b14d2aff4', ...)\r\nevent = 'tap'\r\ncallbacks = (<bokeh.embed.tests.test_util._GoodEventCallback object at 0x7f6f7a9a5d90>,)\r\n    def on_event(self, event, *callbacks):\r\n>       if not isinstance(event, str) and issubclass(event, Event):\r\nE       TypeError: issubclass() arg 1 must be a class\r\nbokeh/util/callback_manager.py:31: TypeError\r\n```\r\n\r\nWe should:\r\n- [ ] Add tests that check setting `on_event` with a string\r\n- [ ] Fix `bokeh/util/callback_manager.py` to accept strings in Python2.7\n", "before_files": [{"content": "''' Provides ``PropertyCallbackManager`` and ``EventCallbackManager``\nmixin classes for adding ``on_change`` and ``on_event`` callback\ninterfaces to classes.\n'''\nfrom __future__ import absolute_import\n\nfrom ..events import Event\nfrom ..util.future import get_param_info, format_signature, signature\n\ndef _check_callback(callback, fargs, what=\"Callback functions\"):\n    '''Bokeh-internal function to check callback signature'''\n    sig = signature(callback)\n    formatted_args = format_signature(sig)\n    error_msg = what + \" must have signature func(%s), got func%s\"\n\n    all_names, default_values = get_param_info(sig)\n\n    if len(all_names) - len(default_values) != len(fargs):\n        raise ValueError(error_msg % (\", \".join(fargs), formatted_args))\n\nclass EventCallbackManager(object):\n    ''' A mixin class to provide an interface for registering and\n    triggering event callbacks on the Python side.\n\n    '''\n    def __init__(self, *args, **kw):\n        super(EventCallbackManager, self).__init__(*args, **kw)\n        self._event_callbacks = dict()\n\n    def on_event(self, event, *callbacks):\n        if not isinstance(event, str) and issubclass(event, Event):\n            event = event.event_name\n\n        for callback in callbacks:\n            _check_callback(callback, ('event',), what='Event callback')\n\n        if event not in self._event_callbacks:\n            self._event_callbacks[event] = [cb for cb in callbacks]\n        else:\n            self._event_callbacks[event].extend(callbacks)\n\n        if event not in self.subscribed_events:\n            self.subscribed_events.append(event)\n\n    def _trigger_event(self, event):\n        for callback in self._event_callbacks.get(event.event_name,[]):\n            if event._model_id is not None and self._id == event._model_id:\n                callback(event)\n\n    def _update_event_callbacks(self):\n        if self.document is None:\n            return\n\n        for key in self._event_callbacks:\n            self.document._subscribed_models[key].add(self)\n\n\nclass PropertyCallbackManager(object):\n    ''' A mixin class to provide an interface for registering and\n    triggering callbacks.\n\n    '''\n\n    def __init__(self, *args, **kw):\n        super(PropertyCallbackManager, self).__init__(*args, **kw)\n        self._callbacks = dict()\n\n    def on_change(self, attr, *callbacks):\n        ''' Add a callback on this object to trigger when ``attr`` changes.\n\n        Args:\n            attr (str) : an attribute name on this object\n            callback (callable) : a callback function to register\n\n        Returns:\n            None\n\n        '''\n        if len(callbacks) == 0:\n            raise ValueError(\"on_change takes an attribute name and one or more callbacks, got only one parameter\")\n\n        _callbacks = self._callbacks.setdefault(attr, [])\n        for callback in callbacks:\n\n            if callback in _callbacks:\n                continue\n\n            _check_callback(callback, ('attr', 'old', 'new'))\n\n            _callbacks.append(callback)\n\n    def remove_on_change(self, attr, *callbacks):\n        ''' Remove a callback from this object '''\n        if len(callbacks) == 0:\n            raise ValueError(\"remove_on_change takes an attribute name and one or more callbacks, got only one parameter\")\n        _callbacks = self._callbacks.setdefault(attr, [])\n        for callback in callbacks:\n            _callbacks.remove(callback)\n\n    def trigger(self, attr, old, new, hint=None, setter=None):\n        ''' Trigger callbacks for ``attr`` on this object.\n\n        Args:\n            attr (str) :\n            old (object) :\n            new (object) :\n\n        Returns:\n            None\n\n        '''\n        def invoke():\n            callbacks = self._callbacks.get(attr)\n            if callbacks:\n                for callback in callbacks:\n                    callback(attr, old, new)\n        if hasattr(self, '_document') and self._document is not None:\n            self._document._notify_change(self, attr, old, new, hint, setter, invoke)\n        else:\n            invoke()\n", "path": "bokeh/util/callback_manager.py"}], "after_files": [{"content": "''' Provides ``PropertyCallbackManager`` and ``EventCallbackManager``\nmixin classes for adding ``on_change`` and ``on_event`` callback\ninterfaces to classes.\n'''\nfrom __future__ import absolute_import\n\nfrom six import string_types\n\nfrom ..events import Event\nfrom ..util.future import get_param_info, format_signature, signature\n\ndef _check_callback(callback, fargs, what=\"Callback functions\"):\n    '''Bokeh-internal function to check callback signature'''\n    sig = signature(callback)\n    formatted_args = format_signature(sig)\n    error_msg = what + \" must have signature func(%s), got func%s\"\n\n    all_names, default_values = get_param_info(sig)\n\n    if len(all_names) - len(default_values) != len(fargs):\n        raise ValueError(error_msg % (\", \".join(fargs), formatted_args))\n\nclass EventCallbackManager(object):\n    ''' A mixin class to provide an interface for registering and\n    triggering event callbacks on the Python side.\n\n    '''\n    def __init__(self, *args, **kw):\n        super(EventCallbackManager, self).__init__(*args, **kw)\n        self._event_callbacks = dict()\n\n    def on_event(self, event, *callbacks):\n        if not isinstance(event, string_types) and issubclass(event, Event):\n            event = event.event_name\n\n        for callback in callbacks:\n            _check_callback(callback, ('event',), what='Event callback')\n\n        if event not in self._event_callbacks:\n            self._event_callbacks[event] = [cb for cb in callbacks]\n        else:\n            self._event_callbacks[event].extend(callbacks)\n\n        if event not in self.subscribed_events:\n            self.subscribed_events.append(event)\n\n    def _trigger_event(self, event):\n        for callback in self._event_callbacks.get(event.event_name,[]):\n            if event._model_id is not None and self._id == event._model_id:\n                callback(event)\n\n    def _update_event_callbacks(self):\n        if self.document is None:\n            return\n\n        for key in self._event_callbacks:\n            self.document._subscribed_models[key].add(self)\n\n\nclass PropertyCallbackManager(object):\n    ''' A mixin class to provide an interface for registering and\n    triggering callbacks.\n\n    '''\n\n    def __init__(self, *args, **kw):\n        super(PropertyCallbackManager, self).__init__(*args, **kw)\n        self._callbacks = dict()\n\n    def on_change(self, attr, *callbacks):\n        ''' Add a callback on this object to trigger when ``attr`` changes.\n\n        Args:\n            attr (str) : an attribute name on this object\n            callback (callable) : a callback function to register\n\n        Returns:\n            None\n\n        '''\n        if len(callbacks) == 0:\n            raise ValueError(\"on_change takes an attribute name and one or more callbacks, got only one parameter\")\n\n        _callbacks = self._callbacks.setdefault(attr, [])\n        for callback in callbacks:\n\n            if callback in _callbacks:\n                continue\n\n            _check_callback(callback, ('attr', 'old', 'new'))\n\n            _callbacks.append(callback)\n\n    def remove_on_change(self, attr, *callbacks):\n        ''' Remove a callback from this object '''\n        if len(callbacks) == 0:\n            raise ValueError(\"remove_on_change takes an attribute name and one or more callbacks, got only one parameter\")\n        _callbacks = self._callbacks.setdefault(attr, [])\n        for callback in callbacks:\n            _callbacks.remove(callback)\n\n    def trigger(self, attr, old, new, hint=None, setter=None):\n        ''' Trigger callbacks for ``attr`` on this object.\n\n        Args:\n            attr (str) :\n            old (object) :\n            new (object) :\n\n        Returns:\n            None\n\n        '''\n        def invoke():\n            callbacks = self._callbacks.get(attr)\n            if callbacks:\n                for callback in callbacks:\n                    callback(attr, old, new)\n        if hasattr(self, '_document') and self._document is not None:\n            self._document._notify_change(self, attr, old, new, hint, setter, invoke)\n        else:\n            invoke()\n", "path": "bokeh/util/callback_manager.py"}]}