Repository: ipython/ipython

Issue:
Single bad kernelspec JSON causes all kernels to not work
If a single kernelspec files has a problem (invalid JSON) right now, no kernels will show up in the notebook. We need to add better error logic so that the remaining kernels run fine and that we at least show a good error message in the notebook-starting terminal.



Discussion:

Resolving patch:
diff --git a/IPython/html/services/kernelspecs/handlers.py b/IPython/html/services/kernelspecs/handlers.py
--- a/IPython/html/services/kernelspecs/handlers.py
+++ b/IPython/html/services/kernelspecs/handlers.py
@@ -19,7 +19,11 @@ def get(self):
         ksm = self.kernel_spec_manager
         results = []
         for kernel_name in sorted(ksm.find_kernel_specs(), key=_pythonfirst):
-            d = ksm.get_kernel_spec(kernel_name).to_dict()
+            try:
+                d = ksm.get_kernel_spec(kernel_name).to_dict()
+            except Exception:
+                self.log.error("Failed to load kernel spec: '%s'", kernel_name, exc_info=True)
+                continue
             d['name'] = kernel_name
             results.append(d)
 


Test patch:
diff --git a/IPython/html/services/kernelspecs/tests/__init__.py b/IPython/html/services/kernelspecs/tests/__init__.py
new file mode 100644
diff --git a/IPython/html/services/kernelspecs/tests/test_kernelspecs_api.py b/IPython/html/services/kernelspecs/tests/test_kernelspecs_api.py
--- a/IPython/html/services/kernelspecs/tests/test_kernelspecs_api.py
+++ b/IPython/html/services/kernelspecs/tests/test_kernelspecs_api.py
@@ -5,6 +5,7 @@
 import io
 import json
 import os
+import shutil
 
 pjoin = os.path.join
 
@@ -18,7 +19,6 @@
 # break these tests
 sample_kernel_json = {'argv':['cat', '{connection_file}'],
                       'display_name':'Test kernel',
-                      'language':'bash',
                      }
 
 some_resource = u"The very model of a modern major general"
@@ -66,6 +66,25 @@ def setUp(self):
 
         self.ks_api = KernelSpecAPI(self.base_url())
 
+    def test_list_kernelspecs_bad(self):
+        """Can list kernelspecs when one is invalid"""
+        bad_kernel_dir = pjoin(self.ipython_dir.name, 'kernels', 'bad')
+        try:
+            os.makedirs(bad_kernel_dir)
+        except OSError as e:
+            if e.errno != errno.EEXIST:
+                raise
+        
+        with open(pjoin(bad_kernel_dir, 'kernel.json'), 'w') as f:
+            f.write("garbage")
+        
+        specs = self.ks_api.list().json()
+        assert isinstance(specs, list)
+        # 2: the sample kernelspec created in setUp, and the native Python kernel
+        self.assertGreaterEqual(len(specs), 2)
+        
+        shutil.rmtree(bad_kernel_dir)
+    
     def test_list_kernelspecs(self):
         specs = self.ks_api.list().json()
         assert isinstance(specs, list)
@@ -84,7 +103,7 @@ def is_default_kernelspec(s):
 
     def test_get_kernelspec(self):
         spec = self.ks_api.kernel_spec_info('Sample').json()  # Case insensitive
-        self.assertEqual(spec['language'], 'bash')
+        self.assertEqual(spec['display_name'], 'Test kernel')
 
     def test_get_nonexistant_kernelspec(self):
         with assert_http_error(404):