Permalink
Browse files

Merge branch 'minrk-keepkernel' into trunk

Improvements to the closing behavior of a Qt console regarding what
happens to the kernel and other consoles that may be active.  Now,
there is fine control on who is closed and who remains active.

Closes gh-164 (pull request).
  • Loading branch information...
2 parents 147b245 + ac47604 commit dc30beecb51872ae82e246fc0adfc841628a4d87 @fperez fperez committed Oct 21, 2010
@@ -101,6 +101,7 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
_CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
_ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
_input_splitter_class = InputSplitter
+ _local_kernel = False
#---------------------------------------------------------------------------
# 'object' interface
@@ -141,6 +142,9 @@ def __init__(self, *args, **kw):
# Connect signal handlers.
document = self._control.document()
document.contentsChange.connect(self._document_contents_change)
+
+ # set flag for whether we are connected via localhost
+ self._local_kernel = kw.get('local_kernel', FrontendWidget._local_kernel)
#---------------------------------------------------------------------------
# 'ConsoleWidget' public interface
@@ -366,14 +370,32 @@ def _handle_shutdown_reply(self, msg):
""" Handle shutdown signal, only if from other console.
"""
if not self._hidden and not self._is_from_this_session(msg):
- if not msg['content']['restart']:
- sys.exit(0)
- else:
- # we just got notified of a restart!
- time.sleep(0.25) # wait 1/4 sec to reset
- # lest the request for a new prompt
- # goes to the old kernel
- self.reset()
+ if self._local_kernel:
+ if not msg['content']['restart']:
+ sys.exit(0)
+ else:
+ # we just got notified of a restart!
+ time.sleep(0.25) # wait 1/4 sec to reset
+ # lest the request for a new prompt
+ # goes to the old kernel
+ self.reset()
+ else: # remote kernel, prompt on Kernel shutdown/reset
+ title = self.window().windowTitle()
+ if not msg['content']['restart']:
+ reply = QtGui.QMessageBox.question(self, title,
+ "Kernel has been shutdown permanently. Close the Console?",
+ QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
+ if reply == QtGui.QMessageBox.Yes:
+ sys.exit(0)
+ else:
+ reply = QtGui.QMessageBox.question(self, title,
+ "Kernel has been reset. Clear the Console?",
+ QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
+ if reply == QtGui.QMessageBox.Yes:
+ time.sleep(0.25) # wait 1/4 sec to reset
+ # lest the request for a new prompt
+ # goes to the old kernel
+ self.reset()
def _started_channels(self):
""" Called when the KernelManager channels have started listening or
@@ -16,10 +16,10 @@
from IPython.frontend.qt.kernelmanager import QtKernelManager
#-----------------------------------------------------------------------------
-# Constants
+# Network Constants
#-----------------------------------------------------------------------------
-LOCALHOST = '127.0.0.1'
+from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
#-----------------------------------------------------------------------------
# Classes
@@ -31,18 +31,24 @@ class MainWindow(QtGui.QMainWindow):
# 'object' interface
#---------------------------------------------------------------------------
- def __init__(self, app, frontend, existing=False):
+ def __init__(self, app, frontend, existing=False, may_close=True):
""" Create a MainWindow for the specified FrontendWidget.
The app is passed as an argument to allow for different
closing behavior depending on whether we are the Kernel's parent.
- If existing is True, then this Window does not own the Kernel.
+ If existing is True, then this Console does not own the Kernel.
+
+ If may_close is True, then this Console is permitted to close the kernel
"""
super(MainWindow, self).__init__()
self._app = app
self._frontend = frontend
self._existing = existing
+ if existing:
+ self._may_close = may_close
+ else:
+ self._may_close = True
self._frontend.exit_requested.connect(self.close)
self.setCentralWidget(frontend)
@@ -56,21 +62,47 @@ def closeEvent(self, event):
kernel_manager = self._frontend.kernel_manager
if kernel_manager and kernel_manager.channels_running:
title = self.window().windowTitle()
- reply = QtGui.QMessageBox.question(self, title,
- "Close just this console, or shutdown the kernel and close "+
- "all windows attached to it?",
- 'Cancel', 'Close Console', 'Close All')
- if reply == 2: # close All
- kernel_manager.shutdown_kernel()
- #kernel_manager.stop_channels()
- event.accept()
- elif reply == 1: # close Console
- if not self._existing:
- # I have the kernel: don't quit, just close the window
- self._app.setQuitOnLastWindowClosed(False)
- event.accept()
+ cancel = QtGui.QMessageBox.Cancel
+ okay = QtGui.QMessageBox.Ok
+ if self._may_close:
+ msg = "You are closing this Console window."
+ info = "Would you like to quit the Kernel and all attached Consoles as well?"
+ justthis = QtGui.QPushButton("&No, just this Console", self)
+ justthis.setShortcut('N')
+ closeall = QtGui.QPushButton("&Yes, quit everything", self)
+ closeall.setShortcut('Y')
+ box = QtGui.QMessageBox(QtGui.QMessageBox.Question, title, msg)
+ box.setInformativeText(info)
+ box.addButton(cancel)
+ box.addButton(justthis, QtGui.QMessageBox.NoRole)
+ box.addButton(closeall, QtGui.QMessageBox.YesRole)
+ box.setDefaultButton(closeall)
+ box.setEscapeButton(cancel)
+ reply = box.exec_()
+ if reply == 1: # close All
+ kernel_manager.shutdown_kernel()
+ #kernel_manager.stop_channels()
+ event.accept()
+ elif reply == 0: # close Console
+ if not self._existing:
+ # I have the kernel: don't quit, just close the window
+ self._app.setQuitOnLastWindowClosed(False)
+ self.deleteLater()
+ event.accept()
+ else:
+ event.ignore()
else:
- event.ignore()
+ reply = QtGui.QMessageBox.question(self, title,
+ "Are you sure you want to close this Console?"+
+ "\nThe Kernel and other Consoles will remain active.",
+ okay|cancel,
+ defaultButton=okay
+ )
+ if reply == okay:
+ event.accept()
+ else:
+ event.ignore()
+
#-----------------------------------------------------------------------------
# Main entry point
@@ -85,7 +117,11 @@ def main():
kgroup.add_argument('-e', '--existing', action='store_true',
help='connect to an existing kernel')
kgroup.add_argument('--ip', type=str, default=LOCALHOST,
- help='set the kernel\'s IP address [default localhost]')
+ help=\
+ "set the kernel\'s IP address [default localhost].\
+ If the IP address is something other than localhost, then \
+ Consoles on other machines will be able to connect\
+ to the Kernel, so be careful!")
kgroup.add_argument('--xreq', type=int, metavar='PORT', default=0,
help='set the XREQ channel port [default random]')
kgroup.add_argument('--sub', type=int, metavar='PORT', default=0,
@@ -124,29 +160,34 @@ def main():
sub_address=(args.ip, args.sub),
rep_address=(args.ip, args.rep),
hb_address=(args.ip, args.hb))
- if args.ip == LOCALHOST and not args.existing:
+ if not args.existing:
+ # if not args.ip in LOCAL_IPS+ALL_ALIAS:
+ # raise ValueError("Must bind a local ip, such as: %s"%LOCAL_IPS)
+
+ kwargs = dict(ip=args.ip)
if args.pure:
- kernel_manager.start_kernel(ipython=False)
+ kwargs['ipython']=False
elif args.pylab:
- kernel_manager.start_kernel(pylab=args.pylab)
- else:
- kernel_manager.start_kernel()
+ kwargs['pylab']=args.pylab
+
+ kernel_manager.start_kernel(**kwargs)
kernel_manager.start_channels()
+ local_kernel = (not args.existing) or args.ip in LOCAL_IPS
# Create the widget.
app = QtGui.QApplication([])
if args.pure:
kind = 'rich' if args.rich else 'plain'
- widget = FrontendWidget(kind=kind, paging=args.paging)
+ widget = FrontendWidget(kind=kind, paging=args.paging, local_kernel=local_kernel)
elif args.rich or args.pylab:
- widget = RichIPythonWidget(paging=args.paging)
+ widget = RichIPythonWidget(paging=args.paging, local_kernel=local_kernel)
else:
- widget = IPythonWidget(paging=args.paging)
+ widget = IPythonWidget(paging=args.paging, local_kernel=local_kernel)
widget.gui_completion = args.gui_completion
widget.kernel_manager = kernel_manager
# Create the main window.
- window = MainWindow(app, widget, args.existing)
+ window = MainWindow(app, widget, args.existing, may_close=local_kernel)
window.setWindowTitle('Python' if args.pure else 'IPython')
window.show()
@@ -0,0 +1,40 @@
+"""Simple utility for building a list of local IPs using the socket module.
+This module defines two constants:
+
+LOCALHOST : The loopback interface, or the first interface that points to this
+ machine. It will *almost* always be '127.0.0.1'
+
+LOCAL_IPS : A list of IP addresses, loopback first, that point to this machine.
+"""
+#-----------------------------------------------------------------------------
+# Copyright (C) 2010 The IPython Development Team
+#
+# Distributed under the terms of the BSD License. The full license is in
+# the file COPYING, distributed as part of this software.
+#-----------------------------------------------------------------------------
+
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+
+import socket
+
+#-----------------------------------------------------------------------------
+# Code
+#-----------------------------------------------------------------------------
+
+LOCAL_IPS = []
+try:
+ LOCAL_IPS = socket.gethostbyname_ex('localhost')[2]
+except socket.gaierror:
+ pass
+
+try:
+ LOCAL_IPS.extend(socket.gethostbyname_ex(socket.gethostname())[2])
+except socket.gaierror:
+ pass
+
+# include all-interface aliases: 0.0.0.0 and ''
+LOCAL_IPS.extend(['0.0.0.0', ''])
+
+LOCALHOST = LOCAL_IPS[0]
@@ -16,6 +16,7 @@
from IPython.core.ultratb import FormattedTB
from IPython.external.argparse import ArgumentParser
from IPython.utils import io
+from IPython.utils.localinterfaces import LOCALHOST
from displayhook import DisplayHook
from heartbeat import Heartbeat
from iostream import OutStream
@@ -40,7 +41,7 @@ def make_argument_parser():
kernel entry points.
"""
parser = ArgumentParser()
- parser.add_argument('--ip', type=str, default='127.0.0.1',
+ parser.add_argument('--ip', type=str, default=LOCALHOST,
help='set the kernel\'s IP address [default: local]')
parser.add_argument('--xrep', type=int, metavar='PORT', default=0,
help='set the XREP channel port [default: random]')
View
@@ -17,6 +17,7 @@
import zmq
import session
import completer
+from IPython.utils.localinterfaces import LOCALHOST
#-----------------------------------------------------------------------------
# Classes and functions
@@ -168,7 +169,7 @@ def interact(self):
def main():
# Defaults
#ip = '192.168.2.109'
- ip = '127.0.0.1'
+ ip = LOCALHOST
#ip = '99.146.222.252'
port_base = 5575
connection = ('tcp://%s' % ip) + ':%i'
View
@@ -17,6 +17,8 @@
import zmq
+from IPython.utils.localinterfaces import LOCALHOST
+
#-----------------------------------------------------------------------------
# Code
#-----------------------------------------------------------------------------
@@ -25,7 +27,7 @@
class Heartbeat(Thread):
"A simple ping-pong style heartbeat that runs in a thread."
- def __init__(self, context, addr=('127.0.0.1', 0)):
+ def __init__(self, context, addr=(LOCALHOST, 0)):
Thread.__init__(self)
self.context = context
self.addr = addr
View
@@ -534,12 +534,15 @@ def start(self):
# Kernel main and launch functions
#-----------------------------------------------------------------------------
-def launch_kernel(xrep_port=0, pub_port=0, req_port=0, hb_port=0,
+def launch_kernel(ip=None, xrep_port=0, pub_port=0, req_port=0, hb_port=0,
independent=False, pylab=False):
"""Launches a localhost kernel, binding to the specified ports.
Parameters
----------
+ ip : str, optional
+ The ip address the kernel will bind to.
+
xrep_port : int, optional
The port to use for XREP channel.
@@ -574,6 +577,10 @@ def launch_kernel(xrep_port=0, pub_port=0, req_port=0, hb_port=0,
extra_arguments.append('--pylab')
if isinstance(pylab, basestring):
extra_arguments.append(pylab)
+ if ip is not None:
+ extra_arguments.append('--ip')
+ if isinstance(ip, basestring):
+ extra_arguments.append(ip)
return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
xrep_port, pub_port, req_port, hb_port,
independent, extra_arguments)
@@ -31,15 +31,14 @@
# Local imports.
from IPython.utils import io
+from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
from session import Session
#-----------------------------------------------------------------------------
# Constants and exceptions
#-----------------------------------------------------------------------------
-LOCALHOST = '127.0.0.1'
-
class InvalidPortNumber(Exception):
pass
@@ -724,24 +723,26 @@ def start_kernel(self, **kw):
"""
xreq, sub, rep, hb = self.xreq_address, self.sub_address, \
self.rep_address, self.hb_address
- if xreq[0] != LOCALHOST or sub[0] != LOCALHOST or \
- rep[0] != LOCALHOST or hb[0] != LOCALHOST:
- raise RuntimeError("Can only launch a kernel on localhost."
+ if xreq[0] not in LOCAL_IPS or sub[0] not in LOCAL_IPS or \
+ rep[0] not in LOCAL_IPS or hb[0] not in LOCAL_IPS:
+ raise RuntimeError("Can only launch a kernel on a local interface. "
"Make sure that the '*_address' attributes are "
- "configured properly.")
-
+ "configured properly. "
+ "Currently valid addresses are: %s"%LOCAL_IPS
+ )
+
self._launch_args = kw.copy()
if kw.pop('ipython', True):
from ipkernel import launch_kernel
else:
from pykernel import launch_kernel
- self.kernel, xrep, pub, req, hb = launch_kernel(
+ self.kernel, xrep, pub, req, _hb = launch_kernel(
xrep_port=xreq[1], pub_port=sub[1],
req_port=rep[1], hb_port=hb[1], **kw)
- self.xreq_address = (LOCALHOST, xrep)
- self.sub_address = (LOCALHOST, pub)
- self.rep_address = (LOCALHOST, req)
- self.hb_address = (LOCALHOST, hb)
+ self.xreq_address = (xreq[0], xrep)
+ self.sub_address = (sub[0], pub)
+ self.rep_address = (rep[0], req)
+ self.hb_address = (hb[0], _hb)
def shutdown_kernel(self, restart=False):
""" Attempts to the stop the kernel process cleanly. If the kernel
Oops, something went wrong.

0 comments on commit dc30bee

Please sign in to comment.