File size: 4,684 Bytes
d1ceb73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
"""A Jupyter console app to run files."""
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import queue
import signal
import sys
import time
import typing as t

from jupyter_core.application import JupyterApp, base_aliases, base_flags
from traitlets import Any, Dict, Float
from traitlets.config import catch_config_error

from . import __version__
from .consoleapp import JupyterConsoleApp, app_aliases, app_flags

OUTPUT_TIMEOUT = 10

# copy flags from mixin:
flags = dict(base_flags)
# start with mixin frontend flags:
frontend_flags_dict = dict(app_flags)
# update full dict with frontend flags:
flags.update(frontend_flags_dict)

# copy flags from mixin
aliases = dict(base_aliases)
# start with mixin frontend flags
frontend_aliases_dict = dict(app_aliases)
# load updated frontend flags into full dict
aliases.update(frontend_aliases_dict)

# get flags&aliases into sets, and remove a couple that
# shouldn't be scrubbed from backend flags:
frontend_aliases = set(frontend_aliases_dict.keys())
frontend_flags = set(frontend_flags_dict.keys())


class RunApp(JupyterApp, JupyterConsoleApp):  # type:ignore[misc]
    """An Jupyter Console app to run files."""

    version = __version__
    name = "jupyter run"
    description = """Run Jupyter kernel code."""
    flags = Dict(flags)  # type:ignore[assignment]
    aliases = Dict(aliases)  # type:ignore[assignment]
    frontend_aliases = Any(frontend_aliases)
    frontend_flags = Any(frontend_flags)
    kernel_timeout = Float(
        60,
        config=True,
        help="""Timeout for giving up on a kernel (in seconds).

        On first connect and restart, the console tests whether the
        kernel is running and responsive by sending kernel_info_requests.
        This sets the timeout in seconds for how long the kernel can take
        before being presumed dead.
        """,
    )

    def parse_command_line(self, argv: list[str] | None = None) -> None:
        """Parse the command line arguments."""
        super().parse_command_line(argv)
        self.build_kernel_argv(self.extra_args)
        self.filenames_to_run = self.extra_args[:]

    @catch_config_error
    def initialize(self, argv: list[str] | None = None) -> None:  # type:ignore[override]
        """Initialize the app."""
        self.log.debug("jupyter run: initialize...")
        super().initialize(argv)
        JupyterConsoleApp.initialize(self)
        signal.signal(signal.SIGINT, self.handle_sigint)
        self.init_kernel_info()

    def handle_sigint(self, *args: t.Any) -> None:
        """Handle SIGINT."""
        if self.kernel_manager:
            self.kernel_manager.interrupt_kernel()
        else:
            self.log.error("Cannot interrupt kernels we didn't start.\n")

    def init_kernel_info(self) -> None:
        """Wait for a kernel to be ready, and store kernel info"""
        timeout = self.kernel_timeout
        tic = time.time()
        self.kernel_client.hb_channel.unpause()
        msg_id = self.kernel_client.kernel_info()
        while True:
            try:
                reply = self.kernel_client.get_shell_msg(timeout=1)
            except queue.Empty as e:
                if (time.time() - tic) > timeout:
                    msg = "Kernel didn't respond to kernel_info_request"
                    raise RuntimeError(msg) from e
            else:
                if reply["parent_header"].get("msg_id") == msg_id:
                    self.kernel_info = reply["content"]
                    return

    def start(self) -> None:
        """Start the application."""
        self.log.debug("jupyter run: starting...")
        super().start()
        if self.filenames_to_run:
            for filename in self.filenames_to_run:
                self.log.debug("jupyter run: executing `%s`", filename)
                with open(filename) as fp:
                    code = fp.read()
                    reply = self.kernel_client.execute_interactive(code, timeout=OUTPUT_TIMEOUT)
                    return_code = 0 if reply["content"]["status"] == "ok" else 1
                    if return_code:
                        raise Exception("jupyter-run error running '%s'" % filename)
        else:
            code = sys.stdin.read()
            reply = self.kernel_client.execute_interactive(code, timeout=OUTPUT_TIMEOUT)
            return_code = 0 if reply["content"]["status"] == "ok" else 1
            if return_code:
                msg = "jupyter-run error running 'stdin'"
                raise Exception(msg)


main = launch_new_instance = RunApp.launch_instance

if __name__ == "__main__":
    main()