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