##// END OF EJS Templates
Put full kernel info in REST API response for all kernels
Thomas Kluyver -
Show More
@@ -1,51 +1,52 b''
1 """Tornado handlers for kernel specifications."""
1 """Tornado handlers for kernel specifications."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from tornado import web
6 from tornado import web
7
7
8 from zmq.utils import jsonapi
8 from zmq.utils import jsonapi
9
9
10 from ...base.handlers import IPythonHandler, json_errors
10 from ...base.handlers import IPythonHandler, json_errors
11
11
12
12
13 class MainKernelSpecHandler(IPythonHandler):
13 class MainKernelSpecHandler(IPythonHandler):
14 SUPPORTED_METHODS = ('GET',)
14 SUPPORTED_METHODS = ('GET',)
15
15
16 @web.authenticated
16 @web.authenticated
17 @json_errors
17 @json_errors
18 def get(self):
18 def get(self):
19 ksm = self.kernel_spec_manager
19 ksm = self.kernel_spec_manager
20 results = []
20 results = []
21 for kernel_name in ksm.find_kernel_specs():
21 for kernel_name in ksm.find_kernel_specs():
22 results.append(dict(name=kernel_name,
22 d = ksm.get_kernel_spec(kernel_name).to_dict()
23 display_name=ksm.get_kernel_spec(kernel_name).display_name))
23 d['name'] = kernel_name
24 results.append(d)
24
25
25 self.set_header("Content-Type", 'application/json')
26 self.set_header("Content-Type", 'application/json')
26 self.finish(jsonapi.dumps(results))
27 self.finish(jsonapi.dumps(results))
27
28
28
29
29 class KernelSpecHandler(IPythonHandler):
30 class KernelSpecHandler(IPythonHandler):
30 SUPPORTED_METHODS = ('GET',)
31 SUPPORTED_METHODS = ('GET',)
31
32
32 @web.authenticated
33 @web.authenticated
33 @json_errors
34 @json_errors
34 def get(self, kernel_name):
35 def get(self, kernel_name):
35 ksm = self.kernel_spec_manager
36 ksm = self.kernel_spec_manager
36 try:
37 try:
37 kernelspec = ksm.get_kernel_spec(kernel_name)
38 kernelspec = ksm.get_kernel_spec(kernel_name)
38 except KeyError:
39 except KeyError:
39 raise web.HTTPError(404, u'Kernel spec %s not found' % kernel_name)
40 raise web.HTTPError(404, u'Kernel spec %s not found' % kernel_name)
40 self.set_header("Content-Type", 'application/json')
41 self.set_header("Content-Type", 'application/json')
41 self.finish(kernelspec.to_json())
42 self.finish(kernelspec.to_json())
42
43
43
44
44 # URL to handler mappings
45 # URL to handler mappings
45
46
46 kernel_name_regex = r"(?P<kernel_name>\w+)"
47 kernel_name_regex = r"(?P<kernel_name>\w+)"
47
48
48 default_handlers = [
49 default_handlers = [
49 (r"/api/kernelspecs", MainKernelSpecHandler),
50 (r"/api/kernelspecs", MainKernelSpecHandler),
50 (r"/api/kernelspecs/%s" % kernel_name_regex, KernelSpecHandler),
51 (r"/api/kernelspecs/%s" % kernel_name_regex, KernelSpecHandler),
51 ]
52 ]
@@ -1,94 +1,97 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Test the kernel specs webservice API."""
2 """Test the kernel specs webservice API."""
3
3
4 import errno
4 import errno
5 import io
5 import io
6 import json
6 import json
7 import os
7 import os
8
8
9 pjoin = os.path.join
9 pjoin = os.path.join
10
10
11 import requests
11 import requests
12
12
13 from IPython.html.utils import url_path_join
13 from IPython.html.utils import url_path_join
14 from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error
14 from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error
15
15
16 # Copied from IPython.kernel.tests.test_kernelspec so updating that doesn't
16 # Copied from IPython.kernel.tests.test_kernelspec so updating that doesn't
17 # break these tests
17 # break these tests
18 sample_kernel_json = {'argv':['cat', '{connection_file}'],
18 sample_kernel_json = {'argv':['cat', '{connection_file}'],
19 'display_name':'Test kernel',
19 'display_name':'Test kernel',
20 'language':'bash',
20 'language':'bash',
21 }
21 }
22
22
23 some_resource = u"The very model of a modern major general"
23 some_resource = u"The very model of a modern major general"
24
24
25
25
26 class KernelSpecAPI(object):
26 class KernelSpecAPI(object):
27 """Wrapper for notebook API calls."""
27 """Wrapper for notebook API calls."""
28 def __init__(self, base_url):
28 def __init__(self, base_url):
29 self.base_url = base_url
29 self.base_url = base_url
30
30
31 def _req(self, verb, path, body=None):
31 def _req(self, verb, path, body=None):
32 response = requests.request(verb,
32 response = requests.request(verb,
33 url_path_join(self.base_url, path),
33 url_path_join(self.base_url, path),
34 data=body,
34 data=body,
35 )
35 )
36 response.raise_for_status()
36 response.raise_for_status()
37 return response
37 return response
38
38
39 def list(self):
39 def list(self):
40 return self._req('GET', 'api/kernelspecs')
40 return self._req('GET', 'api/kernelspecs')
41
41
42 def kernel_spec_info(self, name):
42 def kernel_spec_info(self, name):
43 return self._req('GET', url_path_join('api/kernelspecs', name))
43 return self._req('GET', url_path_join('api/kernelspecs', name))
44
44
45 def kernel_resource(self, name, path):
45 def kernel_resource(self, name, path):
46 return self._req('GET', url_path_join('kernelspecs', name, path))
46 return self._req('GET', url_path_join('kernelspecs', name, path))
47
47
48 class APITest(NotebookTestBase):
48 class APITest(NotebookTestBase):
49 """Test the kernelspec web service API"""
49 """Test the kernelspec web service API"""
50 def setUp(self):
50 def setUp(self):
51 ipydir = self.ipython_dir.name
51 ipydir = self.ipython_dir.name
52 sample_kernel_dir = pjoin(ipydir, 'kernels', 'sample')
52 sample_kernel_dir = pjoin(ipydir, 'kernels', 'sample')
53 try:
53 try:
54 os.makedirs(sample_kernel_dir)
54 os.makedirs(sample_kernel_dir)
55 except OSError as e:
55 except OSError as e:
56 if e.errno != errno.EEXIST:
56 if e.errno != errno.EEXIST:
57 raise
57 raise
58
58
59 with open(pjoin(sample_kernel_dir, 'kernel.json'), 'w') as f:
59 with open(pjoin(sample_kernel_dir, 'kernel.json'), 'w') as f:
60 json.dump(sample_kernel_json, f)
60 json.dump(sample_kernel_json, f)
61
61
62 with io.open(pjoin(sample_kernel_dir, 'resource.txt'), 'w',
62 with io.open(pjoin(sample_kernel_dir, 'resource.txt'), 'w',
63 encoding='utf-8') as f:
63 encoding='utf-8') as f:
64 f.write(some_resource)
64 f.write(some_resource)
65
65
66 self.ks_api = KernelSpecAPI(self.base_url())
66 self.ks_api = KernelSpecAPI(self.base_url())
67
67
68 def test_list_kernelspecs(self):
68 def test_list_kernelspecs(self):
69 specs = self.ks_api.list().json()
69 specs = self.ks_api.list().json()
70 assert isinstance(specs, list)
70 assert isinstance(specs, list)
71
71
72 # 2: the sample kernelspec created in setUp, and the native Python kernel
72 # 2: the sample kernelspec created in setUp, and the native Python kernel
73 self.assertEqual(len(specs), 2)
73 self.assertEqual(len(specs), 2)
74 assert any(s == {'name': 'sample', 'display_name': 'Test kernel'}
74
75 for s in specs), specs
75 def is_sample_kernelspec(s):
76 return s['name'] == 'sample' and s['display_name'] == 'Test kernel'
77
78 assert any(is_sample_kernelspec(s) for s in specs), specs
76
79
77 def test_get_kernelspec(self):
80 def test_get_kernelspec(self):
78 spec = self.ks_api.kernel_spec_info('Sample').json() # Case insensitive
81 spec = self.ks_api.kernel_spec_info('Sample').json() # Case insensitive
79 self.assertEqual(spec['language'], 'bash')
82 self.assertEqual(spec['language'], 'bash')
80
83
81 def test_get_nonexistant_kernelspec(self):
84 def test_get_nonexistant_kernelspec(self):
82 with assert_http_error(404):
85 with assert_http_error(404):
83 self.ks_api.kernel_spec_info('nonexistant')
86 self.ks_api.kernel_spec_info('nonexistant')
84
87
85 def test_get_kernel_resource_file(self):
88 def test_get_kernel_resource_file(self):
86 res = self.ks_api.kernel_resource('sAmple', 'resource.txt')
89 res = self.ks_api.kernel_resource('sAmple', 'resource.txt')
87 self.assertEqual(res.text, some_resource)
90 self.assertEqual(res.text, some_resource)
88
91
89 def test_get_nonexistant_resource(self):
92 def test_get_nonexistant_resource(self):
90 with assert_http_error(404):
93 with assert_http_error(404):
91 self.ks_api.kernel_resource('nonexistant', 'resource.txt')
94 self.ks_api.kernel_resource('nonexistant', 'resource.txt')
92
95
93 with assert_http_error(404):
96 with assert_http_error(404):
94 self.ks_api.kernel_resource('sample', 'nonexistant.txt')
97 self.ks_api.kernel_resource('sample', 'nonexistant.txt')
@@ -1,150 +1,153 b''
1 import io
1 import io
2 import json
2 import json
3 import os
3 import os
4 import sys
4 import sys
5
5
6 pjoin = os.path.join
6 pjoin = os.path.join
7
7
8 from IPython.utils.path import get_ipython_dir
8 from IPython.utils.path import get_ipython_dir
9 from IPython.utils.py3compat import PY3
9 from IPython.utils.py3compat import PY3
10 from IPython.utils.traitlets import HasTraits, List, Unicode, Dict
10 from IPython.utils.traitlets import HasTraits, List, Unicode, Dict
11
11
12 if os.name == 'nt':
12 if os.name == 'nt':
13 programdata = os.environ.get('PROGRAMDATA', None)
13 programdata = os.environ.get('PROGRAMDATA', None)
14 if programdata:
14 if programdata:
15 SYSTEM_KERNEL_DIR = pjoin(programdata, 'ipython', 'kernels')
15 SYSTEM_KERNEL_DIR = pjoin(programdata, 'ipython', 'kernels')
16 else: # PROGRAMDATA is not defined by default on XP.
16 else: # PROGRAMDATA is not defined by default on XP.
17 SYSTEM_KERNEL_DIR = None
17 SYSTEM_KERNEL_DIR = None
18 else:
18 else:
19 SYSTEM_KERNEL_DIR = "/usr/share/ipython/kernels"
19 SYSTEM_KERNEL_DIR = "/usr/share/ipython/kernels"
20
20
21 NATIVE_KERNEL_NAME = 'python3' if PY3 else 'python2'
21 NATIVE_KERNEL_NAME = 'python3' if PY3 else 'python2'
22
22
23 class KernelSpec(HasTraits):
23 class KernelSpec(HasTraits):
24 argv = List()
24 argv = List()
25 display_name = Unicode()
25 display_name = Unicode()
26 language = Unicode()
26 language = Unicode()
27 codemirror_mode = None
27 codemirror_mode = None
28 env = Dict()
28 env = Dict()
29
29
30 resource_dir = Unicode()
30 resource_dir = Unicode()
31
31
32 def __init__(self, resource_dir, argv, display_name, language,
32 def __init__(self, resource_dir, argv, display_name, language,
33 codemirror_mode=None):
33 codemirror_mode=None):
34 super(KernelSpec, self).__init__(resource_dir=resource_dir, argv=argv,
34 super(KernelSpec, self).__init__(resource_dir=resource_dir, argv=argv,
35 display_name=display_name, language=language,
35 display_name=display_name, language=language,
36 codemirror_mode=codemirror_mode)
36 codemirror_mode=codemirror_mode)
37 if not self.codemirror_mode:
37 if not self.codemirror_mode:
38 self.codemirror_mode = self.language
38 self.codemirror_mode = self.language
39
39
40 @classmethod
40 @classmethod
41 def from_resource_dir(cls, resource_dir):
41 def from_resource_dir(cls, resource_dir):
42 """Create a KernelSpec object by reading kernel.json
42 """Create a KernelSpec object by reading kernel.json
43
43
44 Pass the path to the *directory* containing kernel.json.
44 Pass the path to the *directory* containing kernel.json.
45 """
45 """
46 kernel_file = pjoin(resource_dir, 'kernel.json')
46 kernel_file = pjoin(resource_dir, 'kernel.json')
47 with io.open(kernel_file, 'r', encoding='utf-8') as f:
47 with io.open(kernel_file, 'r', encoding='utf-8') as f:
48 kernel_dict = json.load(f)
48 kernel_dict = json.load(f)
49 return cls(resource_dir=resource_dir, **kernel_dict)
49 return cls(resource_dir=resource_dir, **kernel_dict)
50
50
51 def to_json(self):
51 def to_dict(self):
52 return json.dumps(dict(argv=self.argv,
52 return dict(argv=self.argv,
53 env=self.env,
53 env=self.env,
54 display_name=self.display_name,
54 display_name=self.display_name,
55 language=self.language,
55 language=self.language,
56 codemirror_mode=self.codemirror_mode,
56 codemirror_mode=self.codemirror_mode,
57 ))
57 )
58
59 def to_json(self):
60 return json.dumps(self.to_dict())
58
61
59 def _is_kernel_dir(path):
62 def _is_kernel_dir(path):
60 """Is ``path`` a kernel directory?"""
63 """Is ``path`` a kernel directory?"""
61 return os.path.isdir(path) and os.path.isfile(pjoin(path, 'kernel.json'))
64 return os.path.isdir(path) and os.path.isfile(pjoin(path, 'kernel.json'))
62
65
63 def _list_kernels_in(dir):
66 def _list_kernels_in(dir):
64 """Return a mapping of kernel names to resource directories from dir.
67 """Return a mapping of kernel names to resource directories from dir.
65
68
66 If dir is None or does not exist, returns an empty dict.
69 If dir is None or does not exist, returns an empty dict.
67 """
70 """
68 if dir is None or not os.path.isdir(dir):
71 if dir is None or not os.path.isdir(dir):
69 return {}
72 return {}
70 return {f.lower(): pjoin(dir, f) for f in os.listdir(dir)
73 return {f.lower(): pjoin(dir, f) for f in os.listdir(dir)
71 if _is_kernel_dir(pjoin(dir, f))}
74 if _is_kernel_dir(pjoin(dir, f))}
72
75
73 class NoSuchKernel(KeyError):
76 class NoSuchKernel(KeyError):
74 def __init__(self, name):
77 def __init__(self, name):
75 self.name = name
78 self.name = name
76
79
77 class KernelSpecManager(HasTraits):
80 class KernelSpecManager(HasTraits):
78 ipython_dir = Unicode()
81 ipython_dir = Unicode()
79 def _ipython_dir_default(self):
82 def _ipython_dir_default(self):
80 return get_ipython_dir()
83 return get_ipython_dir()
81
84
82 user_kernel_dir = Unicode()
85 user_kernel_dir = Unicode()
83 def _user_kernel_dir_default(self):
86 def _user_kernel_dir_default(self):
84 return pjoin(self.ipython_dir, 'kernels')
87 return pjoin(self.ipython_dir, 'kernels')
85
88
86 kernel_dirs = List(
89 kernel_dirs = List(
87 help="List of kernel directories to search. Later ones take priority over earlier."
90 help="List of kernel directories to search. Later ones take priority over earlier."
88 )
91 )
89 def _kernel_dirs_default(self):
92 def _kernel_dirs_default(self):
90 return [
93 return [
91 SYSTEM_KERNEL_DIR,
94 SYSTEM_KERNEL_DIR,
92 self.user_kernel_dir,
95 self.user_kernel_dir,
93 ]
96 ]
94
97
95 def _make_native_kernel_dir(self):
98 def _make_native_kernel_dir(self):
96 """Makes a kernel directory for the native kernel.
99 """Makes a kernel directory for the native kernel.
97
100
98 The native kernel is the kernel using the same Python runtime as this
101 The native kernel is the kernel using the same Python runtime as this
99 process. This will put its informatino in the user kernels directory.
102 process. This will put its informatino in the user kernels directory.
100 """
103 """
101 path = pjoin(self.user_kernel_dir, NATIVE_KERNEL_NAME)
104 path = pjoin(self.user_kernel_dir, NATIVE_KERNEL_NAME)
102 os.makedirs(path, mode=0o755)
105 os.makedirs(path, mode=0o755)
103 with open(pjoin(path, 'kernel.json'), 'w') as f:
106 with open(pjoin(path, 'kernel.json'), 'w') as f:
104 json.dump({'argv':[NATIVE_KERNEL_NAME, '-c',
107 json.dump({'argv':[NATIVE_KERNEL_NAME, '-c',
105 'from IPython.kernel.zmq.kernelapp import main; main()',
108 'from IPython.kernel.zmq.kernelapp import main; main()',
106 '-f', '{connection_file}'],
109 '-f', '{connection_file}'],
107 'display_name': 'Python 3' if PY3 else 'Python 2',
110 'display_name': 'Python 3' if PY3 else 'Python 2',
108 'language': 'python',
111 'language': 'python',
109 'codemirror_mode': {'name': 'python',
112 'codemirror_mode': {'name': 'python',
110 'version': sys.version_info[0]},
113 'version': sys.version_info[0]},
111 },
114 },
112 f, indent=1)
115 f, indent=1)
113 # TODO: Copy icons into directory
116 # TODO: Copy icons into directory
114 return path
117 return path
115
118
116 def find_kernel_specs(self):
119 def find_kernel_specs(self):
117 """Returns a dict mapping kernel names to resource directories."""
120 """Returns a dict mapping kernel names to resource directories."""
118 d = {}
121 d = {}
119 for kernel_dir in self.kernel_dirs:
122 for kernel_dir in self.kernel_dirs:
120 d.update(_list_kernels_in(kernel_dir))
123 d.update(_list_kernels_in(kernel_dir))
121
124
122 if NATIVE_KERNEL_NAME not in d:
125 if NATIVE_KERNEL_NAME not in d:
123 d[NATIVE_KERNEL_NAME] = self._make_native_kernel_dir()
126 d[NATIVE_KERNEL_NAME] = self._make_native_kernel_dir()
124 return d
127 return d
125 # TODO: Caching?
128 # TODO: Caching?
126
129
127 def get_kernel_spec(self, kernel_name):
130 def get_kernel_spec(self, kernel_name):
128 """Returns a :class:`KernelSpec` instance for the given kernel_name.
131 """Returns a :class:`KernelSpec` instance for the given kernel_name.
129
132
130 Raises :exc:`NoSuchKernel` if the given kernel name is not found.
133 Raises :exc:`NoSuchKernel` if the given kernel name is not found.
131 """
134 """
132 if kernel_name == 'python':
135 if kernel_name == 'python':
133 kernel_name = NATIVE_KERNEL_NAME
136 kernel_name = NATIVE_KERNEL_NAME
134 d = self.find_kernel_specs()
137 d = self.find_kernel_specs()
135 try:
138 try:
136 resource_dir = d[kernel_name.lower()]
139 resource_dir = d[kernel_name.lower()]
137 except KeyError:
140 except KeyError:
138 raise NoSuchKernel(kernel_name)
141 raise NoSuchKernel(kernel_name)
139 return KernelSpec.from_resource_dir(resource_dir)
142 return KernelSpec.from_resource_dir(resource_dir)
140
143
141 def find_kernel_specs():
144 def find_kernel_specs():
142 """Returns a dict mapping kernel names to resource directories."""
145 """Returns a dict mapping kernel names to resource directories."""
143 return KernelSpecManager().find_kernel_specs()
146 return KernelSpecManager().find_kernel_specs()
144
147
145 def get_kernel_spec(kernel_name):
148 def get_kernel_spec(kernel_name):
146 """Returns a :class:`KernelSpec` instance for the given kernel_name.
149 """Returns a :class:`KernelSpec` instance for the given kernel_name.
147
150
148 Raises KeyError if the given kernel name is not found.
151 Raises KeyError if the given kernel name is not found.
149 """
152 """
150 return KernelSpecManager().get_kernel_spec(kernel_name) No newline at end of file
153 return KernelSpecManager().get_kernel_spec(kernel_name)
General Comments 0
You need to be logged in to leave comments. Login now