Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds jupyter-run to jupyter_client #184

Merged
merged 11 commits into from
Feb 7, 2017
146 changes: 146 additions & 0 deletions jupyter_client/runapp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.

from __future__ import print_function

import logging
import signal
import time
import sys

from traitlets.config import catch_config_error
from traitlets import (
Instance, Dict, Unicode, Bool, List, CUnicode, Any, Float
)
from jupyter_core.application import (
JupyterApp, base_flags, base_aliases
)

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

try:
import queue
except ImportError:
import Queue as queue

OUTPUT_TIMEOUT = 10

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

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

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

class RunApp(JupyterApp, JupyterConsoleApp):
version = __version__
name = "jupyter run"
description = """Run Jupyter kernel code."""
flags = Dict(flags)
aliases = Dict(aliases)
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=None):
super(RunApp, self).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=None):
self.log.debug("jupyter run: initialize...")
super(RunApp, self).initialize(argv)
JupyterConsoleApp.initialize(self)
signal.signal(signal.SIGINT, self.handle_sigint)
self.init_kernel_info()

def handle_sigint(self, *args):
if self.kernel_manager:
self.kernel_manager.interrupt_kernel()
else:
print("", file=sys.stderr)
error("Cannot interrupt kernels we didn't start.\n")

def init_kernel_info(self):
"""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:
if (time.time() - tic) > timeout:
raise RuntimeError("Kernel didn't respond to kernel_info_request")
else:
if reply['parent_header'].get('msg_id') == msg_id:
self.kernel_info = reply['content']
return

def start(self):
self.log.debug("jupyter run: starting...")
super(RunApp, self).start()
for filename in self.filenames_to_run:
self.log.debug("jupyter run: executing `%s`" % filename)
cell = open(filename).read()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As is good practice in general, we should ensure the file is closed deterministically:

with open(filename) as f:
    cell = f.read()

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch; thanks. Pushed fix.

self.run_cell(cell)

def run_cell(self, cell):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have several run_cell methods, so I'd rather call this something more precise. Maybe execute_printing_output?

"""
Run a cell on a KernelClient
Any output from the cell will be displayed.
"""
msg_id = self.kernel_client.execute(cell)
while True:
try:
msg = self.kernel_client.get_iopub_msg(timeout=OUTPUT_TIMEOUT)
except queue.Empty:
raise TimeoutError("Timeout waiting for kernel output")

if msg['parent_header'].get('msg_id') != msg_id:
continue
msg_type = msg['header']['msg_type']
content = msg['content']
if msg_type == 'status':
if content['execution_state'] == 'idle':
# idle means output is done
break
elif msg_type == 'stream':
stream = getattr(sys, content['name'])
stream.write(content['text'])
elif msg_type in ('display_data', 'execute_result', 'error'):
if msg_type == 'error':
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This nesting is a bit odd - why not have elif msg_type == 'error' broken out separately before display_data and execute_result.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is cut and paste from Min's link:

https://github.com/dask/distributed/pull/370/files#diff-b7f744240c50427e50e9c844a13a00fdR43

so @minrk can comment on that.

print('\n'.join(content['traceback']), file=sys.stderr)
else:
sys.stdout.write(content['data'].get('text/plain', ''))
else:
pass

main = launch_new_instance = RunApp.launch_instance

if __name__ == '__main__':
main()
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
setup_args['entry_points'] = {
'console_scripts': [
'jupyter-kernelspec = jupyter_client.kernelspecapp:KernelSpecApp.launch_instance',
'jupyter-run = jupyter_client.runapp:RunApp.launch_instance',
]
}
setup_args.pop('scripts', None)
Expand Down