diff --git a/IPython/frontend/qt/console/frontend_widget.py b/IPython/frontend/qt/console/frontend_widget.py index e732d42..67ca1b5 100644 --- a/IPython/frontend/qt/console/frontend_widget.py +++ b/IPython/frontend/qt/console/frontend_widget.py @@ -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 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): # 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 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): """ 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 diff --git a/IPython/frontend/qt/console/ipythonqt.py b/IPython/frontend/qt/console/ipythonqt.py index ad5415d..74e7641 100644 --- a/IPython/frontend/qt/console/ipythonqt.py +++ b/IPython/frontend/qt/console/ipythonqt.py @@ -16,10 +16,10 @@ from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget 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 @@ class MainWindow(QtGui.QMainWindow): 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() diff --git a/IPython/utils/localinterfaces.py b/IPython/utils/localinterfaces.py new file mode 100644 index 0000000..4945223 --- /dev/null +++ b/IPython/utils/localinterfaces.py @@ -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] diff --git a/IPython/zmq/entry_point.py b/IPython/zmq/entry_point.py index a69d821..f2e206e 100644 --- a/IPython/zmq/entry_point.py +++ b/IPython/zmq/entry_point.py @@ -16,6 +16,7 @@ import zmq 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]') diff --git a/IPython/zmq/frontend.py b/IPython/zmq/frontend.py index 4dc73c9..90f3530 100755 --- a/IPython/zmq/frontend.py +++ b/IPython/zmq/frontend.py @@ -17,6 +17,7 @@ import uuid import zmq import session import completer +from IPython.utils.localinterfaces import LOCALHOST #----------------------------------------------------------------------------- # Classes and functions @@ -168,7 +169,7 @@ class InteractiveClient(object): 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' diff --git a/IPython/zmq/heartbeat.py b/IPython/zmq/heartbeat.py index 28c85d0..9ff2a4a 100644 --- a/IPython/zmq/heartbeat.py +++ b/IPython/zmq/heartbeat.py @@ -17,6 +17,8 @@ from threading import Thread import zmq +from IPython.utils.localinterfaces import LOCALHOST + #----------------------------------------------------------------------------- # Code #----------------------------------------------------------------------------- @@ -25,7 +27,7 @@ import zmq 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 diff --git a/IPython/zmq/ipkernel.py b/IPython/zmq/ipkernel.py index fe9030b..f271305 100755 --- a/IPython/zmq/ipkernel.py +++ b/IPython/zmq/ipkernel.py @@ -534,12 +534,15 @@ class GTKKernel(Kernel): # 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) diff --git a/IPython/zmq/kernelmanager.py b/IPython/zmq/kernelmanager.py index 46e70cd..089e519 100644 --- a/IPython/zmq/kernelmanager.py +++ b/IPython/zmq/kernelmanager.py @@ -31,6 +31,7 @@ from zmq.eventloop import ioloop # 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 @@ -38,8 +39,6 @@ from session import Session # Constants and exceptions #----------------------------------------------------------------------------- -LOCALHOST = '127.0.0.1' - class InvalidPortNumber(Exception): pass @@ -724,24 +723,26 @@ class KernelManager(HasTraits): """ 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 diff --git a/IPython/zmq/pykernel.py b/IPython/zmq/pykernel.py index 4938a2a..8a8298c 100755 --- a/IPython/zmq/pykernel.py +++ b/IPython/zmq/pykernel.py @@ -256,12 +256,15 @@ class Kernel(HasTraits): # 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): """ 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. @@ -286,9 +289,15 @@ def launch_kernel(xrep_port=0, pub_port=0, req_port=0, hb_port=0, (kernel_process, xrep_port, pub_port, req_port) where kernel_process is a Popen object and the ports are integers. """ + extra_arguments = [] + if ip is not None: + extra_arguments.append('--ip') + if isinstance(ip, basestring): + extra_arguments.append(ip) + return base_launch_kernel('from IPython.zmq.pykernel import main; main()', xrep_port, pub_port, req_port, hb_port, - independent) + independent, extra_arguments=extra_arguments) main = make_default_main(Kernel)