Show More
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 |
@@ -0,0 +1,27 b'' | |||||
|
1 | from tornado import web | |||
|
2 | from ..base.handlers import IPythonHandler | |||
|
3 | from ..services.kernelspecs.handlers import kernel_name_regex | |||
|
4 | ||||
|
5 | class KernelSpecResourceHandler(web.StaticFileHandler, IPythonHandler): | |||
|
6 | SUPPORTED_METHODS = ('GET', 'HEAD') | |||
|
7 | ||||
|
8 | def initialize(self): | |||
|
9 | web.StaticFileHandler.initialize(self, path='') | |||
|
10 | ||||
|
11 | @web.authenticated | |||
|
12 | def get(self, kernel_name, path, include_body=True): | |||
|
13 | ksm = self.kernel_spec_manager | |||
|
14 | try: | |||
|
15 | self.root = ksm.get_kernel_spec(kernel_name).resource_dir | |||
|
16 | except KeyError: | |||
|
17 | raise web.HTTPError(404, u'Kernel spec %s not found' % kernel_name) | |||
|
18 | self.log.debug("Serving kernel resource from: %s", self.root) | |||
|
19 | return web.StaticFileHandler.get(self, path, include_body=include_body) | |||
|
20 | ||||
|
21 | @web.authenticated | |||
|
22 | def head(self, kernel_name, path): | |||
|
23 | self.get(kernel_name, path, include_body=False) | |||
|
24 | ||||
|
25 | default_handlers = [ | |||
|
26 | (r"/kernelspecs/%s/(?P<path>.*)" % kernel_name_regex, KernelSpecResourceHandler), | |||
|
27 | ] No newline at end of file |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 |
@@ -0,0 +1,50 b'' | |||||
|
1 | """Tornado handlers for kernel specifications.""" | |||
|
2 | ||||
|
3 | # Copyright (c) IPython Development Team. | |||
|
4 | # Distributed under the terms of the Modified BSD License. | |||
|
5 | import json | |||
|
6 | from tornado import web | |||
|
7 | ||||
|
8 | from ...base.handlers import IPythonHandler, json_errors | |||
|
9 | ||||
|
10 | ||||
|
11 | class MainKernelSpecHandler(IPythonHandler): | |||
|
12 | SUPPORTED_METHODS = ('GET',) | |||
|
13 | ||||
|
14 | @web.authenticated | |||
|
15 | @json_errors | |||
|
16 | def get(self): | |||
|
17 | ksm = self.kernel_spec_manager | |||
|
18 | results = [] | |||
|
19 | for kernel_name in ksm.find_kernel_specs(): | |||
|
20 | d = ksm.get_kernel_spec(kernel_name).to_dict() | |||
|
21 | d['name'] = kernel_name | |||
|
22 | results.append(d) | |||
|
23 | ||||
|
24 | self.set_header("Content-Type", 'application/json') | |||
|
25 | self.finish(json.dumps(results)) | |||
|
26 | ||||
|
27 | ||||
|
28 | class KernelSpecHandler(IPythonHandler): | |||
|
29 | SUPPORTED_METHODS = ('GET',) | |||
|
30 | ||||
|
31 | @web.authenticated | |||
|
32 | @json_errors | |||
|
33 | def get(self, kernel_name): | |||
|
34 | ksm = self.kernel_spec_manager | |||
|
35 | try: | |||
|
36 | kernelspec = ksm.get_kernel_spec(kernel_name) | |||
|
37 | except KeyError: | |||
|
38 | raise web.HTTPError(404, u'Kernel spec %s not found' % kernel_name) | |||
|
39 | self.set_header("Content-Type", 'application/json') | |||
|
40 | self.finish(kernelspec.to_json()) | |||
|
41 | ||||
|
42 | ||||
|
43 | # URL to handler mappings | |||
|
44 | ||||
|
45 | kernel_name_regex = r"(?P<kernel_name>\w+)" | |||
|
46 | ||||
|
47 | default_handlers = [ | |||
|
48 | (r"/api/kernelspecs", MainKernelSpecHandler), | |||
|
49 | (r"/api/kernelspecs/%s" % kernel_name_regex, KernelSpecHandler), | |||
|
50 | ] |
@@ -0,0 +1,97 b'' | |||||
|
1 | # coding: utf-8 | |||
|
2 | """Test the kernel specs webservice API.""" | |||
|
3 | ||||
|
4 | import errno | |||
|
5 | import io | |||
|
6 | import json | |||
|
7 | import os | |||
|
8 | ||||
|
9 | pjoin = os.path.join | |||
|
10 | ||||
|
11 | import requests | |||
|
12 | ||||
|
13 | from IPython.html.utils import url_path_join | |||
|
14 | from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error | |||
|
15 | ||||
|
16 | # Copied from IPython.kernel.tests.test_kernelspec so updating that doesn't | |||
|
17 | # break these tests | |||
|
18 | sample_kernel_json = {'argv':['cat', '{connection_file}'], | |||
|
19 | 'display_name':'Test kernel', | |||
|
20 | 'language':'bash', | |||
|
21 | } | |||
|
22 | ||||
|
23 | some_resource = u"The very model of a modern major general" | |||
|
24 | ||||
|
25 | ||||
|
26 | class KernelSpecAPI(object): | |||
|
27 | """Wrapper for notebook API calls.""" | |||
|
28 | def __init__(self, base_url): | |||
|
29 | self.base_url = base_url | |||
|
30 | ||||
|
31 | def _req(self, verb, path, body=None): | |||
|
32 | response = requests.request(verb, | |||
|
33 | url_path_join(self.base_url, path), | |||
|
34 | data=body, | |||
|
35 | ) | |||
|
36 | response.raise_for_status() | |||
|
37 | return response | |||
|
38 | ||||
|
39 | def list(self): | |||
|
40 | return self._req('GET', 'api/kernelspecs') | |||
|
41 | ||||
|
42 | def kernel_spec_info(self, name): | |||
|
43 | return self._req('GET', url_path_join('api/kernelspecs', name)) | |||
|
44 | ||||
|
45 | def kernel_resource(self, name, path): | |||
|
46 | return self._req('GET', url_path_join('kernelspecs', name, path)) | |||
|
47 | ||||
|
48 | class APITest(NotebookTestBase): | |||
|
49 | """Test the kernelspec web service API""" | |||
|
50 | def setUp(self): | |||
|
51 | ipydir = self.ipython_dir.name | |||
|
52 | sample_kernel_dir = pjoin(ipydir, 'kernels', 'sample') | |||
|
53 | try: | |||
|
54 | os.makedirs(sample_kernel_dir) | |||
|
55 | except OSError as e: | |||
|
56 | if e.errno != errno.EEXIST: | |||
|
57 | raise | |||
|
58 | ||||
|
59 | with open(pjoin(sample_kernel_dir, 'kernel.json'), 'w') as f: | |||
|
60 | json.dump(sample_kernel_json, f) | |||
|
61 | ||||
|
62 | with io.open(pjoin(sample_kernel_dir, 'resource.txt'), 'w', | |||
|
63 | encoding='utf-8') as f: | |||
|
64 | f.write(some_resource) | |||
|
65 | ||||
|
66 | self.ks_api = KernelSpecAPI(self.base_url()) | |||
|
67 | ||||
|
68 | def test_list_kernelspecs(self): | |||
|
69 | specs = self.ks_api.list().json() | |||
|
70 | assert isinstance(specs, list) | |||
|
71 | ||||
|
72 | # 2: the sample kernelspec created in setUp, and the native Python kernel | |||
|
73 | self.assertEqual(len(specs), 2) | |||
|
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 | |||
|
79 | ||||
|
80 | def test_get_kernelspec(self): | |||
|
81 | spec = self.ks_api.kernel_spec_info('Sample').json() # Case insensitive | |||
|
82 | self.assertEqual(spec['language'], 'bash') | |||
|
83 | ||||
|
84 | def test_get_nonexistant_kernelspec(self): | |||
|
85 | with assert_http_error(404): | |||
|
86 | self.ks_api.kernel_spec_info('nonexistant') | |||
|
87 | ||||
|
88 | def test_get_kernel_resource_file(self): | |||
|
89 | res = self.ks_api.kernel_resource('sAmple', 'resource.txt') | |||
|
90 | self.assertEqual(res.text, some_resource) | |||
|
91 | ||||
|
92 | def test_get_nonexistant_resource(self): | |||
|
93 | with assert_http_error(404): | |||
|
94 | self.ks_api.kernel_resource('nonexistant', 'resource.txt') | |||
|
95 | ||||
|
96 | with assert_http_error(404): | |||
|
97 | self.ks_api.kernel_resource('sample', 'nonexistant.txt') |
@@ -159,6 +159,10 b' class IPythonHandler(AuthenticatedHandler):' | |||||
159 | return self.settings['session_manager'] |
|
159 | return self.settings['session_manager'] | |
160 |
|
160 | |||
161 | @property |
|
161 | @property | |
|
162 | def kernel_spec_manager(self): | |||
|
163 | return self.settings['kernel_spec_manager'] | |||
|
164 | ||||
|
165 | @property | |||
162 | def project_dir(self): |
|
166 | def project_dir(self): | |
163 | return self.notebook_manager.notebook_dir |
|
167 | return self.notebook_manager.notebook_dir | |
164 |
|
168 |
@@ -67,12 +67,13 b' from IPython.core.application import (' | |||||
67 | ) |
|
67 | ) | |
68 | from IPython.core.profiledir import ProfileDir |
|
68 | from IPython.core.profiledir import ProfileDir | |
69 | from IPython.kernel import KernelManager |
|
69 | from IPython.kernel import KernelManager | |
|
70 | from IPython.kernel.kernelspec import KernelSpecManager | |||
70 | from IPython.kernel.zmq.session import default_secure, Session |
|
71 | from IPython.kernel.zmq.session import default_secure, Session | |
71 | from IPython.nbformat.sign import NotebookNotary |
|
72 | from IPython.nbformat.sign import NotebookNotary | |
72 | from IPython.utils.importstring import import_item |
|
73 | from IPython.utils.importstring import import_item | |
73 | from IPython.utils import submodule |
|
74 | from IPython.utils import submodule | |
74 | from IPython.utils.traitlets import ( |
|
75 | from IPython.utils.traitlets import ( | |
75 | Dict, Unicode, Integer, List, Bool, Bytes, |
|
76 | Dict, Unicode, Integer, List, Bool, Bytes, Instance, | |
76 | DottedObjectName, TraitError, |
|
77 | DottedObjectName, TraitError, | |
77 | ) |
|
78 | ) | |
78 | from IPython.utils import py3compat |
|
79 | from IPython.utils import py3compat | |
@@ -118,19 +119,21 b' def load_handlers(name):' | |||||
118 | class NotebookWebApplication(web.Application): |
|
119 | class NotebookWebApplication(web.Application): | |
119 |
|
120 | |||
120 | def __init__(self, ipython_app, kernel_manager, notebook_manager, |
|
121 | def __init__(self, ipython_app, kernel_manager, notebook_manager, | |
121 |
cluster_manager, session_manager, log, |
|
122 | cluster_manager, session_manager, kernel_spec_manager, log, | |
122 | settings_overrides, jinja_env_options): |
|
123 | base_url, settings_overrides, jinja_env_options): | |
123 |
|
124 | |||
124 | settings = self.init_settings( |
|
125 | settings = self.init_settings( | |
125 | ipython_app, kernel_manager, notebook_manager, cluster_manager, |
|
126 | ipython_app, kernel_manager, notebook_manager, cluster_manager, | |
126 | session_manager, log, base_url, settings_overrides, jinja_env_options) |
|
127 | session_manager, kernel_spec_manager, log, base_url, | |
|
128 | settings_overrides, jinja_env_options) | |||
127 | handlers = self.init_handlers(settings) |
|
129 | handlers = self.init_handlers(settings) | |
128 |
|
130 | |||
129 | super(NotebookWebApplication, self).__init__(handlers, **settings) |
|
131 | super(NotebookWebApplication, self).__init__(handlers, **settings) | |
130 |
|
132 | |||
131 | def init_settings(self, ipython_app, kernel_manager, notebook_manager, |
|
133 | def init_settings(self, ipython_app, kernel_manager, notebook_manager, | |
132 |
cluster_manager, session_manager, |
|
134 | cluster_manager, session_manager, kernel_spec_manager, | |
133 |
settings_overrides, |
|
135 | log, base_url, settings_overrides, | |
|
136 | jinja_env_options=None): | |||
134 | # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and |
|
137 | # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and | |
135 | # base_url will always be unicode, which will in turn |
|
138 | # base_url will always be unicode, which will in turn | |
136 | # make the patterns unicode, and ultimately result in unicode |
|
139 | # make the patterns unicode, and ultimately result in unicode | |
@@ -162,6 +165,7 b' class NotebookWebApplication(web.Application):' | |||||
162 | notebook_manager=notebook_manager, |
|
165 | notebook_manager=notebook_manager, | |
163 | cluster_manager=cluster_manager, |
|
166 | cluster_manager=cluster_manager, | |
164 | session_manager=session_manager, |
|
167 | session_manager=session_manager, | |
|
168 | kernel_spec_manager=kernel_spec_manager, | |||
165 |
|
169 | |||
166 | # IPython stuff |
|
170 | # IPython stuff | |
167 | nbextensions_path = ipython_app.nbextensions_path, |
|
171 | nbextensions_path = ipython_app.nbextensions_path, | |
@@ -183,11 +187,13 b' class NotebookWebApplication(web.Application):' | |||||
183 | handlers.extend(load_handlers('auth.logout')) |
|
187 | handlers.extend(load_handlers('auth.logout')) | |
184 | handlers.extend(load_handlers('notebook.handlers')) |
|
188 | handlers.extend(load_handlers('notebook.handlers')) | |
185 | handlers.extend(load_handlers('nbconvert.handlers')) |
|
189 | handlers.extend(load_handlers('nbconvert.handlers')) | |
|
190 | handlers.extend(load_handlers('kernelspecs.handlers')) | |||
186 | handlers.extend(load_handlers('services.kernels.handlers')) |
|
191 | handlers.extend(load_handlers('services.kernels.handlers')) | |
187 | handlers.extend(load_handlers('services.notebooks.handlers')) |
|
192 | handlers.extend(load_handlers('services.notebooks.handlers')) | |
188 | handlers.extend(load_handlers('services.clusters.handlers')) |
|
193 | handlers.extend(load_handlers('services.clusters.handlers')) | |
189 | handlers.extend(load_handlers('services.sessions.handlers')) |
|
194 | handlers.extend(load_handlers('services.sessions.handlers')) | |
190 | handlers.extend(load_handlers('services.nbconvert.handlers')) |
|
195 | handlers.extend(load_handlers('services.nbconvert.handlers')) | |
|
196 | handlers.extend(load_handlers('services.kernelspecs.handlers')) | |||
191 | # FIXME: /files/ should be handled by the Contents service when it exists |
|
197 | # FIXME: /files/ should be handled by the Contents service when it exists | |
192 | nbm = settings['notebook_manager'] |
|
198 | nbm = settings['notebook_manager'] | |
193 | if hasattr(nbm, 'notebook_dir'): |
|
199 | if hasattr(nbm, 'notebook_dir'): | |
@@ -510,6 +516,11 b' class NotebookApp(BaseIPythonApplication):' | |||||
510 | help='The cluster manager class to use.' |
|
516 | help='The cluster manager class to use.' | |
511 | ) |
|
517 | ) | |
512 |
|
518 | |||
|
519 | kernel_spec_manager = Instance(KernelSpecManager) | |||
|
520 | ||||
|
521 | def _kernel_spec_manager_default(self): | |||
|
522 | return KernelSpecManager(ipython_dir=self.ipython_dir) | |||
|
523 | ||||
513 | trust_xheaders = Bool(False, config=True, |
|
524 | trust_xheaders = Bool(False, config=True, | |
514 | help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers" |
|
525 | help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers" | |
515 | "sent by the upstream reverse proxy. Necessary if the proxy handles SSL") |
|
526 | "sent by the upstream reverse proxy. Necessary if the proxy handles SSL") | |
@@ -616,7 +627,7 b' class NotebookApp(BaseIPythonApplication):' | |||||
616 | """initialize tornado webapp and httpserver""" |
|
627 | """initialize tornado webapp and httpserver""" | |
617 | self.web_app = NotebookWebApplication( |
|
628 | self.web_app = NotebookWebApplication( | |
618 | self, self.kernel_manager, self.notebook_manager, |
|
629 | self, self.kernel_manager, self.notebook_manager, | |
619 | self.cluster_manager, self.session_manager, |
|
630 | self.cluster_manager, self.session_manager, self.kernel_spec_manager, | |
620 | self.log, self.base_url, self.webapp_settings, |
|
631 | self.log, self.base_url, self.webapp_settings, | |
621 | self.jinja_environment_options |
|
632 | self.jinja_environment_options | |
622 | ) |
|
633 | ) |
@@ -50,6 +50,17 b' class KernelSpec(HasTraits):' | |||||
50 | with io.open(kernel_file, 'r', encoding='utf-8') as f: |
|
50 | with io.open(kernel_file, 'r', encoding='utf-8') as f: | |
51 | kernel_dict = json.load(f) |
|
51 | kernel_dict = json.load(f) | |
52 | return cls(resource_dir=resource_dir, **kernel_dict) |
|
52 | return cls(resource_dir=resource_dir, **kernel_dict) | |
|
53 | ||||
|
54 | def to_dict(self): | |||
|
55 | return dict(argv=self.argv, | |||
|
56 | env=self.env, | |||
|
57 | display_name=self.display_name, | |||
|
58 | language=self.language, | |||
|
59 | codemirror_mode=self.codemirror_mode, | |||
|
60 | ) | |||
|
61 | ||||
|
62 | def to_json(self): | |||
|
63 | return json.dumps(self.to_dict()) | |||
53 |
|
64 | |||
54 | def _is_kernel_dir(path): |
|
65 | def _is_kernel_dir(path): | |
55 | """Is ``path`` a kernel directory?""" |
|
66 | """Is ``path`` a kernel directory?""" |
General Comments 0
You need to be logged in to leave comments.
Login now