diff --git a/IPython/html/services/kernelspecs/handlers.py b/IPython/html/services/kernelspecs/handlers.py index 2d1a75fc2..72397788f 100644 --- a/IPython/html/services/kernelspecs/handlers.py +++ b/IPython/html/services/kernelspecs/handlers.py @@ -2,11 +2,43 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. + +import glob import json +import os +pjoin = os.path.join + from tornado import web from ...base.handlers import IPythonHandler, json_errors +from ...utils import url_path_join +def kernelspec_model(handler, name): + """Load a KernelSpec by name and return the REST API model""" + ksm = handler.kernel_spec_manager + spec = ksm.get_kernel_spec(name) + d = {'name': name} + d['spec'] = spec.to_dict() + d['resources'] = resources = {} + resource_dir = spec.resource_dir + for resource in ['kernel.js', 'kernel.css']: + if os.path.exists(pjoin(resource_dir, resource)): + resources[resource] = url_path_join( + handler.base_url, + 'kernelspecs', + name, + resource + ) + for logo_file in glob.glob(pjoin(resource_dir, 'logo-*')): + fname = os.path.basename(logo_file) + no_ext, _ = os.path.splitext(fname) + resources[no_ext] = url_path_join( + handler.base_url, + 'kernelspecs', + name, + fname + ) + return d class MainKernelSpecHandler(IPythonHandler): SUPPORTED_METHODS = ('GET',) @@ -21,13 +53,11 @@ class MainKernelSpecHandler(IPythonHandler): model['kernelspecs'] = specs = {} for kernel_name in ksm.find_kernel_specs(): try: - d = ksm.get_kernel_spec(kernel_name).to_dict() + d = kernelspec_model(self, kernel_name) except Exception: self.log.error("Failed to load kernel spec: '%s'", kernel_name, exc_info=True) continue - d['name'] = kernel_name specs[kernel_name] = d - self.set_header("Content-Type", 'application/json') self.finish(json.dumps(model)) @@ -38,13 +68,12 @@ class KernelSpecHandler(IPythonHandler): @web.authenticated @json_errors def get(self, kernel_name): - ksm = self.kernel_spec_manager try: - kernelspec = ksm.get_kernel_spec(kernel_name) + model = kernelspec_model(self, kernel_name) except KeyError: raise web.HTTPError(404, u'Kernel spec %s not found' % kernel_name) self.set_header("Content-Type", 'application/json') - self.finish(kernelspec.to_json()) + self.finish(json.dumps(model)) # URL to handler mappings diff --git a/IPython/html/services/kernelspecs/tests/test_kernelspecs_api.py b/IPython/html/services/kernelspecs/tests/test_kernelspecs_api.py index 0d43fb1de..485948dd1 100644 --- a/IPython/html/services/kernelspecs/tests/test_kernelspecs_api.py +++ b/IPython/html/services/kernelspecs/tests/test_kernelspecs_api.py @@ -99,17 +99,20 @@ class APITest(NotebookTestBase): self.assertGreaterEqual(len(specs), 2) def is_sample_kernelspec(s): - return s['name'] == 'sample' and s['display_name'] == 'Test kernel' + return s['name'] == 'sample' and s['spec']['display_name'] == 'Test kernel' def is_default_kernelspec(s): - return s['name'] == NATIVE_KERNEL_NAME and s['display_name'].startswith("IPython") + return s['name'] == NATIVE_KERNEL_NAME and s['spec']['display_name'].startswith("IPython") assert any(is_sample_kernelspec(s) for s in specs.values()), specs assert any(is_default_kernelspec(s) for s in specs.values()), specs def test_get_kernelspec(self): - spec = self.ks_api.kernel_spec_info('Sample').json() # Case insensitive - self.assertEqual(spec['display_name'], 'Test kernel') + model = self.ks_api.kernel_spec_info('Sample').json() # Case insensitive + self.assertEqual(model['name'].lower(), 'sample') + self.assertIsInstance(model['spec'], dict) + self.assertEqual(model['spec']['display_name'], 'Test kernel') + self.assertIsInstance(model['resources'], dict) def test_get_nonexistant_kernelspec(self): with assert_http_error(404): diff --git a/IPython/html/static/notebook/js/kernelselector.js b/IPython/html/static/notebook/js/kernelselector.js index a9165b8a7..3cd13d39e 100644 --- a/IPython/html/static/notebook/js/kernelselector.js +++ b/IPython/html/static/notebook/js/kernelselector.js @@ -35,8 +35,8 @@ define([ var change_kernel_submenu = $("#menu-change-kernel-submenu"); var keys = Object.keys(data.kernelspecs).sort(function (a, b) { // sort by display_name - var da = data.kernelspecs[a].display_name; - var db = data.kernelspecs[b].display_name; + var da = data.kernelspecs[a].spec.display_name; + var db = data.kernelspecs[b].spec.display_name; if (da === db) { return 0; } else if (da > db) { @@ -50,7 +50,7 @@ define([ var ks_submenu_entry = $("
  • ").attr("id", "kernel-submenu-"+ks.name).append($('') .attr('href', '#') .click($.proxy(this.change_kernel, this, ks.name)) - .text(ks.display_name)); + .text(ks.spec.display_name)); change_kernel_submenu.append(ks_submenu_entry); } }; @@ -59,25 +59,17 @@ define([ /** * TODO, have a methods to set kernel spec directly ? **/ - var that = this; if (kernel_name === this.current_selection) { return; } var ks = this.kernelspecs[kernel_name]; - var new_mode_url = 'kernelspecs/'+ks.name+'/kernel'; - - var css_url = this.notebook.base_url+new_mode_url+'.css'; - $.ajax({ - type: 'HEAD', - url: css_url, - success: function(){ - $('#kernel-css') - .attr('href',css_url); - }, - error:function(){ - console.info("No custom kernel.css at URL:", css_url) - } - }); + + var css_url = ks.resources['kernel.css']; + if (css_url) { + $('#kernel-css').attr('href', css_url); + } else { + $('#kernel-css').attr('href', ''); + } try { this.notebook.start_session(kernel_name); @@ -92,26 +84,23 @@ define([ return; } this.events.trigger('spec_changed.Kernel', ks); - - - // load new mode kernel.js if exist - require([new_mode_url], - // if new mode has custom.js - function(new_mode){ - that.lock_switch(); - if(new_mode && new_mode.onload){ - new_mode.onload(); - } else { - console.warn("The current kernel defined a kernel.js file but does not contain "+ - "any asynchronous module definition. This is undefined behavior "+ - "which is not recommended"); + + if (ks.resources['kernel.js']) { + require([ks.resources['kernel.js']], + function (kernel_mod) { + if (kernel_mod && kernel_mod.onload) { + kernel_mod.onload(); + } else { + console.warn("Kernel " + ks.name + " has a kernel.js file that does not contain "+ + "any asynchronous module definition. This is undefined behavior "+ + "and not recommended."); + } + }, function (err) { + console.warn("Failed to load kernel.js from ", ks.resources['kernel.js'], err); } - }, - function(err){ - // if new mode does not have custom.js - console.info("No custom kernel.css at URL:", new_mode_url) - } - ); + ); + } + }; KernelSelector.prototype.lock_switch = function() { @@ -123,10 +112,16 @@ define([ KernelSelector.prototype.bind_events = function() { var that = this; + var logo_img = this.element.find("img.current_kernel_logo"); this.events.on('spec_changed.Kernel', function(event, data) { that.current_selection = data.name; - $("#kernel_indicator").find('.kernel_indicator_name').text(data.display_name); - that.element.find("img.current_kernel_logo").attr("src", that.notebook.base_url + "kernelspecs/" + data.name + "/logo-64x64.png"); + $("#kernel_indicator").find('.kernel_indicator_name').text(data.spec.display_name); + if (data.resources['logo-64x64']) { + logo_img.attr("src", data.resources['logo-64x64']); + logo_img.show(); + } else { + logo_img.hide(); + } }); this.events.on('kernel_created.Session', function(event, data) { @@ -139,7 +134,6 @@ define([ } }); - var logo_img = this.element.find("img.current_kernel_logo"); logo_img.on("load", function() { logo_img.show(); }); diff --git a/IPython/html/static/notebook/js/notebook.js b/IPython/html/static/notebook/js/notebook.js index 94be416de..8e8fe5987 100644 --- a/IPython/html/static/notebook/js/notebook.js +++ b/IPython/html/static/notebook/js/notebook.js @@ -247,7 +247,7 @@ define([ this.events.on('spec_changed.Kernel', function(event, data) { that.metadata.kernelspec = - {name: data.name, display_name: data.display_name}; + {name: data.name, display_name: data.spec.display_name}; }); this.events.on('kernel_ready.Kernel', function(event, data) {