Repository: ipython/ipykernel

Issue:
'thread._local' object has no attribute 'hooks'
I just installed the newest versions of ipython and GraphLab and noticed this error. Here is the relevant code and the traceback.

    # sales is an SFrame
    graphlab.canvas.set_target('ipynb')
    sales.show(view="Scatter Plot", x="CrimeRate", y="HousePrice")

Traceback:
```


AttributeErrorTraceback (most recent call last)
<ipython-input-7-6f077ba0dabb> in <module>()
      1 graphlab.canvas.set_target('ipynb')
----> 2 sales.show(view="Scatter Plot", x="CrimeRate", y="HousePrice")

/usr/local/lib/python2.7/site-packages/graphlab/data_structures/sframe.pyc in show(self, columns, view, x, y)
   4950             __LOGGER__.warn("Column selection for SFrame.show is deprecated. To show only certain columns, use the sf[['column1', 'column2']] syntax or construct a new SFrame with the desired columns.")
   4951         from ..visualization.show import show
-> 4952         show(self, view=view, x=x, y=y)
   4953 
   4954     def pack_columns(self, columns=None, column_prefix=None, dtype=list,

/usr/local/lib/python2.7/site-packages/multipledispatch/dispatcher.pyc in __call__(self, *args, **kwargs)
    162             self._cache[types] = func
    163         try:
--> 164             return func(*args, **kwargs)
    165 
    166         except MDNotImplementedError:

/usr/local/lib/python2.7/site-packages/graphlab/canvas/glc_display_dispatch.pyc in show(obj, **kwargs)
      8     import graphlab.canvas.views.sframe
      9     graphlab.canvas.inspect.find_vars(obj)
---> 10     return graphlab.canvas.show(graphlab.canvas.views.sframe.SFrameView(obj, params=kwargs))
     11 
     12 

/usr/local/lib/python2.7/site-packages/graphlab/canvas/utils.pyc in show(variable)
    129         get_target().state.set_selected_variable(variable)
    130         variable.validate_js_component_name(variable.get_js_component())
--> 131     return get_target().show()
    132 
    133 def _get_id(ref):

/usr/local/lib/python2.7/site-packages/graphlab/canvas/target.pyc in show(self, variable)
    175             IPython.core.display.Javascript(
    176                 data=self.__makeJS(_to_json(data), view.get_js_file(), view.get_js_component()),
--> 177                 css=['//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.1.0/css/font-awesome.min.css', self.get_asset_url() + 'css/canvas.css']
    178             )
    179         )

/usr/local/lib/python2.7/site-packages/IPython/core/display.pyc in display_javascript(*objs, **kwargs)
    328         Metadata to be associated with the specific mimetype output.
    329     """
--> 330     _display_mimetype('application/javascript', objs, **kwargs)
    331 
    332 

/usr/local/lib/python2.7/site-packages/IPython/core/display.pyc in _display_mimetype(mimetype, objs, raw, metadata)
     74         # turn list of pngdata into list of { 'image/png': pngdata }
     75         objs = [ {mimetype: obj} for obj in objs ]
---> 76     display(*objs, raw=raw, metadata=metadata, include=[mimetype])
     77 
     78 #-----------------------------------------------------------------------------

/usr/local/lib/python2.7/site-packages/IPython/core/display.pyc in display(*objs, **kwargs)
    169                 # kwarg-specified metadata gets precedence
    170                 _merge(md_dict, metadata)
--> 171             publish_display_data(data=format_dict, metadata=md_dict)
    172 
    173 

/usr/local/lib/python2.7/site-packages/IPython/core/display.pyc in publish_display_data(data, metadata, source)
    119     InteractiveShell.instance().display_pub.publish(
    120         data=data,
--> 121         metadata=metadata,
    122     )
    123 

/usr/local/lib/python2.7/site-packages/ipykernel/zmqshell.pyc in publish(self, data, metadata, source)
    109         # message or None. If None is returned,
    110         # the message has been 'used' and we return.
--> 111         for hook in self.thread_local.hooks:
    112             msg = hook(msg)
    113             if msg is None:

AttributeError: 'thread._local' object has no attribute 'hooks'
```

Discussion:

Patch:
diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py
index 5c7f96e..dae05af 100644
--- a/ipykernel/iostream.py
+++ b/ipykernel/iostream.py
@@ -205,8 +205,6 @@ class OutStream(object):
             warnings.warn("pipe argument to OutStream is deprecated and ignored",
                 DeprecationWarning)
         self.encoding = 'UTF-8'
-        # This is necessary for compatibility with Python built-in streams
-        self.errors = None
         self.session = session
         if not isinstance(pub_thread, IOPubThread):
             # Backward-compat: given socket, not thread. Wrap in a thread.
diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py
index b209ee6..cb17c70 100644
--- a/ipykernel/zmqshell.py
+++ b/ipykernel/zmqshell.py
@@ -68,7 +68,7 @@ class ZMQDisplayPublisher(DisplayPublisher):
     # thread_local:
     # An attribute used to ensure the correct output message
     # is processed. See ipykernel Issue 113 for a discussion.
-    thread_local = Any()
+    _thread_local = Any()
 
     def set_parent(self, parent):
         """Set the parent for outbound messages."""
@@ -79,14 +79,17 @@ class ZMQDisplayPublisher(DisplayPublisher):
         sys.stdout.flush()
         sys.stderr.flush()
 
-    @default('thread_local')
+    @default('_thread_local')
     def _default_thread_local(self):
-        """ Initialises the threadlocal attribute and
-            gives it a 'hooks' attribute.
-        """
-        loc = local()
-        loc.hooks = []
-        return loc
+        """Initialize our thread local storage"""
+        return local()
+
+    @property
+    def _hooks(self):
+        if not hasattr(self._thread_local, 'hooks'):
+            # create new list for a new thread
+            self._thread_local.hooks = []
+        return self._thread_local.hooks
 
     def publish(self, data, metadata=None, source=None):
         self._flush_streams()
@@ -108,7 +111,7 @@ class ZMQDisplayPublisher(DisplayPublisher):
         # Each transform either returns a new
         # message or None. If None is returned,
         # the message has been 'used' and we return.
-        for hook in self.thread_local.hooks:
+        for hook in self._hooks:
             msg = hook(msg)
             if msg is None:
                 return
@@ -143,7 +146,7 @@ class ZMQDisplayPublisher(DisplayPublisher):
         Returning `None` will halt that execution path, and
         session.send will not be called.
         """
-        self.thread_local.hooks.append(hook)
+        self._hooks.append(hook)
 
     def unregister_hook(self, hook):
         """
@@ -160,7 +163,7 @@ class ZMQDisplayPublisher(DisplayPublisher):
                found.
         """
         try:
-            self.thread_local.hooks.remove(hook)
+            self._hooks.remove(hook)
             return True
         except ValueError:
             return False


Test patch:
diff --git a/ipykernel/tests/test_zmq_shell.py b/ipykernel/tests/test_zmq_shell.py
index 6743db6..8426fa2 100644
--- a/ipykernel/tests/test_zmq_shell.py
+++ b/ipykernel/tests/test_zmq_shell.py
@@ -5,7 +5,14 @@
 # Distributed under the terms of the Modified BSD License.
 
 import os
+try:
+    from queue import Queue
+except ImportError:
+    # py2
+    from Queue import Queue
+from threading import Thread
 import unittest
+
 from traitlets import Int
 import zmq
 
@@ -88,12 +95,24 @@ class ZMQDisplayPublisherTests(unittest.TestCase):
         self.assertEqual(self.disp_pub.session, self.session)
         self.assertEqual(self.disp_pub.pub_socket, self.socket)
 
-    def test_thread_local_default(self):
+    def test_thread_local_hooks(self):
         """
         Confirms that the thread_local attribute is correctly
         initialised with an empty list for the display hooks
         """
-        self.assertEqual(self.disp_pub.thread_local.hooks, [])
+        self.assertEqual(self.disp_pub._hooks, [])
+        def hook(msg):
+            return msg
+        self.disp_pub.register_hook(hook)
+        self.assertEqual(self.disp_pub._hooks, [hook])
+
+        q = Queue()
+        def set_thread_hooks():
+            q.put(self.disp_pub._hooks)
+        t = Thread(target=set_thread_hooks)
+        t.start()
+        thread_hooks = q.get(timeout=10)
+        self.assertEqual(thread_hooks, [])
 
     def test_publish(self):
         """