Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

ZMQ terminal frontend #433

Closed
wants to merge 27 commits into from

3 participants

Omar Andres Zapata Mesa Thomas Kluyver Min RK
Omar Andres Zapata Mesa

Frontend for two process model.
Done:
->Some KernelManager's Hendlers implemented
->Prompt Numeration for inputs and outputs
->Indentation
->All command line arguments for connection with a opened kernel eg: frontend -e --xreq 52797 --sub 38906 --rep 40895 --hb 4320
->Tab-completion bugs fixed (Thanks to Thomas)
->raw_input captured
->Print beautiful tracebacks
TODO
->Support colors in outputs
->Support Crtl+C to send the signal SIGINT
->Heartbeat's verification.

Thanks!

Thomas Kluyver
Owner

An organisational detail - would it make sense to put these files in a separate module under frontend? Something like IPython/frontend/zmq-terminal?

Omar Andres Zapata Mesa

I think that is good idea.

Thomas Kluyver
Owner

We should also have a script that gets installed so we can start it easily. Something like ipython-zmqterm?

Thomas Kluyver
Owner

Oh, and unicode input! a = u"€" blows it up at the moment.

Thomas Kluyver
Owner

Would it make sense to subclass TerminalInteractiveShell, and overload the relevant methods (like run_cell?) A lot of the stuff like readline handling, inputsplitter, displaying prompts, and so on, is going to be the same.

Min RK
Owner

I've updated this to subclass the InteractiveShell and Application from the regular terminal frontend. This means the configuration, colors, prompts, etc. are all inherited, but the underlying execution and completion code you wrote is the same.

See here.

Still some work to be done, particularly wrt KeyboardInterrupts, but basic functionality is working.

Thomas Kluyver
Owner

Closing this one, as it's included in Min's PR #708.

Thomas Kluyver takluyver closed this
Damián Avila damianavila referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 30, 2011
  1. Omar Andres Zapata Mesa
Commits on Feb 6, 2011
  1. Omar Andres Zapata Mesa

    working in handlers

    omazapa authored
Commits on Mar 29, 2011
  1. Omar Andres Zapata Mesa

    working in tab completion

    omazapa authored
  2. Omar Andres Zapata Mesa

    completer not working fine

    omazapa authored
  3. Omar Andres Zapata Mesa
Commits on Apr 3, 2011
  1. Omar Andres Zapata Mesa
Commits on Apr 4, 2011
  1. Omar Andres Zapata Mesa

    -mworking in tab-completion

    omazapa authored
  2. Omar Andres Zapata Mesa
Commits on May 5, 2011
  1. Thomas Kluyver
Commits on May 6, 2011
  1. Omar Andres Zapata Mesa

    Merge pull request #1 from takluyver/frontend-logging

    omazapa authored
    Frontend logging
Commits on May 8, 2011
  1. Omar Andres Zapata Mesa
Commits on May 12, 2011
  1. Omar Andres Zapata Mesa
Commits on May 13, 2011
  1. Omar Andres Zapata Mesa
  2. Omar Andres Zapata Mesa
  3. Omar Andres Zapata Mesa

    traceback support added

    omazapa authored
  4. Omar Andres Zapata Mesa
Commits on May 15, 2011
  1. Thomas Kluyver

    Replace tabs with spaces

    takluyver authored
  2. Thomas Kluyver
  3. Thomas Kluyver
  4. Thomas Kluyver
  5. Thomas Kluyver
  6. Thomas Kluyver
Commits on May 24, 2011
  1. Thomas Kluyver
  2. Thomas Kluyver
  3. Thomas Kluyver
Commits on May 25, 2011
  1. Omar Andres Zapata Mesa

    Merge pull request #3 from takluyver/refactor-zmqterminal

    omazapa authored
    Refactor zmqterminal
Commits on May 26, 2011
  1. Omar Andres Zapata Mesa
This page is out of date. Refresh to see the latest.
0  IPython/frontend/zmqterminal/__init__.py
View
No changes.
41 IPython/frontend/zmqterminal/completer.py
View
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+import readline
+from Queue import Empty
+
+class ClientCompleter2p(object):
+ """Client-side completion machinery.
+
+ How it works: self.complete will be called multiple times, with
+ state=0,1,2,... When state=0 it should compute ALL the completion matches,
+ and then return them for each value of state."""
+
+ def __init__(self,client, km):
+ self.km = km
+ self.matches = []
+ self.client = client
+
+ def complete_request(self,text):
+ line = readline.get_line_buffer()
+ cursor_pos = readline.get_endidx()
+
+ # send completion request to kernel
+ # Give the kernel up to 0.5s to respond
+ msg_id = self.km.xreq_channel.complete(text=text, line=line,
+ cursor_pos=cursor_pos)
+
+ msg_xreq = self.km.xreq_channel.get_msg(timeout=0.5)
+ if msg_xreq['parent_header']['msg_id'] == msg_id:
+ return msg_xreq["content"]["matches"]
+ return []
+
+ def complete(self, text, state):
+ if state == 0:
+ try:
+ self.matches = self.complete_request(text)
+ except Empty:
+ print('WARNING: Kernel timeout on tab completion.')
+
+ try:
+ return self.matches[state]
+ except IndexError:
+ return None
262 IPython/frontend/zmqterminal/frontend.py
View
@@ -0,0 +1,262 @@
+# -*- coding: utf-8 -*-
+"""Frontend of ipython working with python-zmq
+
+Ipython's frontend, is a ipython interface that send request to kernel and proccess the kernel's outputs.
+
+For more details, see the ipython-zmq design
+"""
+#-----------------------------------------------------------------------------
+# 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
+#-----------------------------------------------------------------------------
+from __future__ import print_function
+
+import __builtin__
+import sys
+import os
+from Queue import Empty
+import readline
+import rlcompleter
+
+#-----------------------------------------------------------------------------
+# Imports from ipython
+#-----------------------------------------------------------------------------
+from IPython.external.argparse import ArgumentParser
+from IPython.core.inputsplitter import IPythonInputSplitter
+from IPython.zmq.blockingkernelmanager import BlockingKernelManager as KernelManager
+from IPython.frontend.zmqterminal.completer import ClientCompleter2p
+
+#-----------------------------------------------------------------------------
+# Network Constants
+#-----------------------------------------------------------------------------
+
+from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
+class Frontend(object):
+ """This class is a simple frontend to ipython-zmq
+
+ NOTE: this class uses kernelmanager to manipulate sockets
+
+ Parameters:
+ -----------
+ kernelmanager : object
+ instantiated object from class KernelManager in module kernelmanager
+
+ """
+
+ def __init__(self, kernelmanager):
+ self.km = kernelmanager
+ self.session_id = self.km.session.session
+ self.completer = ClientCompleter2p(self, self.km)
+ readline.parse_and_bind("tab: complete")
+ readline.parse_and_bind('set show-all-if-ambiguous on')
+ readline.set_completer(self.completer.complete)
+
+ history_path = os.path.expanduser('~/.ipython/history')
+ if os.path.isfile(history_path):
+ rlcompleter.readline.read_history_file(history_path)
+ else:
+ print("history file cannot be read.")
+
+ self.messages = {}
+
+ self._splitter = IPythonInputSplitter()
+ self.code = ""
+
+ self.prompt_count = 0
+ self._get_initial_prompt()
+
+ def _get_initial_prompt(self):
+ self._execute('', hidden=True)
+
+ def interact(self):
+ """Gets input from console using inputsplitter, then
+ while you enter code it can indent and set index id to any input
+ """
+
+ try:
+ print()
+ self._splitter.push(raw_input(' In[%i]: '%self.prompt_count+self.code))
+ while self._splitter.push_accepts_more():
+ self.code = raw_input(' .....: '+' '*self._splitter.indent_spaces)
+ self._splitter.push(' '*self._splitter.indent_spaces+self.code)
+ self._execute(self._splitter.source,False)
+ self._splitter.reset()
+ except KeyboardInterrupt:
+ print('\nKeyboardInterrupt\n')
+ pass
+
+
+ def start(self):
+ """Start the interaction loop, calling the .interact() method for each
+ input cell.
+ """
+ while True:
+ try:
+ self.interact()
+ except KeyboardInterrupt:
+ print('\nKeyboardInterrupt\n')
+ pass
+ except EOFError:
+ answer = ''
+ while True:
+ answer = raw_input('\nDo you really want to exit ([y]/n)?')
+ if answer == 'y' or answer == '' :
+ self.km.shutdown_kernel()
+ sys.exit()
+ elif answer == 'n':
+ break
+
+ def _execute(self, source, hidden = True):
+ """ Execute 'source'. If 'hidden', do not show any output.
+
+ See parent class :meth:`execute` docstring for full details.
+ """
+ msg_id = self.km.xreq_channel.execute(source, hidden)
+ while not self.km.xreq_channel.msg_ready():
+ try:
+ self.handle_rep_channel(timeout=0.1)
+ except Empty:
+ pass
+ self.handle_execute_reply(msg_id)
+
+ def handle_execute_reply(self, msg_id):
+ msg_xreq = self.km.xreq_channel.get_msg()
+ if msg_xreq["parent_header"]["msg_id"] == msg_id:
+ if msg_xreq["content"]["status"] == 'ok' :
+ self.handle_sub_channel()
+
+ elif msg_xreq["content"]["status"] == 'error':
+ for frame in msg_xreq["content"]["traceback"]:
+ print(frame, file=sys.stderr)
+
+ self.prompt_count = msg_xreq["content"]["execution_count"] + 1
+
+
+ def handle_sub_channel(self):
+ """ Method to procces subscribe channel's messages
+
+ This method reads a message and processes the content in different
+ outputs like stdout, stderr, pyout and status
+
+ Arguments:
+ sub_msg: message receive from kernel in the sub socket channel
+ capture by kernel manager.
+ """
+ while self.km.sub_channel.msg_ready():
+ sub_msg = self.km.sub_channel.get_msg()
+ if self.session_id == sub_msg['parent_header']['session']:
+ if sub_msg['msg_type'] == 'status' :
+ if sub_msg["content"]["execution_state"] == "busy" :
+ pass
+
+ elif sub_msg['msg_type'] == 'stream' :
+ if sub_msg["content"]["name"] == "stdout":
+ print(sub_msg["content"]["data"], file=sys.stdout, end="")
+ sys.stdout.flush()
+ elif sub_msg["content"]["name"] == "stderr" :
+ print(sub_msg["content"]["data"], file=sys.stderr, end="")
+ sys.stderr.flush()
+
+ elif sub_msg['msg_type'] == 'pyout' :
+ print("Out[%i]:"%sub_msg["content"]["execution_count"],
+ sub_msg["content"]["data"]["text/plain"],
+ file=sys.stdout)
+ sys.stdout.flush()
+
+ def handle_rep_channel(self, timeout=0.1):
+ """ Method to capture raw_input
+ """
+ msg_rep = self.km.rep_channel.get_msg(timeout=timeout)
+ if self.session_id == msg_rep["parent_header"]["session"] :
+ raw_data = raw_input(msg_rep["content"]["prompt"])
+ self.km.rep_channel.input(raw_data)
+
+
+
+
+def start_frontend():
+ """ Entry point for application.
+
+ """
+ # 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'].")
+ egroup.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'
+ else:
+ colors=None
+
+ # Create a KernelManager and start a kernel.
+ kernel_manager = KernelManager(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
+ else:
+ kwargs['colors']=colors
+ if args.pylab:
+ kwargs['pylab']=args.pylab
+ kernel_manager.start_kernel(**kwargs)
+
+
+ kernel_manager.start_channels()
+
+ frontend=Frontend(kernel_manager)
+ return frontend
+
+def main():
+ frontend=start_frontend()
+ frontend.start()
+
+if __name__ == "__main__" :
+ main()
6 IPython/scripts/ipython-zmqterminal
View
@@ -0,0 +1,6 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from IPython.frontend.zmqterminal.frontend import main
+
+main()
26 IPython/zmq/blockingkernelmanager.py
View
@@ -16,6 +16,7 @@
# Stdlib
from Queue import Queue, Empty
+from threading import Event
# Our own
from IPython.utils import io
@@ -95,10 +96,31 @@ def get_msgs(self):
class BlockingRepSocketChannel(RepSocketChannel):
-
+ def __init__(self, context, session, address=None):
+ super(BlockingRepSocketChannel, self).__init__(context, session, address)
+ self._in_queue = Queue()
+
def call_handlers(self, msg):
#io.rprint('[[Rep]]', msg) # dbg
- pass
+ self._in_queue.put(msg)
+
+ def get_msg(self, block=True, timeout=None):
+ "Gets a message if there is one that is ready."
+ return self._in_queue.get(block, timeout)
+
+ def get_msgs(self):
+ """Get all messages that are currently ready."""
+ msgs = []
+ while True:
+ try:
+ msgs.append(self.get_msg(block=False))
+ except Empty:
+ break
+ return msgs
+
+ def msg_ready(self):
+ "Is there a message that has been received?"
+ return not self._in_queue.empty()
class BlockingHBSocketChannel(HBSocketChannel):
7 setupbase.py
View
@@ -125,7 +125,8 @@ def find_packages():
add_package(packages, 'frontend')
add_package(packages, 'frontend.qt')
add_package(packages, 'frontend.qt.console', tests=True)
- add_package(packages, 'frontend.terminal', tests=True)
+ add_package(packages, 'frontend.terminal', tests=True)
+ add_package(packages, 'frontend.zmqterminal')
add_package(packages, 'lib', tests=True)
add_package(packages, 'parallel', tests=True, scripts=True,
others=['apps','engine','client','controller'])
@@ -277,7 +278,8 @@ def find_scripts(entry_points=False):
'iplogger = IPython.parallel.apps.iploggerapp:launch_new_instance',
'ipcluster = IPython.parallel.apps.ipclusterapp:launch_new_instance',
'iptest = IPython.testing.iptest:main',
- 'irunner = IPython.lib.irunner:main'
+ 'irunner = IPython.lib.irunner:main',
+ 'ipython-zmqterminal = IPython.frontend.zmqterminal.frontend:main'
]
gui_scripts = [
'ipython-qtconsole = IPython.frontend.qt.console.ipythonqt:main',
@@ -293,6 +295,7 @@ def find_scripts(entry_points=False):
pjoin(parallel_scripts, 'iplogger'),
pjoin(main_scripts, 'ipython'),
pjoin(main_scripts, 'ipython-qtconsole'),
+ pjoin(main_scripts, 'ipython-zmqterminal'),
pjoin(main_scripts, 'pycolor'),
pjoin(main_scripts, 'irunner'),
pjoin(main_scripts, 'iptest')
Something went wrong with that request. Please try again.