Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

merge #492 newapp-qt

  • Loading branch information...
commit f6b70295753883a0b7406cbb91bd7536fc19b20c 2 parents abf93be + fffc83f
Min RK authored
25 IPython/config/profile/default/ipython_config.py
View
@@ -163,3 +163,28 @@
# Only write to the database every n commands - this can save disk
# access (and hence power) over the default of writing on every command.
# c.HistoryManager.db_cache_size = 0
+
+#-----------------------------------------------------------------------------
+# QtConsole configuration
+#-----------------------------------------------------------------------------
+
+# set the preferred typeface and font size. The default typeface is:
+# 'Consolas' on Windows (fallback to 'Courier')
+# 'Monaco' on OSX
+# 'Monospace' elsewhere
+# c.ConsoleWidget.font_family = "Consolas"
+
+# The point fontsize. Leave as zero to let Qt decide on the starting font size.
+# While running, you can change the font size with ctrl- +/-
+# c.ConsoleWidget.font_size = 0
+
+# set the pygments syntax-highlighting style:
+# c.IPythonWidget.syntax_style = 'default'
+
+# Configure the prompts:
+# c.IPythonWidget.in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
+# c.IPythonWidget.out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
+
+# set the editor - this must be a *GUI* editor, like notepad/gedit/TextMate
+# There is no default on systems other than Windows.
+# c.IPythonWidget.editor = 'notepad'
8 IPython/frontend/qt/base_frontend_mixin.py
View
@@ -30,8 +30,8 @@ def _set_kernel_manager(self, kernel_manager):
# Disconnect the old kernel manager's channels.
old_manager.sub_channel.message_received.disconnect(self._dispatch)
- old_manager.xreq_channel.message_received.disconnect(self._dispatch)
- old_manager.rep_channel.message_received.disconnect(self._dispatch)
+ old_manager.shell_channel.message_received.disconnect(self._dispatch)
+ old_manager.stdin_channel.message_received.disconnect(self._dispatch)
old_manager.hb_channel.kernel_died.disconnect(
self._handle_kernel_died)
@@ -50,8 +50,8 @@ def _set_kernel_manager(self, kernel_manager):
# Connect the new kernel manager's channels.
kernel_manager.sub_channel.message_received.connect(self._dispatch)
- kernel_manager.xreq_channel.message_received.connect(self._dispatch)
- kernel_manager.rep_channel.message_received.connect(self._dispatch)
+ kernel_manager.shell_channel.message_received.connect(self._dispatch)
+ kernel_manager.stdin_channel.message_received.connect(self._dispatch)
kernel_manager.hb_channel.kernel_died.connect(self._handle_kernel_died)
# Handle the case where the kernel manager started channels before
96 IPython/frontend/qt/console/console_widget.py
View
@@ -19,7 +19,7 @@
from IPython.config.configurable import Configurable
from IPython.frontend.qt.rich_text import HtmlExporter
from IPython.frontend.qt.util import MetaQObjectHasTraits, get_font
-from IPython.utils.traitlets import Bool, Enum, Int
+from IPython.utils.traitlets import Bool, Enum, Int, Unicode
from ansi_code_processor import QtAnsiCodeProcessor
from completion_widget import CompletionWidget
from kill_ring import QtKillRing
@@ -55,33 +55,61 @@ class ConsoleWidget(Configurable, QtGui.QWidget):
#------ Configuration ------------------------------------------------------
- # Whether to process ANSI escape codes.
- ansi_codes = Bool(True, config=True)
-
- # The maximum number of lines of text before truncation. Specifying a
- # non-positive number disables text truncation (not recommended).
- buffer_size = Int(500, config=True)
-
- # Whether to use a list widget or plain text output for tab completion.
- gui_completion = Bool(False, config=True)
-
- # The type of underlying text widget to use. Valid values are 'plain', which
- # specifies a QPlainTextEdit, and 'rich', which specifies a QTextEdit.
+ ansi_codes = Bool(True, config=True,
+ help="Whether to process ANSI escape codes."
+ )
+ buffer_size = Int(500, config=True,
+ help="""
+ The maximum number of lines of text before truncation. Specifying a
+ non-positive number disables text truncation (not recommended).
+ """
+ )
+ gui_completion = Bool(False, config=True,
+ help="Use a list widget instead of plain text output for tab completion."
+ )
# NOTE: this value can only be specified during initialization.
- kind = Enum(['plain', 'rich'], default_value='plain', config=True)
-
- # The type of paging to use. Valid values are:
- # 'inside' : The widget pages like a traditional terminal.
- # 'hsplit' : When paging is requested, the widget is split
- # horizontally. The top pane contains the console, and the
- # bottom pane contains the paged text.
- # 'vsplit' : Similar to 'hsplit', except that a vertical splitter used.
- # 'custom' : No action is taken by the widget beyond emitting a
- # 'custom_page_requested(str)' signal.
- # 'none' : The text is written directly to the console.
+ kind = Enum(['plain', 'rich'], default_value='plain', config=True,
+ help="""
+ The type of underlying text widget to use. Valid values are 'plain', which
+ specifies a QPlainTextEdit, and 'rich', which specifies a QTextEdit.
+ """
+ )
# NOTE: this value can only be specified during initialization.
paging = Enum(['inside', 'hsplit', 'vsplit', 'custom', 'none'],
- default_value='inside', config=True)
+ default_value='inside', config=True,
+ help="""
+ The type of paging to use. Valid values are:
+ 'inside' : The widget pages like a traditional terminal.
+ 'hsplit' : When paging is requested, the widget is split
+ : horizontally. The top pane contains the console, and the
+ : bottom pane contains the paged text.
+ 'vsplit' : Similar to 'hsplit', except that a vertical splitter used.
+ 'custom' : No action is taken by the widget beyond emitting a
+ : 'custom_page_requested(str)' signal.
+ 'none' : The text is written directly to the console.
+ """)
+
+ font_family = Unicode(config=True,
+ help="""The font family to use for the console.
+ On OSX this defaults to Monaco, on Windows the default is
+ Consolas with fallback of Courier, and on other platforms
+ the default is Monospace.
+ """)
+ def _font_family_default(self):
+ if sys.platform == 'win32':
+ # Consolas ships with Vista/Win7, fallback to Courier if needed
+ return 'Consolas'
+ elif sys.platform == 'darwin':
+ # OSX always has Monaco, no need for a fallback
+ return 'Monaco'
+ else:
+ # Monospace should always exist, no need for a fallback
+ return 'Monospace'
+
+ font_size = Int(config=True,
+ help="""The font size. If unconfigured, Qt will be entrusted
+ with the size of the font.
+ """)
# Whether to override ShortcutEvents for the keybindings defined by this
# widget (Ctrl+n, Ctrl+a, etc). Enable this if you want this widget to take
@@ -591,16 +619,18 @@ def reset_font(self):
"""
if sys.platform == 'win32':
# Consolas ships with Vista/Win7, fallback to Courier if needed
- family, fallback = 'Consolas', 'Courier'
+ fallback = 'Courier'
elif sys.platform == 'darwin':
- # OSX always has Monaco, no need for a fallback
- family, fallback = 'Monaco', None
+ # OSX always has Monaco
+ fallback = 'Monaco'
+ else:
+ # Monospace should always exist
+ fallback = 'Monospace'
+ font = get_font(self.font_family, fallback)
+ if self.font_size:
+ font.setPointSize(self.font_size)
else:
- # FIXME: remove Consolas as a default on Linux once our font
- # selections are configurable by the user.
- family, fallback = 'Consolas', 'Monospace'
- font = get_font(family, fallback)
- font.setPointSize(QtGui.qApp.font().pointSize())
+ font.setPointSize(QtGui.qApp.font().pointSize())
font.setStyleHint(QtGui.QFont.TypeWriter)
self._set_font(font)
11 IPython/frontend/qt/console/frontend_widget.py
View
@@ -13,7 +13,7 @@
from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
from IPython.core.oinspect import call_tip
from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
-from IPython.utils.traitlets import Bool
+from IPython.utils.traitlets import Bool, Instance
from bracket_matcher import BracketMatcher
from call_tip_widget import CallTipWidget
from completion_lexer import CompletionLexer
@@ -106,6 +106,7 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
_ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
_input_splitter_class = InputSplitter
_local_kernel = False
+ _highlighter = Instance(FrontendHighlighter)
#---------------------------------------------------------------------------
# 'object' interface
@@ -183,7 +184,7 @@ def _execute(self, source, hidden):
See parent class :meth:`execute` docstring for full details.
"""
- msg_id = self.kernel_manager.xreq_channel.execute(source, hidden)
+ msg_id = self.kernel_manager.shell_channel.execute(source, hidden)
self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user')
self._hidden = hidden
if not hidden:
@@ -329,7 +330,7 @@ def _handle_input_request(self, msg):
self.kernel_manager.sub_channel.flush()
def callback(line):
- self.kernel_manager.rep_channel.input(line)
+ self.kernel_manager.stdin_channel.input(line)
self._readline(msg['content']['prompt'], callback=callback)
def _handle_kernel_died(self, since_last_heartbeat):
@@ -517,7 +518,7 @@ def _call_tip(self):
# Send the metadata request to the kernel
name = '.'.join(context)
- msg_id = self.kernel_manager.xreq_channel.object_info(name)
+ msg_id = self.kernel_manager.shell_channel.object_info(name)
pos = self._get_cursor().position()
self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
return True
@@ -528,7 +529,7 @@ def _complete(self):
context = self._get_context()
if context:
# Send the completion request to the kernel
- msg_id = self.kernel_manager.xreq_channel.complete(
+ msg_id = self.kernel_manager.shell_channel.complete(
'.'.join(context), # text
self._get_input_buffer_cursor_line(), # line
self._get_input_buffer_cursor_column(), # cursor_pos
84 IPython/frontend/qt/console/ipython_widget.py
View
@@ -23,9 +23,7 @@
from IPython.core.usage import default_gui_banner
from IPython.utils.traitlets import Bool, Str, Unicode
from frontend_widget import FrontendWidget
-from styles import (default_light_style_sheet, default_light_syntax_style,
- default_dark_style_sheet, default_dark_syntax_style,
- default_bw_style_sheet, default_bw_syntax_style)
+import styles
#-----------------------------------------------------------------------------
# Constants
@@ -42,6 +40,11 @@
# Base path for most payload sources.
zmq_shell_source = 'IPython.zmq.zmqshell.ZMQInteractiveShell'
+if sys.platform.startswith('win'):
+ default_editor = 'notepad'
+else:
+ default_editor = ''
+
#-----------------------------------------------------------------------------
# IPythonWidget class
#-----------------------------------------------------------------------------
@@ -56,26 +59,35 @@ class IPythonWidget(FrontendWidget):
custom_edit = Bool(False)
custom_edit_requested = QtCore.Signal(object, object)
- # A command for invoking a system text editor. If the string contains a
- # {filename} format specifier, it will be used. Otherwise, the filename will
- # be appended to the end the command.
- editor = Unicode('default', config=True)
-
- # The editor command to use when a specific line number is requested. The
- # string should contain two format specifiers: {line} and {filename}. If
- # this parameter is not specified, the line number option to the %edit magic
- # will be ignored.
- editor_line = Unicode(config=True)
-
- # A CSS stylesheet. The stylesheet can contain classes for:
- # 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
- # 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
- # 3. IPython: .error, .in-prompt, .out-prompt, etc
- style_sheet = Unicode(config=True)
+ editor = Unicode(default_editor, config=True,
+ help="""
+ A command for invoking a system text editor. If the string contains a
+ {filename} format specifier, it will be used. Otherwise, the filename will
+ be appended to the end the command.
+ """)
+
+ editor_line = Unicode(config=True,
+ help="""
+ The editor command to use when a specific line number is requested. The
+ string should contain two format specifiers: {line} and {filename}. If
+ this parameter is not specified, the line number option to the %edit magic
+ will be ignored.
+ """)
+
+ style_sheet = Unicode(config=True,
+ help="""
+ A CSS stylesheet. The stylesheet can contain classes for:
+ 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
+ 2. Pygments: .c, .k, .o, etc. (see PygmentsHighlighter)
+ 3. IPython: .error, .in-prompt, .out-prompt, etc
+ """)
- # If not empty, use this Pygments style for syntax highlighting. Otherwise,
- # the style sheet is queried for Pygments style information.
- syntax_style = Str(config=True)
+
+ syntax_style = Str(config=True,
+ help="""
+ If not empty, use this Pygments style for syntax highlighting. Otherwise,
+ the style sheet is queried for Pygments style information.
+ """)
# Prompts.
in_prompt = Str(default_in_prompt, config=True)
@@ -215,7 +227,7 @@ def _started_channels(self):
""" Reimplemented to make a history request.
"""
super(IPythonWidget, self)._started_channels()
- self.kernel_manager.xreq_channel.history(hist_access_type='tail', n=1000)
+ self.kernel_manager.shell_channel.history(hist_access_type='tail', n=1000)
#---------------------------------------------------------------------------
# 'ConsoleWidget' public interface
@@ -257,7 +269,7 @@ def _complete(self):
text = ''
# Send the completion request to the kernel
- msg_id = self.kernel_manager.xreq_channel.complete(
+ msg_id = self.kernel_manager.shell_channel.complete(
text, # text
self._get_input_buffer_cursor_line(), # line
self._get_input_buffer_cursor_column(), # cursor_pos
@@ -308,7 +320,7 @@ def _show_interpreter_prompt(self, number=None):
"""
# If a number was not specified, make a prompt number request.
if number is None:
- msg_id = self.kernel_manager.xreq_channel.execute('', silent=True)
+ msg_id = self.kernel_manager.shell_channel.execute('', silent=True)
info = self._ExecutionRequest(msg_id, 'prompt')
self._request_info['execute'] = info
return
@@ -371,14 +383,14 @@ def set_default_style(self, colors='lightbg'):
"""
colors = colors.lower()
if colors=='lightbg':
- self.style_sheet = default_light_style_sheet
- self.syntax_style = default_light_syntax_style
+ self.style_sheet = styles.default_light_style_sheet
+ self.syntax_style = styles.default_light_syntax_style
elif colors=='linux':
- self.style_sheet = default_dark_style_sheet
- self.syntax_style = default_dark_syntax_style
+ self.style_sheet = styles.default_dark_style_sheet
+ self.syntax_style = styles.default_dark_syntax_style
elif colors=='nocolor':
- self.style_sheet = default_bw_style_sheet
- self.syntax_style = default_bw_syntax_style
+ self.style_sheet = styles.default_bw_style_sheet
+ self.syntax_style = styles.default_bw_syntax_style
else:
raise KeyError("No such color scheme: %s"%colors)
@@ -399,8 +411,10 @@ def _edit(self, filename, line=None):
"""
if self.custom_edit:
self.custom_edit_requested.emit(filename, line)
- elif self.editor == 'default':
- self._append_plain_text('No default editor available.\n')
+ elif not self.editor:
+ self._append_plain_text('No default editor available.\n'
+ 'Specify a GUI text editor in the `IPythonWidget.editor` configurable\n'
+ 'to enable the %edit magic')
else:
try:
filename = '"%s"' % filename
@@ -482,9 +496,13 @@ def _style_sheet_changed(self):
bg_color = self._control.palette().window().color()
self._ansi_processor.set_background_color(bg_color)
+
def _syntax_style_changed(self):
""" Set the style for the syntax highlighter.
"""
+ if self._highlighter is None:
+ # ignore premature calls
+ return
if self.syntax_style:
self._highlighter.set_style(self.syntax_style)
else:
375 IPython/frontend/qt/console/ipythonqt.py
View
@@ -5,17 +5,33 @@
# Imports
#-----------------------------------------------------------------------------
-# Systemm library imports
+# stdlib imports
+import os
+import signal
+import sys
+
+# System library imports
from IPython.external.qt import QtGui
from pygments.styles import get_all_styles
# Local imports
-from IPython.external.argparse import ArgumentParser
+from IPython.config.application import boolean_flag
+from IPython.core.newapplication import ProfileDir, BaseIPythonApplication
from IPython.frontend.qt.console.frontend_widget import FrontendWidget
from IPython.frontend.qt.console.ipython_widget import IPythonWidget
from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
from IPython.frontend.qt.console import styles
from IPython.frontend.qt.kernelmanager import QtKernelManager
+from IPython.utils.traitlets import (
+ Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
+)
+from IPython.zmq.ipkernel import (
+ flags as ipkernel_flags,
+ aliases as ipkernel_aliases,
+ IPKernelApp
+)
+from IPython.zmq.zmqshell import ZMQInteractiveShell
+
#-----------------------------------------------------------------------------
# Network Constants
@@ -33,7 +49,8 @@ class MainWindow(QtGui.QMainWindow):
# 'object' interface
#---------------------------------------------------------------------------
- def __init__(self, app, frontend, existing=False, may_close=True):
+ def __init__(self, app, frontend, existing=False, may_close=True,
+ confirm_exit=True):
""" Create a MainWindow for the specified FrontendWidget.
The app is passed as an argument to allow for different
@@ -52,6 +69,7 @@ def __init__(self, app, frontend, existing=False, may_close=True):
else:
self._may_close = True
self._frontend.exit_requested.connect(self.close)
+ self._confirm_exit = confirm_exit
self.setCentralWidget(frontend)
#---------------------------------------------------------------------------
@@ -71,6 +89,11 @@ def closeEvent(self, event):
kernel_manager = self._frontend.kernel_manager
+ if keepkernel is None and not self._confirm_exit:
+ # don't prompt, just terminate the kernel if we own it
+ # or leave it alone if we don't
+ keepkernel = not self._existing
+
if keepkernel is None: #show prompt
if kernel_manager and kernel_manager.channels_running:
title = self.window().windowTitle()
@@ -127,123 +150,210 @@ def closeEvent(self, event):
event.accept()
#-----------------------------------------------------------------------------
-# Main entry point
+# Aliases and Flags
#-----------------------------------------------------------------------------
-def main():
- """ Entry point for application.
+flags = dict(ipkernel_flags)
+
+flags.update({
+ 'existing' : ({'IPythonQtConsoleApp' : {'existing' : True}},
+ "Connect to an existing kernel."),
+ 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
+ "Use a pure Python kernel instead of an IPython kernel."),
+ 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
+ "Disable rich text support."),
+})
+flags.update(boolean_flag(
+ 'gui-completion', 'ConsoleWidget.gui_completion',
+ "use a GUI widget for tab completion",
+ "use plaintext output for completion"
+))
+flags.update(boolean_flag(
+ 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
+ """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
+ to force a direct exit without any confirmation.
+ """,
+ """Don't prompt the user when exiting. This will terminate the kernel
+ if it is owned by the frontend, and leave it alive if it is external.
"""
- # Parse command line arguments.
- parser = ArgumentParser()
- kgroup = parser.add_argument_group('kernel options')
- 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].\
- 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,
- help='set the SUB channel port [default random]')
- kgroup.add_argument('--rep', type=int, metavar='PORT', default=0,
- help='set the REP channel port [default random]')
- kgroup.add_argument('--hb', type=int, metavar='PORT', default=0,
- help='set the heartbeat port [default random]')
-
- egroup = kgroup.add_mutually_exclusive_group()
- egroup.add_argument('--pure', action='store_true', help = \
- 'use a pure Python kernel instead of an IPython kernel')
- egroup.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
- const='auto', help = \
- "Pre-load matplotlib and numpy for interactive use. If GUI is not \
- given, the GUI backend is matplotlib's, otherwise use one of: \
- ['tk', 'gtk', 'qt', 'wx', 'inline'].")
-
- wgroup = parser.add_argument_group('widget options')
- wgroup.add_argument('--paging', type=str, default='inside',
- choices = ['inside', 'hsplit', 'vsplit', 'none'],
- help='set the paging style [default inside]')
- wgroup.add_argument('--plain', action='store_true',
- help='disable rich text support')
- wgroup.add_argument('--gui-completion', action='store_true',
- help='use a GUI widget for tab completion')
- wgroup.add_argument('--style', type=str,
- choices = list(get_all_styles()),
- help='specify a pygments style for by name')
- wgroup.add_argument('--stylesheet', type=str,
- help='path to a custom CSS stylesheet')
- wgroup.add_argument('--colors', type=str, help = \
- "Set the color scheme (LightBG,Linux,NoColor). This is guessed \
- based on the pygments style if not set.")
-
- args = parser.parse_args()
-
- # parse the colors arg down to current known labels
- if args.colors:
- colors=args.colors.lower()
- if colors in ('lightbg', 'light'):
- colors='lightbg'
- elif colors in ('dark', 'linux'):
- colors='linux'
- else:
- colors='nocolor'
- elif args.style:
- if args.style=='bw':
- colors='nocolor'
- elif styles.dark_style(args.style):
- colors='linux'
+))
+# the flags that are specific to the frontend
+# these must be scrubbed before being passed to the kernel,
+# or it will raise an error on unrecognized flags
+qt_flags = ['existing', 'pure', 'plain', 'gui-completion', 'no-gui-completion',
+ 'confirm-exit', 'no-confirm-exit']
+
+aliases = dict(ipkernel_aliases)
+
+aliases.update(dict(
+ hb = 'IPythonQtConsoleApp.hb_port',
+ shell = 'IPythonQtConsoleApp.shell_port',
+ iopub = 'IPythonQtConsoleApp.iopub_port',
+ stdin = 'IPythonQtConsoleApp.stdin_port',
+ ip = 'IPythonQtConsoleApp.ip',
+
+ plain = 'IPythonQtConsoleApp.plain',
+ pure = 'IPythonQtConsoleApp.pure',
+ gui_completion = 'ConsoleWidget.gui_completion',
+ style = 'IPythonWidget.syntax_style',
+ stylesheet = 'IPythonQtConsoleApp.stylesheet',
+ colors = 'ZMQInteractiveShell.colors',
+
+ editor = 'IPythonWidget.editor',
+ pi = 'IPythonWidget.in_prompt',
+ po = 'IPythonWidget.out_prompt',
+ si = 'IPythonWidget.input_sep',
+ so = 'IPythonWidget.output_sep',
+ so2 = 'IPythonWidget.output_sep2',
+))
+
+#-----------------------------------------------------------------------------
+# IPythonQtConsole
+#-----------------------------------------------------------------------------
+
+class IPythonQtConsoleApp(BaseIPythonApplication):
+ name = 'ipython-qtconsole'
+ default_config_file_name='ipython_config.py'
+ classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir]
+ flags = Dict(flags)
+ aliases = Dict(aliases)
+
+ kernel_argv = List(Unicode)
+
+ # connection info:
+ ip = Unicode(LOCALHOST, config=True,
+ 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!"""
+ )
+ hb_port = Int(0, config=True,
+ help="set the heartbeat port [default: random]")
+ shell_port = Int(0, config=True,
+ help="set the shell (XREP) port [default: random]")
+ iopub_port = Int(0, config=True,
+ help="set the iopub (PUB) port [default: random]")
+ stdin_port = Int(0, config=True,
+ help="set the stdin (XREQ) port [default: random]")
+
+ existing = CBool(False, config=True,
+ help="Whether to connect to an already running Kernel.")
+
+ stylesheet = Unicode('', config=True,
+ help="path to a custom CSS stylesheet")
+
+ pure = CBool(False, config=True,
+ help="Use a pure Python kernel instead of an IPython kernel.")
+ plain = CBool(False, config=True,
+ help="Use a plaintext widget instead of rich text (plain can't print/save).")
+
+ def _pure_changed(self, name, old, new):
+ kind = 'plain' if self.plain else 'rich'
+ self.config.ConsoleWidget.kind = kind
+ if self.pure:
+ self.widget_factory = FrontendWidget
+ elif self.plain:
+ self.widget_factory = IPythonWidget
else:
- colors='lightbg'
- else:
- colors=None
-
- # Don't let Qt or ZMQ swallow KeyboardInterupts.
- import signal
- signal.signal(signal.SIGINT, signal.SIG_DFL)
-
- # Create a KernelManager and start a kernel.
- kernel_manager = QtKernelManager(xreq_address=(args.ip, args.xreq),
- sub_address=(args.ip, args.sub),
- rep_address=(args.ip, args.rep),
- hb_address=(args.ip, args.hb))
- 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:
- kwargs['ipython']=False
+ self.widget_factory = RichIPythonWidget
+
+ _plain_changed = _pure_changed
+
+ confirm_exit = CBool(True, config=True,
+ help="""
+ Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
+ to force a direct exit without any confirmation.""",
+ )
+
+ # the factory for creating a widget
+ widget_factory = Any(RichIPythonWidget)
+
+ def parse_command_line(self, argv=None):
+ super(IPythonQtConsoleApp, self).parse_command_line(argv)
+ if argv is None:
+ argv = sys.argv[1:]
+
+ self.kernel_argv = list(argv) # copy
+
+ # scrub frontend-specific flags
+ for a in argv:
+ if a.startswith('--') and a[2:] in qt_flags:
+ self.kernel_argv.remove(a)
+
+ def init_kernel_manager(self):
+ # Don't let Qt or ZMQ swallow KeyboardInterupts.
+ signal.signal(signal.SIGINT, signal.SIG_DFL)
+
+ # Create a KernelManager and start a kernel.
+ self.kernel_manager = QtKernelManager(
+ shell_address=(self.ip, self.shell_port),
+ sub_address=(self.ip, self.iopub_port),
+ stdin_address=(self.ip, self.stdin_port),
+ hb_address=(self.ip, self.hb_port)
+ )
+ # start the kernel
+ if not self.existing:
+ kwargs = dict(ip=self.ip, ipython=not self.pure)
+ kwargs['extra_arguments'] = self.kernel_argv
+ self.kernel_manager.start_kernel(**kwargs)
+ self.kernel_manager.start_channels()
+
+
+ def init_qt_elements(self):
+ # Create the widget.
+ self.app = QtGui.QApplication([])
+ local_kernel = (not self.existing) or self.ip in LOCAL_IPS
+ self.widget = self.widget_factory(config=self.config,
+ local_kernel=local_kernel)
+ self.widget.kernel_manager = self.kernel_manager
+ self.window = MainWindow(self.app, self.widget, self.existing,
+ may_close=local_kernel,
+ confirm_exit=self.confirm_exit)
+ self.window.setWindowTitle('Python' if self.pure else 'IPython')
+
+ def init_colors(self):
+ """Configure the coloring of the widget"""
+ # Note: This will be dramatically simplified when colors
+ # are removed from the backend.
+
+ if self.pure:
+ # only IPythonWidget supports styling
+ return
+
+ # parse the colors arg down to current known labels
+ try:
+ colors = self.config.ZMQInteractiveShell.colors
+ except AttributeError:
+ colors = None
+ try:
+ style = self.config.IPythonWidget.colors
+ except AttributeError:
+ style = None
+
+ # find the value for colors:
+ if colors:
+ colors=colors.lower()
+ if colors in ('lightbg', 'light'):
+ colors='lightbg'
+ elif colors in ('dark', 'linux'):
+ colors='linux'
+ else:
+ colors='nocolor'
+ elif style:
+ if style=='bw':
+ colors='nocolor'
+ elif styles.dark_style(style):
+ colors='linux'
+ else:
+ colors='lightbg'
else:
- kwargs['colors']=colors
- if args.pylab:
- kwargs['pylab']=args.pylab
-
- kernel_manager.start_kernel(**kwargs)
- kernel_manager.start_channels()
-
- # Create the widget.
- app = QtGui.QApplication([])
- local_kernel = (not args.existing) or args.ip in LOCAL_IPS
- if args.pure:
- kind = 'plain' if args.plain else 'rich'
- widget = FrontendWidget(kind=kind, paging=args.paging,
- local_kernel=local_kernel)
- elif args.plain:
- widget = IPythonWidget(paging=args.paging, local_kernel=local_kernel)
- else:
- widget = RichIPythonWidget(paging=args.paging,
- local_kernel=local_kernel)
- widget.gui_completion = args.gui_completion
- widget.kernel_manager = kernel_manager
-
- # Configure the style.
- if not args.pure: # only IPythonWidget supports styles
- if args.style:
- widget.syntax_style = args.style
- widget.style_sheet = styles.sheet_from_template(args.style, colors)
+ colors=None
+
+ # Configure the style.
+ widget = self.widget
+ if style:
+ widget.style_sheet = styles.sheet_from_template(style, colors)
+ widget.syntax_style = style
widget._syntax_style_changed()
widget._style_sheet_changed()
elif colors:
@@ -254,23 +364,38 @@ def main():
# defaults to change
widget.set_default_style()
- if args.stylesheet:
+ if self.stylesheet:
# we got an expicit stylesheet
- if os.path.isfile(args.stylesheet):
- with open(args.stylesheet) as f:
+ if os.path.isfile(self.stylesheet):
+ with open(self.stylesheet) as f:
sheet = f.read()
widget.style_sheet = sheet
widget._style_sheet_changed()
else:
- raise IOError("Stylesheet %r not found."%args.stylesheet)
+ raise IOError("Stylesheet %r not found."%self.stylesheet)
+
+ def initialize(self, argv=None):
+ super(IPythonQtConsoleApp, self).initialize(argv)
+ self.init_kernel_manager()
+ self.init_qt_elements()
+ self.init_colors()
+
+ def start(self):
+
+ # draw the window
+ self.window.show()
- # Create the main window.
- window = MainWindow(app, widget, args.existing, may_close=local_kernel)
- window.setWindowTitle('Python' if args.pure else 'IPython')
- window.show()
+ # Start the application main loop.
+ self.app.exec_()
- # Start the application main loop.
- app.exec_()
+#-----------------------------------------------------------------------------
+# Main entry point
+#-----------------------------------------------------------------------------
+
+def main():
+ app = IPythonQtConsoleApp()
+ app.initialize()
+ app.start()
if __name__ == '__main__':
32 IPython/frontend/qt/kernelmanager.py
View
@@ -7,7 +7,7 @@
# IPython imports.
from IPython.utils.traitlets import Type
from IPython.zmq.kernelmanager import KernelManager, SubSocketChannel, \
- XReqSocketChannel, RepSocketChannel, HBSocketChannel
+ ShellSocketChannel, StdInSocketChannel, HBSocketChannel
from util import MetaQObjectHasTraits, SuperQObject
@@ -20,7 +20,7 @@ class SocketChannelQObject(SuperQObject):
stopped = QtCore.Signal()
#---------------------------------------------------------------------------
- # 'ZmqSocketChannel' interface
+ # 'ZMQSocketChannel' interface
#---------------------------------------------------------------------------
def start(self):
@@ -36,7 +36,7 @@ def stop(self):
self.stopped.emit()
-class QtXReqSocketChannel(SocketChannelQObject, XReqSocketChannel):
+class QtShellSocketChannel(SocketChannelQObject, ShellSocketChannel):
# Emitted when any message is received.
message_received = QtCore.Signal(object)
@@ -56,7 +56,7 @@ class QtXReqSocketChannel(SocketChannelQObject, XReqSocketChannel):
_handlers_called = False
#---------------------------------------------------------------------------
- # 'XReqSocketChannel' interface
+ # 'ShellSocketChannel' interface
#---------------------------------------------------------------------------
def call_handlers(self, msg):
@@ -76,7 +76,7 @@ def call_handlers(self, msg):
self._handlers_called = True
#---------------------------------------------------------------------------
- # 'QtXReqSocketChannel' interface
+ # 'QtShellSocketChannel' interface
#---------------------------------------------------------------------------
def reset_first_reply(self):
@@ -136,7 +136,7 @@ def flush(self):
QtCore.QCoreApplication.instance().processEvents()
-class QtRepSocketChannel(SocketChannelQObject, RepSocketChannel):
+class QtStdInSocketChannel(SocketChannelQObject, StdInSocketChannel):
# Emitted when any message is received.
message_received = QtCore.Signal(object)
@@ -145,7 +145,7 @@ class QtRepSocketChannel(SocketChannelQObject, RepSocketChannel):
input_requested = QtCore.Signal(object)
#---------------------------------------------------------------------------
- # 'RepSocketChannel' interface
+ # 'StdInSocketChannel' interface
#---------------------------------------------------------------------------
def call_handlers(self, msg):
@@ -190,8 +190,8 @@ class QtKernelManager(KernelManager, SuperQObject):
# Use Qt-specific channel classes that emit signals.
sub_channel_class = Type(QtSubSocketChannel)
- xreq_channel_class = Type(QtXReqSocketChannel)
- rep_channel_class = Type(QtRepSocketChannel)
+ shell_channel_class = Type(QtShellSocketChannel)
+ stdin_channel_class = Type(QtStdInSocketChannel)
hb_channel_class = Type(QtHBSocketChannel)
#---------------------------------------------------------------------------
@@ -203,8 +203,8 @@ class QtKernelManager(KernelManager, SuperQObject):
def start_kernel(self, *args, **kw):
""" Reimplemented for proper heartbeat management.
"""
- if self._xreq_channel is not None:
- self._xreq_channel.reset_first_reply()
+ if self._shell_channel is not None:
+ self._shell_channel.reset_first_reply()
super(QtKernelManager, self).start_kernel(*args, **kw)
#------ Channel management -------------------------------------------------
@@ -222,13 +222,13 @@ def stop_channels(self):
self.stopped_channels.emit()
@property
- def xreq_channel(self):
+ def shell_channel(self):
""" Reimplemented for proper heartbeat management.
"""
- if self._xreq_channel is None:
- self._xreq_channel = super(QtKernelManager, self).xreq_channel
- self._xreq_channel.first_reply.connect(self._first_reply)
- return self._xreq_channel
+ if self._shell_channel is None:
+ self._shell_channel = super(QtKernelManager, self).shell_channel
+ self._shell_channel.first_reply.connect(self._first_reply)
+ return self._shell_channel
#---------------------------------------------------------------------------
# Protected interface
13 IPython/frontend/terminal/ipapp.py
View
@@ -206,9 +206,16 @@ class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
# command_line_loader = IPAppConfigLoader
default_config_file_name = default_config_file_name
crash_handler_class = IPAppCrashHandler
+
flags = Dict(flags)
aliases = Dict(aliases)
classes = [InteractiveShellApp, TerminalInteractiveShell, ProfileDir, PlainTextFormatter]
+ subcommands = Dict(dict(
+ qtconsole=('IPython.frontend.qt.console.ipythonqt.IPythonQtConsoleApp',
+ """Launch the IPython QtConsole. Also launched as ipython-qtconsole"""
+ )
+ ))
+
# *do* autocreate requested profile
auto_create=Bool(True)
copy_config_files=Bool(True)
@@ -259,9 +266,11 @@ def _file_to_run_changed(self, name, old, new):
def initialize(self, argv=None):
"""Do actions after construct, but before starting the app."""
super(TerminalIPythonApp, self).initialize(argv)
+ if self.subapp is not None:
+ # don't bother initializing further, starting subapp
+ return
if not self.ignore_old_config:
check_for_old_config(self.ipython_dir)
-
# print self.extra_args
if self.extra_args:
self.file_to_run = self.extra_args[0]
@@ -322,6 +331,8 @@ def init_gui_pylab(self):
self.shell.showtraceback()
def start(self):
+ if self.subapp is not None:
+ return self.subapp.start()
# perform any prexec steps:
if self.interact:
self.log.debug("Starting IPython's mainloop...")
78 IPython/lib/pylabtools.py
View
@@ -87,8 +87,8 @@ def figsize(sizex, sizey):
matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
-def figure_to_svg(fig):
- """Convert a figure to svg for inline display."""
+def print_figure(fig, fmt='png'):
+ """Convert a figure to svg or png for inline display."""
# When there's an empty figure, we shouldn't return anything, otherwise we
# get big blank areas in the qt console.
if not fig.axes:
@@ -100,12 +100,15 @@ def figure_to_svg(fig):
fig.set_edgecolor('white')
try:
string_io = StringIO()
- fig.canvas.print_figure(string_io, format='svg')
- svg = string_io.getvalue()
+ # use 72 dpi to match QTConsole's dpi
+ fig.canvas.print_figure(string_io, format=fmt, dpi=72)
+ data = string_io.getvalue()
finally:
fig.set_facecolor(fc)
fig.set_edgecolor(ec)
- return svg
+ if fmt == 'png':
+ data = data.encode('base64')
+ return data
# We need a little factory function here to create the closure where
@@ -150,6 +153,29 @@ def mpl_execfile(fname,*where,**kw):
return mpl_execfile
+def select_figure_format(shell, fmt):
+ """Select figure format for inline backend, either 'png' or 'svg'.
+
+ Using this method ensures only one figure format is active at a time.
+ """
+ from matplotlib.figure import Figure
+ from IPython.zmq.pylab import backend_inline
+
+ svg_formatter = shell.display_formatter.formatters['image/svg+xml']
+ png_formatter = shell.display_formatter.formatters['image/png']
+
+ if fmt=='png':
+ svg_formatter.type_printers.pop(Figure, None)
+ png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png'))
+ elif fmt=='svg':
+ png_formatter.type_printers.pop(Figure, None)
+ svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg'))
+ else:
+ raise ValueError("supported formats are: 'png', 'svg', not %r"%fmt)
+
+ # set the format to be used in the backend()
+ backend_inline._figure_format = fmt
+
#-----------------------------------------------------------------------------
# Code for initializing matplotlib and importing pylab
#-----------------------------------------------------------------------------
@@ -208,7 +234,6 @@ def activate_matplotlib(backend):
# For this, we wrap it into a decorator which adds a 'called' flag.
pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive)
-
def import_pylab(user_ns, backend, import_all=True, shell=None):
"""Import the standard pylab symbols into user_ns."""
@@ -228,43 +253,32 @@ def import_pylab(user_ns, backend, import_all=True, shell=None):
# If using our svg payload backend, register the post-execution
# function that will pick up the results for display. This can only be
# done with access to the real shell object.
+ #
+ from IPython.zmq.pylab.backend_inline import InlineBackendConfig
+
+ cfg = InlineBackendConfig.instance(config=shell.config)
+ cfg.shell = shell
+
if backend == backends['inline']:
- from IPython.zmq.pylab.backend_inline import flush_svg
+ from IPython.zmq.pylab.backend_inline import flush_figures
from matplotlib import pyplot
- shell.register_post_execute(flush_svg)
- # The typical default figure size is too large for inline use,
- # so we shrink the figure size to 6x4, and tweak fonts to
- # make that fit. This is configurable via Global.pylab_inline_rc,
- # or rather it will be once the zmq kernel is hooked up to
- # the config system.
-
- default_rc = {
- 'figure.figsize': (6.0,4.0),
- # 12pt labels get cutoff on 6x4 logplots, so use 10pt.
- 'font.size': 10,
- # 10pt still needs a little more room on the xlabel:
- 'figure.subplot.bottom' : .125
- }
- rc = getattr(shell.config.Global, 'pylab_inline_rc', default_rc)
- pyplot.rcParams.update(rc)
- shell.config.Global.pylab_inline_rc = rc
+ shell.register_post_execute(flush_figures)
+ # load inline_rc
+ pyplot.rcParams.update(cfg.rc)
# Add 'figsize' to pyplot and to the user's namespace
user_ns['figsize'] = pyplot.figsize = figsize
shell.user_ns_hidden['figsize'] = figsize
+ # Setup the default figure format
+ fmt = cfg.figure_format
+ select_figure_format(shell, fmt)
+
# The old pastefig function has been replaced by display
- # Always add this svg formatter so display works.
- from IPython.core.display import display, display_svg
- svg_formatter = shell.display_formatter.formatters['image/svg+xml']
- svg_formatter.for_type_by_name(
- 'matplotlib.figure','Figure',figure_to_svg
- )
+ from IPython.core.display import display
# Add display and display_png to the user's namespace
user_ns['display'] = display
shell.user_ns_hidden['display'] = display
- user_ns['display_svg'] = display_svg
- shell.user_ns_hidden['display_svg'] = display_svg
user_ns['getfigs'] = getfigs
shell.user_ns_hidden['getfigs'] = getfigs
16 IPython/zmq/blockingkernelmanager.py
View
@@ -21,8 +21,8 @@
from IPython.utils import io
from IPython.utils.traitlets import Type
-from .kernelmanager import (KernelManager, SubSocketChannel,
- XReqSocketChannel, RepSocketChannel, HBSocketChannel)
+from .kernelmanager import (KernelManager, SubSocketChannel, HBSocketChannel,
+ ShellSocketChannel, StdInSocketChannel)
#-----------------------------------------------------------------------------
# Functions and classes
@@ -61,15 +61,15 @@ def get_msgs(self):
return msgs
-class BlockingXReqSocketChannel(XReqSocketChannel):
+class BlockingShellSocketChannel(ShellSocketChannel):
def __init__(self, context, session, address=None):
- super(BlockingXReqSocketChannel, self).__init__(context, session,
+ super(BlockingShellSocketChannel, self).__init__(context, session,
address)
self._in_queue = Queue()
def call_handlers(self, msg):
- #io.rprint('[[XReq]]', msg) # dbg
+ #io.rprint('[[Shell]]', msg) # dbg
self._in_queue.put(msg)
def msg_ready(self):
@@ -94,7 +94,7 @@ def get_msgs(self):
return msgs
-class BlockingRepSocketChannel(RepSocketChannel):
+class BlockingStdInSocketChannel(StdInSocketChannel):
def call_handlers(self, msg):
#io.rprint('[[Rep]]', msg) # dbg
@@ -114,8 +114,8 @@ def call_handlers(self, since_last_heartbeat):
class BlockingKernelManager(KernelManager):
# The classes to use for the various channels.
- xreq_channel_class = Type(BlockingXReqSocketChannel)
+ shell_channel_class = Type(BlockingShellSocketChannel)
sub_channel_class = Type(BlockingSubSocketChannel)
- rep_channel_class = Type(BlockingRepSocketChannel)
+ stdin_channel_class = Type(BlockingStdInSocketChannel)
hb_channel_class = Type(BlockingHBSocketChannel)
197 IPython/zmq/entry_point.py
View
@@ -9,159 +9,14 @@
from subprocess import Popen, PIPE
import sys
-# System library imports.
-import zmq
-
# Local imports.
-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
-from parentpoller import ParentPollerUnix, ParentPollerWindows
-from session import Session
-
-
-def bind_port(socket, ip, port):
- """ Binds the specified ZMQ socket. If the port is zero, a random port is
- chosen. Returns the port that was bound.
- """
- connection = 'tcp://%s' % ip
- if port <= 0:
- port = socket.bind_to_random_port(connection)
- else:
- connection += ':%i' % port
- socket.bind(connection)
- return port
-
-
-def make_argument_parser():
- """ Creates an ArgumentParser for the generic arguments supported by all
- kernel entry points.
- """
- parser = ArgumentParser()
- 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]')
- parser.add_argument('--pub', type=int, metavar='PORT', default=0,
- help='set the PUB channel port [default: random]')
- parser.add_argument('--req', type=int, metavar='PORT', default=0,
- help='set the REQ channel port [default: random]')
- parser.add_argument('--hb', type=int, metavar='PORT', default=0,
- help='set the heartbeat port [default: random]')
- parser.add_argument('--no-stdout', action='store_true',
- help='redirect stdout to the null device')
- parser.add_argument('--no-stderr', action='store_true',
- help='redirect stderr to the null device')
-
- if sys.platform == 'win32':
- parser.add_argument('--interrupt', type=int, metavar='HANDLE',
- default=0, help='interrupt this process when '
- 'HANDLE is signaled')
- parser.add_argument('--parent', type=int, metavar='HANDLE',
- default=0, help='kill this process if the process '
- 'with HANDLE dies')
- else:
- parser.add_argument('--parent', action='store_true',
- help='kill this process if its parent dies')
-
- return parser
-
-
-def make_kernel(namespace, kernel_factory,
- out_stream_factory=None, display_hook_factory=None):
- """ Creates a kernel, redirects stdout/stderr, and installs a display hook
- and exception handler.
- """
- # Re-direct stdout/stderr, if necessary.
- if namespace.no_stdout or namespace.no_stderr:
- blackhole = file(os.devnull, 'w')
- if namespace.no_stdout:
- sys.stdout = sys.__stdout__ = blackhole
- if namespace.no_stderr:
- sys.stderr = sys.__stderr__ = blackhole
-
- # Install minimal exception handling
- sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
- ostream=sys.__stdout__)
+from parentpoller import ParentPollerWindows
- # Create a context, a session, and the kernel sockets.
- io.raw_print("Starting the kernel at pid:", os.getpid())
- context = zmq.Context()
- # Uncomment this to try closing the context.
- # atexit.register(context.close)
- session = Session(username=u'kernel')
- reply_socket = context.socket(zmq.XREP)
- xrep_port = bind_port(reply_socket, namespace.ip, namespace.xrep)
- io.raw_print("XREP Channel on port", xrep_port)
- pub_socket = context.socket(zmq.PUB)
- pub_port = bind_port(pub_socket, namespace.ip, namespace.pub)
- io.raw_print("PUB Channel on port", pub_port)
-
- req_socket = context.socket(zmq.XREQ)
- req_port = bind_port(req_socket, namespace.ip, namespace.req)
- io.raw_print("REQ Channel on port", req_port)
-
- hb = Heartbeat(context, (namespace.ip, namespace.hb))
- hb.start()
- hb_port = hb.port
- io.raw_print("Heartbeat REP Channel on port", hb_port)
-
- # Helper to make it easier to connect to an existing kernel, until we have
- # single-port connection negotiation fully implemented.
- io.raw_print("To connect another client to this kernel, use:")
- io.raw_print("-e --xreq {0} --sub {1} --rep {2} --hb {3}".format(
- xrep_port, pub_port, req_port, hb_port))
-
- # Redirect input streams and set a display hook.
- if out_stream_factory:
- sys.stdout = out_stream_factory(session, pub_socket, u'stdout')
- sys.stderr = out_stream_factory(session, pub_socket, u'stderr')
- if display_hook_factory:
- sys.displayhook = display_hook_factory(session, pub_socket)
-
- # Create the kernel.
- kernel = kernel_factory(session=session, reply_socket=reply_socket,
- pub_socket=pub_socket, req_socket=req_socket)
- kernel.record_ports(xrep_port=xrep_port, pub_port=pub_port,
- req_port=req_port, hb_port=hb_port)
- return kernel
-
-
-def start_kernel(namespace, kernel):
- """ Starts a kernel.
- """
- # Configure this kernel process to poll the parent process, if necessary.
- if sys.platform == 'win32':
- if namespace.interrupt or namespace.parent:
- poller = ParentPollerWindows(namespace.interrupt, namespace.parent)
- poller.start()
- elif namespace.parent:
- poller = ParentPollerUnix()
- poller.start()
-
- # Start the kernel mainloop.
- kernel.start()
-
-
-def make_default_main(kernel_factory):
- """ Creates the simplest possible kernel entry point.
- """
- def main():
- namespace = make_argument_parser().parse_args()
- kernel = make_kernel(namespace, kernel_factory, OutStream, DisplayHook)
- start_kernel(namespace, kernel)
- return main
-
-
-def base_launch_kernel(code, xrep_port=0, pub_port=0, req_port=0, hb_port=0,
- stdin=None, stdout=None, stderr=None,
- executable=None, independent=False, extra_arguments=[]):
+def base_launch_kernel(code, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
+ ip=None, stdin=None, stdout=None, stderr=None,
+ executable=None, independent=False, extra_arguments=[]):
""" Launches a localhost kernel, binding to the specified ports.
Parameters
@@ -169,18 +24,21 @@ def base_launch_kernel(code, xrep_port=0, pub_port=0, req_port=0, hb_port=0,
code : str,
A string of Python code that imports and executes a kernel entry point.
- xrep_port : int, optional
+ shell_port : int, optional
The port to use for XREP channel.
- pub_port : int, optional
+ iopub_port : int, optional
The port to use for the SUB channel.
- req_port : int, optional
+ stdin_port : int, optional
The port to use for the REQ (raw input) channel.
hb_port : int, optional
The port to use for the hearbeat REP channel.
+ ip : str, optional
+ The ip address the kernel will bind to.
+
stdin, stdout, stderr : optional (default None)
Standards streams, as defined in subprocess.Popen.
@@ -199,13 +57,13 @@ def base_launch_kernel(code, xrep_port=0, pub_port=0, req_port=0, hb_port=0,
Returns
-------
A tuple of form:
- (kernel_process, xrep_port, pub_port, req_port)
+ (kernel_process, shell_port, iopub_port, stdin_port, hb_port)
where kernel_process is a Popen object and the ports are integers.
"""
# Find open ports as necessary.
ports = []
- ports_needed = int(xrep_port <= 0) + int(pub_port <= 0) + \
- int(req_port <= 0) + int(hb_port <= 0)
+ ports_needed = int(shell_port <= 0) + int(iopub_port <= 0) + \
+ int(stdin_port <= 0) + int(hb_port <= 0)
for i in xrange(ports_needed):
sock = socket.socket()
sock.bind(('', 0))
@@ -214,28 +72,31 @@ def base_launch_kernel(code, xrep_port=0, pub_port=0, req_port=0, hb_port=0,
port = sock.getsockname()[1]
sock.close()
ports[i] = port
- if xrep_port <= 0:
- xrep_port = ports.pop(0)
- if pub_port <= 0:
- pub_port = ports.pop(0)
- if req_port <= 0:
- req_port = ports.pop(0)
+ if shell_port <= 0:
+ shell_port = ports.pop(0)
+ if iopub_port <= 0:
+ iopub_port = ports.pop(0)
+ if stdin_port <= 0:
+ stdin_port = ports.pop(0)
if hb_port <= 0:
hb_port = ports.pop(0)
# Build the kernel launch command.
if executable is None:
executable = sys.executable
- arguments = [ executable, '-c', code, '--xrep', str(xrep_port),
- '--pub', str(pub_port), '--req', str(req_port),
- '--hb', str(hb_port) ]
+ arguments = [ executable, '-c', code, 'shell=%i'%shell_port,
+ 'iopub=%i'%iopub_port, 'stdin=%i'%stdin_port,
+ 'hb=%i'%hb_port
+ ]
+ if ip is not None:
+ arguments.append('ip=%s'%ip)
arguments.extend(extra_arguments)
# Spawn a kernel.
if sys.platform == 'win32':
# Create a Win32 event for interrupting the kernel.
interrupt_event = ParentPollerWindows.create_interrupt_event()
- arguments += [ '--interrupt', str(int(interrupt_event)) ]
+ arguments += [ 'interrupt=%i'%interrupt_event ]
# If this process in running on pythonw, stdin, stdout, and stderr are
# invalid. Popen will fail unless they are suitably redirected. We don't
@@ -273,7 +134,7 @@ def base_launch_kernel(code, xrep_port=0, pub_port=0, req_port=0, hb_port=0,
handle = DuplicateHandle(pid, pid, pid, 0,
True, # Inheritable by new processes.
DUPLICATE_SAME_ACCESS)
- proc = Popen(arguments + ['--parent', str(int(handle))],
+ proc = Popen(arguments + ['parent=%i'%int(handle)],
stdin=_stdin, stdout=_stdout, stderr=_stderr)
# Attach the interrupt event to the Popen objet so it can be used later.
@@ -293,7 +154,7 @@ def base_launch_kernel(code, xrep_port=0, pub_port=0, req_port=0, hb_port=0,
proc = Popen(arguments, preexec_fn=lambda: os.setsid(),
stdin=stdin, stdout=stdout, stderr=stderr)
else:
- proc = Popen(arguments + ['--parent'],
+ proc = Popen(arguments + ['parent=1'],
stdin=stdin, stdout=stdout, stderr=stderr)
- return proc, xrep_port, pub_port, req_port, hb_port
+ return proc, shell_port, iopub_port, stdin_port, hb_port
310 IPython/zmq/ipkernel.py
View
@@ -27,37 +27,25 @@
# Local imports.
from IPython.config.configurable import Configurable
+from IPython.config.application import boolean_flag
+from IPython.core.newapplication import ProfileDir
+from IPython.core.shellapp import (
+ InteractiveShellApp, shell_flags, shell_aliases
+)
from IPython.utils import io
from IPython.utils.jsonutil import json_clean
from IPython.lib import pylabtools
-from IPython.utils.traitlets import Instance, Float
-from entry_point import (base_launch_kernel, make_argument_parser, make_kernel,
- start_kernel)
+from IPython.utils.traitlets import (
+ List, Instance, Float, Dict, Bool, Int, Unicode, CaselessStrEnum
+)
+
+from entry_point import base_launch_kernel
+from kernelapp import KernelApp, kernel_flags, kernel_aliases
from iostream import OutStream
from session import Session, Message
from zmqshell import ZMQInteractiveShell
-#-----------------------------------------------------------------------------
-# Globals
-#-----------------------------------------------------------------------------
-
-# Module-level logger
-logger = logging.getLogger(__name__)
-# FIXME: this needs to be done more cleanly later, once we have proper
-# configuration support. This is a library, so it shouldn't set a stream
-# handler, see:
-# http://docs.python.org/library/logging.html#configuring-logging-for-a-library
-# But this lets us at least do developer debugging for now by manually turning
-# it on/off. And once we have full config support, the client entry points
-# will select their logging handlers, as well as passing to this library the
-# logging level.
-
-if 0: # dbg - set to 1 to actually see the messages.
- logger.addHandler(logging.StreamHandler())
- logger.setLevel(logging.DEBUG)
-
-# /FIXME
#-----------------------------------------------------------------------------
# Main kernel class
@@ -71,9 +59,10 @@ class Kernel(Configurable):
shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
session = Instance(Session)
- reply_socket = Instance('zmq.Socket')
- pub_socket = Instance('zmq.Socket')
- req_socket = Instance('zmq.Socket')
+ shell_socket = Instance('zmq.Socket')
+ iopub_socket = Instance('zmq.Socket')
+ stdin_socket = Instance('zmq.Socket')
+ log = Instance(logging.Logger)
# Private interface
@@ -100,7 +89,8 @@ class Kernel(Configurable):
# This is a dict of port number that the kernel is listening on. It is set
# by record_ports and used by connect_request.
- _recorded_ports = None
+ _recorded_ports = Dict()
+
def __init__(self, **kwargs):
@@ -111,11 +101,11 @@ def __init__(self, **kwargs):
atexit.register(self._at_shutdown)
# Initialize the InteractiveShell subclass
- self.shell = ZMQInteractiveShell.instance()
+ self.shell = ZMQInteractiveShell.instance(config=self.config)
self.shell.displayhook.session = self.session
- self.shell.displayhook.pub_socket = self.pub_socket
+ self.shell.displayhook.pub_socket = self.iopub_socket
self.shell.display_pub.session = self.session
- self.shell.display_pub.pub_socket = self.pub_socket
+ self.shell.display_pub.pub_socket = self.iopub_socket
# TMP - hack while developing
self.shell._reply_content = None
@@ -131,7 +121,7 @@ def __init__(self, **kwargs):
def do_one_iteration(self):
"""Do one iteration of the kernel's evaluation loop.
"""
- ident,msg = self.session.recv(self.reply_socket, zmq.NOBLOCK)
+ ident,msg = self.session.recv(self.shell_socket, zmq.NOBLOCK)
if msg is None:
return
@@ -143,21 +133,20 @@ def do_one_iteration(self):
# Print some info about this message and leave a '--->' marker, so it's
# easier to trace visually the message chain when debugging. Each
# handler prints its message at the end.
- # Eventually we'll move these from stdout to a logger.
- logger.debug('\n*** MESSAGE TYPE:'+str(msg['msg_type'])+'***')
- logger.debug(' Content: '+str(msg['content'])+'\n --->\n ')
+ self.log.debug('\n*** MESSAGE TYPE:'+str(msg['msg_type'])+'***')
+ self.log.debug(' Content: '+str(msg['content'])+'\n --->\n ')
# Find and call actual handler for message
handler = self.handlers.get(msg['msg_type'], None)
if handler is None:
- logger.error("UNKNOWN MESSAGE TYPE:" +str(msg))
+ self.log.error("UNKNOWN MESSAGE TYPE:" +str(msg))
else:
handler(ident, msg)
# Check whether we should exit, in case the incoming message set the
# exit flag on
if self.shell.exit_now:
- logger.debug('\nExiting IPython kernel...')
+ self.log.debug('\nExiting IPython kernel...')
# We do a normal, clean exit, which allows any actions registered
# via atexit (such as history saving) to take place.
sys.exit(0)
@@ -166,26 +155,27 @@ def do_one_iteration(self):
def start(self):
""" Start the kernel main loop.
"""
+ poller = zmq.Poller()
+ poller.register(self.shell_socket, zmq.POLLIN)
while True:
try:
- time.sleep(self._poll_interval)
+ # scale by extra factor of 10, because there is no
+ # reason for this to be anything less than ~ 0.1s
+ # since it is a real poller and will respond
+ # to events immediately
+ poller.poll(10*1000*self._poll_interval)
self.do_one_iteration()
except KeyboardInterrupt:
# Ctrl-C shouldn't crash the kernel
io.raw_print("KeyboardInterrupt caught in kernel")
- def record_ports(self, xrep_port, pub_port, req_port, hb_port):
+ def record_ports(self, ports):
"""Record the ports that this kernel is using.
The creator of the Kernel instance must call this methods if they
want the :meth:`connect_request` method to return the port numbers.
"""
- self._recorded_ports = {
- 'xrep_port' : xrep_port,
- 'pub_port' : pub_port,
- 'req_port' : req_port,
- 'hb_port' : hb_port
- }
+ self._recorded_ports = ports
#---------------------------------------------------------------------------
# Kernel request handlers
@@ -194,11 +184,11 @@ def record_ports(self, xrep_port, pub_port, req_port, hb_port):
def _publish_pyin(self, code, parent):
"""Publish the code request on the pyin stream."""
- pyin_msg = self.session.send(self.pub_socket, u'pyin',{u'code':code}, parent=parent)
+ pyin_msg = self.session.send(self.iopub_socket, u'pyin',{u'code':code}, parent=parent)
def execute_request(self, ident, parent):
- status_msg = self.session.send(self.pub_socket,
+ status_msg = self.session.send(self.iopub_socket,
u'status',
{u'execution_state':u'busy'},
parent=parent
@@ -209,8 +199,8 @@ def execute_request(self, ident, parent):
code = content[u'code']
silent = content[u'silent']
except:
- logger.error("Got bad msg: ")
- logger.error(str(Message(parent)))
+ self.log.error("Got bad msg: ")
+ self.log.error(str(Message(parent)))
return
shell = self.shell # we'll need this a lot here
@@ -298,14 +288,14 @@ def execute_request(self, ident, parent):
time.sleep(self._execute_sleep)
# Send the reply.
- reply_msg = self.session.send(self.reply_socket, u'execute_reply',
+ reply_msg = self.session.send(self.shell_socket, u'execute_reply',
reply_content, parent, ident=ident)
- logger.debug(str(reply_msg))
+ self.log.debug(str(reply_msg))
if reply_msg['content']['status'] == u'error':
self._abort_queue()
- status_msg = self.session.send(self.pub_socket,
+ status_msg = self.session.send(self.iopub_socket,
u'status',
{u'execution_state':u'idle'},
parent=parent
@@ -316,17 +306,17 @@ def complete_request(self, ident, parent):
matches = {'matches' : matches,
'matched_text' : txt,
'status' : 'ok'}
- completion_msg = self.session.send(self.reply_socket, 'complete_reply',
+ completion_msg = self.session.send(self.shell_socket, 'complete_reply',
matches, parent, ident)
- logger.debug(str(completion_msg))
+ self.log.debug(str(completion_msg))
def object_info_request(self, ident, parent):
object_info = self.shell.object_inspect(parent['content']['oname'])
# Before we send this object over, we scrub it for JSON usage
oinfo = json_clean(object_info)
- msg = self.session.send(self.reply_socket, 'object_info_reply',
+ msg = self.session.send(self.shell_socket, 'object_info_reply',
oinfo, parent, ident)
- logger.debug(msg)
+ self.log.debug(msg)
def history_request(self, ident, parent):
# We need to pull these out, as passing **kwargs doesn't work with
@@ -353,18 +343,18 @@ def history_request(self, ident, parent):
else:
hist = []
content = {'history' : list(hist)}
- msg = self.session.send(self.reply_socket, 'history_reply',
+ msg = self.session.send(self.shell_socket, 'history_reply',
content, parent, ident)
- logger.debug(str(msg))
+ self.log.debug(str(msg))
def connect_request(self, ident, parent):
if self._recorded_ports is not None:
content = self._recorded_ports.copy()
else:
content = {}
- msg = self.session.send(self.reply_socket, 'connect_reply',
+ msg = self.session.send(self.shell_socket, 'connect_reply',
content, parent, ident)
- logger.debug(msg)
+ self.log.debug(msg)
def shutdown_request(self, ident, parent):
self.shell.exit_now = True
@@ -377,19 +367,19 @@ def shutdown_request(self, ident, parent):
def _abort_queue(self):
while True:
- ident,msg = self.session.recv(self.reply_socket, zmq.NOBLOCK)
+ ident,msg = self.session.recv(self.shell_socket, zmq.NOBLOCK)
if msg is None:
break
else:
assert ident is not None, \
"Unexpected missing message part."
- logger.debug("Aborting:\n"+str(Message(msg)))
+ self.log.debug("Aborting:\n"+str(Message(msg)))
msg_type = msg['msg_type']
reply_type = msg_type.split('_')[0] + '_reply'
- reply_msg = self.session.send(self.reply_socket, reply_type,
+ reply_msg = self.session.send(self.shell_socket, reply_type,
{'status' : 'aborted'}, msg, ident=ident)
- logger.debug(reply_msg)
+ self.log.debug(reply_msg)
# We need to wait a bit for requests to come in. This can probably
# be set shorter for true asynchronous clients.
time.sleep(0.1)
@@ -401,15 +391,15 @@ def _raw_input(self, prompt, ident, parent):
# Send the input request.
content = dict(prompt=prompt)
- msg = self.session.send(self.req_socket, u'input_request', content, parent)
+ msg = self.session.send(self.stdin_socket, u'input_request', content, parent)
# Await a response.
- ident, reply = self.session.recv(self.req_socket, 0)
+ ident, reply = self.session.recv(self.stdin_socket, 0)
try:
value = reply['content']['value']
except:
- logger.error("Got bad raw_input reply: ")
- logger.error(str(Message(parent)))
+ self.log.error("Got bad raw_input reply: ")
+ self.log.error(str(Message(parent)))
value = ''
return value
@@ -461,9 +451,9 @@ def _at_shutdown(self):
"""
# io.rprint("Kernel at_shutdown") # dbg
if self._shutdown_message is not None:
- self.session.send(self.reply_socket, self._shutdown_message)
- self.session.send(self.pub_socket, self._shutdown_message)
- logger.debug(str(self._shutdown_message))
+ self.session.send(self.shell_socket, self._shutdown_message)
+ self.session.send(self.iopub_socket, self._shutdown_message)
+ self.log.debug(str(self._shutdown_message))
# A very short sleep to give zmq time to flush its message buffers
# before Python truly shuts down.
time.sleep(0.01)
@@ -569,120 +559,114 @@ def start(self):
#-----------------------------------------------------------------------------
-# Kernel main and launch functions
+# Aliases and Flags for the IPKernelApp
#-----------------------------------------------------------------------------
-def launch_kernel(ip=None, xrep_port=0, pub_port=0, req_port=0, hb_port=0,
- stdin=None, stdout=None, stderr=None,
- executable=None, independent=False, pylab=False, colors=None):
- """Launches a localhost kernel, binding to the specified ports.
+flags = dict(kernel_flags)
+flags.update(shell_flags)
- Parameters
- ----------
- ip : str, optional
- The ip address the kernel will bind to.
-
- xrep_port : int, optional
- The port to use for XREP channel.
+addflag = lambda *args: flags.update(boolean_flag(*args))
- pub_port : int, optional
- The port to use for the SUB channel.
+flags['pylab'] = (
+ {'IPKernelApp' : {'pylab' : 'auto'}},
+ """Pre-load matplotlib and numpy for interactive use with
+ the default matplotlib backend."""
+)
- req_port : int, optional
- The port to use for the REQ (raw input) channel.
+aliases = dict(kernel_aliases)
+aliases.update(shell_aliases)
- hb_port : int, optional
- The port to use for the hearbeat REP channel.
+# it's possible we don't want short aliases for *all* of these:
+aliases.update(dict(
+ pylab='IPKernelApp.pylab',
+))
- stdin, stdout, stderr : optional (default None)
- Standards streams, as defined in subprocess.Popen.
+#-----------------------------------------------------------------------------
+# The IPKernelApp class
+#-----------------------------------------------------------------------------
- executable : str, optional (default sys.executable)
- The Python executable to use for the kernel process.
+class IPKernelApp(KernelApp, InteractiveShellApp):
+ name = 'ipkernel'
+
+ aliases = Dict(aliases)
+ flags = Dict(flags)
+ classes = [Kernel, ZMQInteractiveShell, ProfileDir]
+ # configurables
+ pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'inline', 'auto'],
+ config=True,
+ help="""Pre-load matplotlib and numpy for interactive use,
+ selecting a particular matplotlib backend and loop integration.
+ """
+ )
+ def initialize(self, argv=None):
+ super(IPKernelApp, self).initialize(argv)
+ self.init_shell()
+ self.init_extensions()
+ self.init_code()
+
+ def init_kernel(self):
+ kernel_factory = Kernel
+
+ kernel_map = {
+ 'qt' : QtKernel,
+ 'qt4': QtKernel,
+ 'inline': Kernel,
+ 'osx': TkKernel,
+ 'wx' : WxKernel,
+ 'tk' : TkKernel,
+ 'gtk': GTKKernel,
+ }
+
+ if self.pylab:
+ key = None if self.pylab == 'auto' else self.pylab
+ gui, backend = pylabtools.find_gui_and_backend(key)
+ kernel_factory = kernel_map.get(gui)
+ if kernel_factory is None:
+ raise ValueError('GUI is not supported: %r' % gui)
+ pylabtools.activate_matplotlib(backend)
+
+ kernel = kernel_factory(config=self.config, session=self.session,
+ shell_socket=self.shell_socket,
+ iopub_socket=self.iopub_socket,
+ stdin_socket=self.stdin_socket,
+ log=self.log
+ )
+ self.kernel = kernel
+ kernel.record_ports(self.ports)
+
+ if self.pylab:
+ pylabtools.import_pylab(kernel.shell.user_ns, backend,
+ shell=kernel.shell)
+
+ def init_shell(self):
+ self.shell = self.kernel.shell
- independent : bool, optional (default False)
- If set, the kernel process is guaranteed to survive if this process
- dies. If not set, an effort is made to ensure that the kernel is killed
- when this process dies. Note that in this case it is still good practice
- to kill kernels manually before exiting.
- pylab : bool or string, optional (default False)
- If not False, the kernel will be launched with pylab enabled. If a
- string is passed, matplotlib will use the specified backend. Otherwise,
- matplotlib's default backend will be used.
+#-----------------------------------------------------------------------------
+# Kernel main and launch functions
+#-----------------------------------------------------------------------------
- colors : None or string, optional (default None)
- If not None, specify the color scheme. One of (NoColor, LightBG, Linux)
+def launch_kernel(*args, **kwargs):
+ """Launches a localhost IPython kernel, binding to the specified ports.
+
+ This function simply calls entry_point.base_launch_kernel with the right first
+ command to start an ipkernel. See base_launch_kernel for arguments.
Returns
-------
A tuple of form:
- (kernel_process, xrep_port, pub_port, req_port)
+ (kernel_process, shell_port, iopub_port, stdin_port, hb_port)
where kernel_process is a Popen object and the ports are integers.
"""
- extra_arguments = []
- if pylab:
- 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)
- if colors is not None:
- extra_arguments.append('--colors')
- extra_arguments.append(colors)
return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
- xrep_port, pub_port, req_port, hb_port,
- stdin, stdout, stderr,
- executable, independent, extra_arguments)
+ *args, **kwargs)
def main():
- """ The IPython kernel main entry point.
- """
- parser = make_argument_parser()
- parser.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
- const='auto', help = \
-"Pre-load matplotlib and numpy for interactive use. If GUI is not \
-given, the GUI backend is matplotlib's, otherwise use one of: \
-['tk', 'gtk', 'qt', 'wx', 'osx', 'inline'].")
- parser.add_argument('--colors',
- type=str, dest='colors',
- help="Set the color scheme (NoColor, Linux, and LightBG).",
- metavar='ZMQInteractiveShell.colors')
- namespace = parser.parse_args()
-
- kernel_class = Kernel
-
- kernel_classes = {
- 'qt' : QtKernel,
- 'qt4': QtKernel,
- 'inline': Kernel,
- 'osx': TkKernel,
- 'wx' : WxKernel,
- 'tk' : TkKernel,
- 'gtk': GTKKernel,
- }
- if namespace.pylab:
- if namespace.pylab == 'auto':
- gui, backend = pylabtools.find_gui_and_backend()
- else:
- gui, backend = pylabtools.find_gui_and_backend(namespace.pylab)
- kernel_class = kernel_classes.get(gui)
- if kernel_class is None:
- raise ValueError('GUI is not supported: %r' % gui)
- pylabtools.activate_matplotlib(backend)
- if namespace.colors:
- ZMQInteractiveShell.colors=namespace.colors
-
- kernel = make_kernel(namespace, kernel_class, OutStream)
-
- if namespace.pylab:
- pylabtools.import_pylab(kernel.shell.user_ns, backend,
- shell=kernel.shell)
-
- start_kernel(namespace, kernel)
+ """Run an IPKernel as an application"""
+ app = IPKernelApp.instance()
+ app.initialize()
+ app.start()
if __name__ == '__main__':
214 IPython/zmq/kernelapp.py
View
@@ -0,0 +1,214 @@
+#!/usr/bin/env python
+"""An Application for launching a kernel
+
+Authors
+-------
+* MinRK
+"""
+#-----------------------------------------------------------------------------
+# Copyright (C) 2011 The IPython Development Team
+#
+# Distributed under the terms of the BSD License. The full license is in
+# the file COPYING.txt, distributed as part of this software.
+#-----------------------------------------------------------------------------
+
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+
+# Standard library imports.
+import os
+import sys
+
+# System library imports.
+import zmq
+
+# IPython imports.
+from IPython.core.ultratb import FormattedTB
+from IPython.core.newapplication import (
+ BaseIPythonApplication, base_flags, base_aliases
+)
+from IPython.utils import io
+from IPython.utils.localinterfaces import LOCALHOST
+from IPython.utils.traitlets import Any, Instance, Dict, Unicode, Int, Bool
+from IPython.utils.importstring import import_item
+# local imports
+from IPython.zmq.heartbeat import Heartbeat
+from IPython.zmq.parentpoller import ParentPollerUnix, ParentPollerWindows
+from IPython.zmq.session import Session
+
+
+#-----------------------------------------------------------------------------
+# Flags and Aliases
+#-----------------------------------------------------------------------------
+
+kernel_aliases = dict(base_aliases)
+kernel_aliases.update({
+ 'ip' : 'KernelApp.ip',
+ 'hb' : 'KernelApp.hb_port',
+ 'shell' : 'KernelApp.shell_port',
+ 'iopub' : 'KernelApp.iopub_port',
+ 'stdin' : 'KernelApp.stdin_port',
+ 'parent': 'KernelApp.parent',
+})
+if sys.platform.startswith('win'):
+ kernel_aliases['interrupt'] = 'KernelApp.interrupt'
+
+kernel_flags = dict(base_flags)
+kernel_flags.update({
+ 'no-stdout' : (
+ {'KernelApp' : {'no_stdout' : True}},
+ "redirect stdout to the null device"),
+ 'no-stderr' : (
+ {'KernelApp' : {'no_stderr' : True}},
+ "redirect stderr to the null device"),
+})
+
+
+#-----------------------------------------------------------------------------
+# Application class for starting a Kernel
+#-----------------------------------------------------------------------------
+
+class KernelApp(BaseIPythonApplication):
+ name='pykernel'
+ aliases = Dict(kernel_aliases)
+ flags = Dict(kernel_flags)
+
+ # the kernel class, as an importstring
+ kernel_class = Unicode('IPython.zmq.pykernel.Kernel')
+ kernel = Any()
+ poller = Any() # don't restrict this even though current pollers are all Threads
+ heartbeat = Instance(Heartbeat)
+ session = Instance('IPython.zmq.session.Session')
+ ports = Dict()
+
+ # connection info:
+ ip = Unicode(LOCALHOST, config=True,
+ help="Set the IP or interface on which the kernel will listen.")
+ hb_port = Int(0, config=True, help="set the heartbeat port [default: random]")
+ shell_port = Int(0, config=True, help="set the shell (XREP) port [default: random]")
+ iopub_port = Int(0, config=True, help="set the iopub (PUB) port [default: random]")
+ stdin_port = Int(0, config=True, help="set the stdin (XREQ) port [default: random]")
+
+ # streams, etc.
+ no_stdout = Bool(False, config=True, help="redirect stdout to the null device")
+ no_stderr = Bool(False, config=True, help="redirect stderr to the null device")
+ outstream_class = Unicode('IPython.zmq.iostream.OutStream', config=True,
+ help="The importstring for the OutStream factory")
+ displayhook_class = Unicode('IPython.zmq.displayhook.DisplayHook', config=True,
+ help="The importstring for the DisplayHook factory")
+
+ # polling
+ parent = Int(0, config=True,
+ help="""kill this process if its parent dies. On Windows, the argument
+ specifies the HANDLE of the parent process, otherwise it is simply boolean.
+ """)
+ interrupt = Int(0, config=True,
+ help="""ONLY USED ON WINDOWS
+ Interrupt this process when the parent is signalled.
+ """)
+
+ def init_crash_handler(self):
+ # Install minimal exception handling
+ sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
+ ostream=sys.__stdout__)
+
+ def init_poller(self):
+ if sys.platform == 'win32':
+ if self.interrupt or self.parent:
+ self.poller = ParentPollerWindows(self.interrupt, self.parent)
+ elif self.parent:
+ self.poller = ParentPollerUnix()
+
+ def _bind_socket(self, s, port):
+ iface = 'tcp://%s' % self.ip
+ if port <= 0:
+ port = s.bind_to_random_port(iface)
+ else:
+ s.bind(iface + ':%i'%port)
+ return port
+
+ def init_sockets(self):
+ # Create a context, a session, and the kernel sockets.
+ io.raw_print("Starting the kernel at pid:", os.getpid())
+ context = zmq.Context.instance()
+ # Uncomment this to try closing the context.
+ # atexit.register(context.term)
+
+ self.shell_socket = context.socket(zmq.XREP)
+ self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
+ self.log.debug("shell XREP Channel on port: %i"%self.shell_port)
+
+ self.iopub_socket = context.socket(zmq.PUB)
+ self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
+ self.log.debug("iopub PUB Channel on port: %i"%self.iopub_port)
+
+ self.stdin_socket = context.socket(zmq.XREQ)
+ self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
+ self.log.debug("stdin XREQ Channel on port: %i"%self.stdin_port)
+
+ self.heartbeat = Heartbeat(context, (self.ip, self.hb_port))
+ self.hb_port = self.heartbeat.port
+ self.log.debug("Heartbeat REP Channel on port: %i"%self.hb_port)
+
+ # Helper to make it easier to connect to an existing kernel, until we have
+ # single-port connection negotiation fully implemented.
+ self.log.info("To connect another client to this kernel, use:")
+ self.log.info("--external shell={0} iopub={1} stdin={2} hb={3}".format(
+ self.shell_port, self.iopub_port, self.stdin_port, self.hb_port))
+
+
+ self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
+ stdin=self.stdin_port, hb=self.hb_port)
+
+ def init_session(self):
+ """create our session object"""
+ self.session = Session(username=u'kernel')
+
+ def init_io(self):
+ """redirects stdout/stderr, and installs a display hook"""
+ # Re-direct stdout/stderr, if necessary.
+ if self.no_stdout or self.no_stderr:
+ blackhole = file(os.devnull, 'w')
+ if self.no_stdout:
+ sys.stdout = sys.__stdout__ = blackhole
+ if self.no_stderr:
+ sys.stderr = sys.__stderr__ = blackhole
+
+ # Redirect input streams and set a display hook.
+
+ if self.outstream_class:
+ outstream_factory = import_item(str(self.outstream_class))
+ sys.stdout = outstream_factory(self.session, self.iopub_socket, u'stdout')
+ sys.stderr = outstream_factory(self.session, self.iopub_socket, u'stderr')
+ if self.displayhook_class:
+ displayhook_factory = import_item(str(self.displayhook_class))
+ sys.displayhook = displayhook_factory(self.session, self.iopub_socket)
+
+ def init_kernel(self):
+ """Create the Kernel object itself"""
+ kernel_factory = import_item(str(self.kernel_class))
+ self.kernel = kernel_factory(config=self.config, session=self.session,
+ shell_socket=self.shell_socket,
+ iopub_socket=self.iopub_socket,
+ stdin_socket=self.stdin_socket,
+ log=self.log
+ )
+ self.kernel.record_ports(self.ports)
+
+ def initialize(self, argv=None):
+ super(KernelApp, self).initialize(argv)
+ self.init_session()
+ self.init_poller()
+ self.init_sockets()
+ self.init_io()
+ self.init_kernel()
+
+ def start(self):
+ self.heartbeat.start()
+ if self.poller is not None:
+ self.poller.start()
+ try:
+ self.kernel.start()
+ except KeyboardInterrupt:
+ pass
92 IPython/zmq/kernelmanager.py
View
@@ -77,7 +77,7 @@ def validate_string_dict(dct):
# ZMQ Socket Channel classes
#-----------------------------------------------------------------------------
-class ZmqSocketChannel(Thread):
+class ZMQSocketChannel(Thread):
"""The base class for the channels that use ZMQ sockets.
"""
context = None
@@ -99,7 +99,7 @@ def __init__(self, context, session, address):
address : tuple
Standard (ip, port) tuple that the kernel is listening on.
"""
- super(ZmqSocketChannel, self).__init__()
+ super(ZMQSocketChannel, self).__init__()
self.daemon = True
self.context = context
@@ -173,14 +173,14 @@ def drop_io_state_callback():
self.ioloop.add_callback(drop_io_state_callback)
-class XReqSocketChannel(ZmqSocketChannel):
+class ShellSocketChannel(ZMQSocketChannel):
"""The XREQ channel for issues request/replies to the kernel.
"""
command_queue = None
def __init__(self, context, session, address):
- super(XReqSocketChannel, self).__init__(context, session, address)
+ super(ShellSocketChannel, self).__init__(context, session, address)
self.command_queue = Queue()
self.ioloop = ioloop.IOLoop()
@@ -196,7 +196,7 @@ def run(self):
def stop(self):
self.ioloop.stop()
- super(XReqSocketChannel, self).stop()
+ super(ShellSocketChannel, self).stop()
def call_handlers(self, msg):
"""This method is called in the ioloop thread when a message arrives.
@@ -382,7 +382,7 @@ def _queue_request(self, msg):
self.add_io_state(POLLOUT)
-class SubSocketChannel(ZmqSocketChannel):
+class SubSocketChannel(ZMQSocketChannel):
"""The SUB channel which listens for messages that the kernel publishes.
"""
@@ -469,13 +469,13 @@ def _flush(self):
self._flushed = True
-class RepSocketChannel(ZmqSocketChannel):
+class StdInSocketChannel(ZMQSocketChannel):
"""A reply channel to handle raw_input requests that the kernel makes."""
msg_queue = None
def __init__(self, context, session, address):
- super(RepSocketChannel, self).__init__(context, session, address)
+ super(StdInSocketChannel, self).__init__(context, session, address)
self.ioloop = ioloop.IOLoop()
self.msg_queue = Queue()