From d1c1aaa72ade843d6823b1ab3aa6dd06ac3d7618 2011-10-29 04:04:37
From: MinRK <benjaminrk@gmail.com>
Date: 2011-10-29 04:04:37
Subject: [PATCH] AsyncResult.__getattr__ shouldn't raise TimeoutError

This causes problems for things that use hasattr, e.g. list(ar) checking
for `__length_hint__`.

tests added for getattr/getitem behavior
---

diff --git a/IPython/parallel/client/asyncresult.py b/IPython/parallel/client/asyncresult.py
index 7ae200d..23fed54 100644
--- a/IPython/parallel/client/asyncresult.py
+++ b/IPython/parallel/client/asyncresult.py
@@ -62,6 +62,7 @@ class AsyncResult(object):
         self._tracker = tracker
         self._ready = False
         self._success = None
+        self._metadata = None
         if len(msg_ids) == 1:
             self._single_result = not isinstance(targets, (list, tuple))
         else:
@@ -231,13 +232,13 @@ class AsyncResult(object):
         else:
             raise TypeError("Invalid key type %r, must be 'int','slice', or 'str'"%type(key))
 
-    @check_ready
     def __getattr__(self, key):
         """getattr maps to getitem for convenient attr access to metadata."""
-        if key not in self._metadata[0].keys():
+        try:
+            return self.__getitem__(key)
+        except (error.TimeoutError, KeyError):
             raise AttributeError("%r object has no attribute %r"%(
                     self.__class__.__name__, key))
-        return self.__getitem__(key)
 
     # asynchronous iterator:
     def __iter__(self):
diff --git a/IPython/parallel/tests/test_asyncresult.py b/IPython/parallel/tests/test_asyncresult.py
index f9448ad..30dae4b 100644
--- a/IPython/parallel/tests/test_asyncresult.py
+++ b/IPython/parallel/tests/test_asyncresult.py
@@ -70,4 +70,46 @@ class AsyncResultTest(ClusterTestCase):
         self.assertEquals(sorted(d.keys()), sorted(self.client.ids))
         for eid,r in d.iteritems():
             self.assertEquals(r, 5)
+    
+    def test_list_amr(self):
+        ar = self.client.load_balanced_view().map_async(wait, [0.1]*5)
+        rlist = list(ar)
+    
+    def test_getattr(self):
+        ar = self.client[:].apply_async(wait, 0.5)
+        self.assertRaises(AttributeError, lambda : ar._foo)
+        self.assertRaises(AttributeError, lambda : ar.__length_hint__())
+        self.assertRaises(AttributeError, lambda : ar.foo)
+        self.assertRaises(AttributeError, lambda : ar.engine_id)
+        self.assertFalse(hasattr(ar, '__length_hint__'))
+        self.assertFalse(hasattr(ar, 'foo'))
+        self.assertFalse(hasattr(ar, 'engine_id'))
+        ar.get(5)
+        self.assertRaises(AttributeError, lambda : ar._foo)
+        self.assertRaises(AttributeError, lambda : ar.__length_hint__())
+        self.assertRaises(AttributeError, lambda : ar.foo)
+        self.assertTrue(isinstance(ar.engine_id, list))
+        self.assertEquals(ar.engine_id, ar['engine_id'])
+        self.assertFalse(hasattr(ar, '__length_hint__'))
+        self.assertFalse(hasattr(ar, 'foo'))
+        self.assertTrue(hasattr(ar, 'engine_id'))
+
+    def test_getitem(self):
+        ar = self.client[:].apply_async(wait, 0.5)
+        self.assertRaises(TimeoutError, lambda : ar['foo'])
+        self.assertRaises(TimeoutError, lambda : ar['engine_id'])
+        ar.get(5)
+        self.assertRaises(KeyError, lambda : ar['foo'])
+        self.assertTrue(isinstance(ar['engine_id'], list))
+        self.assertEquals(ar.engine_id, ar['engine_id'])
+    
+    def test_single_result(self):
+        ar = self.client[-1].apply_async(wait, 0.5)
+        self.assertRaises(TimeoutError, lambda : ar['foo'])
+        self.assertRaises(TimeoutError, lambda : ar['engine_id'])
+        self.assertTrue(ar.get(5) == 0.5)
+        self.assertTrue(isinstance(ar['engine_id'], int))
+        self.assertTrue(isinstance(ar.engine_id, int))
+        self.assertEquals(ar.engine_id, ar['engine_id'])
+
 
diff --git a/IPython/parallel/tests/test_lbview.py b/IPython/parallel/tests/test_lbview.py
index ae8eac2..39ac107 100644
--- a/IPython/parallel/tests/test_lbview.py
+++ b/IPython/parallel/tests/test_lbview.py
@@ -68,10 +68,11 @@ class TestLoadBalancedView(ClusterTestCase):
         data = range(16,0,-1)
         reference = map(f, data)
         
-        amr = self.view.map_async(f, data, ordered=False)
+        amr = self.view.map_async(slow_f, data, ordered=False)
         self.assertTrue(isinstance(amr, pmod.AsyncMapResult))
-        # check individual elements, retrieved as they come (uses __iter__)
-        astheycame = list(amr)
+        # check individual elements, retrieved as they come
+        # list comprehension uses __iter__
+        astheycame = [ r for r in amr ]
         # Ensure that at least one result came out of order:
         self.assertNotEquals(astheycame, reference, "should not have preserved order")
         self.assertEquals(sorted(astheycame, reverse=True), reference, "result corrupted")
@@ -86,9 +87,10 @@ class TestLoadBalancedView(ClusterTestCase):
         data = range(16,0,-1)
         reference = map(f, data)
         
-        amr = self.view.map_async(f, data)
+        amr = self.view.map_async(slow_f, data)
         self.assertTrue(isinstance(amr, pmod.AsyncMapResult))
-        # check individual elements, retrieved as they come (uses __iter__)
+        # check individual elements, retrieved as they come
+        # list(amr) uses __iter__
         astheycame = list(amr)
         # Ensure that results came in order
         self.assertEquals(astheycame, reference)