1
2# Copyright (c) Jupyter Development Team.
3# Distributed under the terms of the Modified BSD License.
4
5import logging
6import signal
7import queue
8import time
9import sys
10
11from traitlets.config import catch_config_error
12from traitlets import (
13    Instance, Dict, Unicode, Bool, List, CUnicode, Any, Float
14)
15from jupyter_core.application import (
16    JupyterApp, base_flags, base_aliases
17)
18
19from . import __version__
20from .consoleapp import JupyterConsoleApp, app_aliases, app_flags
21
22OUTPUT_TIMEOUT = 10
23
24# copy flags from mixin:
25flags = dict(base_flags)
26# start with mixin frontend flags:
27frontend_flags = dict(app_flags)
28# update full dict with frontend flags:
29flags.update(frontend_flags)
30
31# copy flags from mixin
32aliases = dict(base_aliases)
33# start with mixin frontend flags
34frontend_aliases = dict(app_aliases)
35# load updated frontend flags into full dict
36aliases.update(frontend_aliases)
37
38# get flags&aliases into sets, and remove a couple that
39# shouldn't be scrubbed from backend flags:
40frontend_aliases = set(frontend_aliases.keys())
41frontend_flags = set(frontend_flags.keys())
42
43class RunApp(JupyterApp, JupyterConsoleApp):
44    version = __version__
45    name = "jupyter run"
46    description = """Run Jupyter kernel code."""
47    flags = Dict(flags)
48    aliases = Dict(aliases)
49    frontend_aliases = Any(frontend_aliases)
50    frontend_flags = Any(frontend_flags)
51    kernel_timeout = Float(60, config=True,
52        help="""Timeout for giving up on a kernel (in seconds).
53
54        On first connect and restart, the console tests whether the
55        kernel is running and responsive by sending kernel_info_requests.
56        This sets the timeout in seconds for how long the kernel can take
57        before being presumed dead.
58        """
59    )
60
61    def parse_command_line(self, argv=None):
62        super().parse_command_line(argv)
63        self.build_kernel_argv(self.extra_args)
64        self.filenames_to_run = self.extra_args[:]
65
66    @catch_config_error
67    def initialize(self, argv=None):
68        self.log.debug("jupyter run: initialize...")
69        super().initialize(argv)
70        JupyterConsoleApp.initialize(self)
71        signal.signal(signal.SIGINT, self.handle_sigint)
72        self.init_kernel_info()
73
74    def handle_sigint(self, *args):
75        if self.kernel_manager:
76            self.kernel_manager.interrupt_kernel()
77        else:
78            self.log.error("Cannot interrupt kernels we didn't start.\n")
79
80    def init_kernel_info(self):
81        """Wait for a kernel to be ready, and store kernel info"""
82        timeout = self.kernel_timeout
83        tic = time.time()
84        self.kernel_client.hb_channel.unpause()
85        msg_id = self.kernel_client.kernel_info()
86        while True:
87            try:
88                reply = self.kernel_client.get_shell_msg(timeout=1)
89            except queue.Empty as e:
90                if (time.time() - tic) > timeout:
91                    raise RuntimeError("Kernel didn't respond to kernel_info_request") from e
92            else:
93                if reply['parent_header'].get('msg_id') == msg_id:
94                    self.kernel_info = reply['content']
95                    return
96
97    def start(self):
98        self.log.debug("jupyter run: starting...")
99        super().start()
100        if self.filenames_to_run:
101            for filename in self.filenames_to_run:
102                self.log.debug("jupyter run: executing `%s`" % filename)
103                with open(filename) as fp:
104                    code = fp.read()
105                    reply = self.kernel_client.execute_interactive(code, timeout=OUTPUT_TIMEOUT)
106                    return_code = 0 if reply['content']['status'] == 'ok' else 1
107                    if return_code:
108                        raise Exception("jupyter-run error running '%s'" % filename)
109        else:
110            code = sys.stdin.read()
111            reply = self.kernel_client.execute_interactive(code, timeout=OUTPUT_TIMEOUT)
112            return_code = 0 if reply['content']['status'] == 'ok' else 1
113            if return_code:
114                raise Exception("jupyter-run error running 'stdin'")
115
116main = launch_new_instance = RunApp.launch_instance
117
118if __name__ == '__main__':
119    main()
120