<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.3.7: http://docutils.sourceforge.net/" />
<title>This document describes the interactions needed by PyMAD with IPython.</title>
<meta name="author" content="Frédéric Mantegazza" />
<link rel="stylesheet" href="default.css" type="text/css" />
</head>
<body>
<div class="document" id="this-document-describes-the-interactions-needed-by-pymad-with-ipython">
<h1 class="title">This document describes the interactions needed by PyMAD with IPython.</h1>
<table class="docinfo" frame="void" rules="none">
<col class="docinfo-name" />
<col class="docinfo-content" />
<tbody valign="top">
<tr><th class="docinfo-name">Author:</th>
<td>Frédéric Mantegazza</td></tr>
<tr><th class="docinfo-name">Version:</th>
<td>1324</td></tr>
</tbody>
</table>
<div class="section" id="introduction">
<h1><a name="introduction">1) Introduction</a></h1>
<p>The philosophy of <strong>PyMAD</strong> is to give to the user a high-level set of tools to
drive a spectrometer. The idea is to directly make some internal python
objects available to the user, and let him combine them.</p>
<p>So, The final user just calls some methods of a few high-level objects to drive
the spectrometer. This as the advantage to make all the python scripting stuff
available, to build some new high level tools.</p>
<p>As we also need a system which can be used from several places, we use a
client/server framework, with <strong>Pyro</strong>. <strong>Pyro</strong> is a distributed objects
server. It just make some remote python objects available to a client as if they
where local.</p>
</div>
<div class="section" id="user-interaction">
<h1><a name="user-interaction">2) User interaction</a></h1>
<p>To avoid the need for the final user to write python scripts and run them to do
something, we need a simple working environment which gives the possibility to
interactively use the server objects. That's where <strong>IPython</strong> solves lots of
problems !</p>
<p><strong>IPython</strong> is an enhanced python shell. It let the user runs python code, but
has many powerfull features :</p>
<ul class="simple">
<li>history, even between sessions</li>
<li>colorized and customizable traceback</li>
<li>code completion</li>
<li>magic commands</li>
<li>much more...</li>
</ul>
<p><strong>IPython</strong> is on the client-side of <strong>PyMAD</strong>. In fact, there is a special
client which connects to the remote objects of <strong>PyMAD</strong>, launch an embbeded
<strong>IPython</strong> instance, and make the remote objects available to the user, in the
global namespace of <strong>IPython</strong>. This way, the user can drive the spectrometer
through the methods of these objects, or can build complex scripts to do complex
actions.</p>
<p><strong>PyMAD</strong> also use the magic commands to make a simple command interpreter.
The magic functions use TPG (Toy Parser Generator), a easy-to-use parser based
on regexp. These MAD-like commands are for users which don't know about python,
but also to make shortcuts, to avoid the need to write several lines of normal
python code to do some complex but repetitive tasks.</p>
<p>One important point is that <strong>PyMAD</strong> can understand both syntax, which can be
combined. Most of the time, simple commands will be used, but python code can
be more powerfull to do expert measures (with automatic feedback interaction
according to the results), or to prototype a new complex command.</p>
</div>
<div class="section" id="ipython-interactions">
<h1><a name="ipython-interactions">3) <strong>IPython</strong> interactions</a></h1>
<p>In all the following, ipshell is a IPShellEmbed instance.</p>
<p>Here is the main <strong>PyMAD/IPython</strong> interactions:</p>
<ol class="arabic">
<li><p class="first">Catch custom <em>PyMADError</em> exceptions. The solution (from CVS version), is to
use the new set_custom_sxc() method of __IP object (ie ishell.IP object):</p>
<pre class="literal-block">
def pymadHandler(self, exceptionType, exceptionValue, traceback):
    """ This is a special handler to handle custom exceptions in IPython.
    """
    
    # Handle PyMAD exceptions
    if issubclass(exceptionType, PyMADError):
        MessageLogger().error(exceptionValue.standardStr())
        
    # Handle Pyro exceptions
    elif issubclass(exceptionType, Pyro.errors.ConnectionClosedError):
        MessageLogger().critical("Connexion with server lost. Please restart it...")
        if self.code_to_run_src.find('.') != -1:
            code = self.code_to_run_src.split(".")
            obj = eval(code[0], self.user_ns)
            obj.adapter.rebindURI()
            MessageLogger().warning("Server found again. Running last command...")
            eval(self.code_to_run_src, self.user_ns)
        else:
            MessageLogger().warning("Could not automatically rebind to server. Better restart console")

    # Handle AttributeError exception
    elif exceptionType is AttributeError:
        if self.code_to_run_src.find('.') != -1:
            code = self.code_to_run_src.split(".")
            obj = eval(code[0], self.user_ns)
            if isinstance(obj, Pyro.core.DynamicProxyWithAttrs):
                MessageLogger().error("%s has no attribute %s" % (code[0], code[1]))
            else:
                self.showtraceback()
        else:
            self.showtraceback()

    # Others
    else:
        self.showtraceback()
        print "\n\n"
        print "*** Unknown exception ***"
        print "Exception type :", exceptionType
        print "Exception value:", exceptionValue
        print "Traceback      :", traceback
        print "Source code    :", self.code_to_run_src

ipshell.IP.set_custom_exc((PyMADError, Pyro.errors.ConnectionClosedError, AttributeError), pymadHandler)
</pre>
</li>
<li><p class="first">Add some new matchers for completion. As <strong>PyMAD</strong> uses remote objects,
completion only shows the client Pyro proxy. So we create a new matcher which
get the object (from the text param), call a special method on this object
which returns all available attributes (in fact, only these we want to show
to the user). Here is the code used:</p>
<pre class="literal-block">
def proxy_matches(self, text, state):
    """ Get the attributes of a remove Pyro object.
    """
    message = MessageLogger()

    # Another option, seems to work great. Catches things like ''.<tab>
    m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", text)

    matches = []
    
    if m:
        expr, attr = m.group(1, 3)
        try:
            obj = eval(expr, self.namespace)
            if isinstance(obj, Pyro.core.DynamicProxyWithAttrs):
                words = obj.getAvailableAttributes()
                n = len(attr)
                if words:
                    for word in words:
                        if word[:n] == attr and word != "__builtins__":
                            matches.append("%s.%s" % (expr, word))
    
        except NameError:
            pass
            
        except Pyro.errors.ConnectionClosedError:
            message.critical("Connexion with server lost.")
        
    return matches

ipshell.IP.set_custom_completer(proxy_matches)
ipshell.IP.Completer.merge_completions = False
</pre>
</li>
<li><p class="first">In the same way as matchers, get the docstring from the remote object instead
of the client one when using 'obj?' and 'obj.method?' syntax. This can be
done by adding a getdoc() method to the object. But it only works for the
first syntax. And if object provides __call__ method, it adds some more infos
not wanted.</p>
</li>
<li><p class="first">New exception handler. Here, the idea is to be able to present different kind
of exceptions in different ways. Some will only print a simple message, some
others will print the entire traceback (maybe a modified traceback).
This is done in the point 1.</p>
</li>
<li><p class="first">Prevent objects from being deleted by <em>del</em> keyword. This can be done with
pre-filters, but I didn't find how such filters are defined.</p>
</li>
<li><p class="first">Dynamic Prompt. Works fine using the PEP 215 syntax. When using multi-line
prompt, color changed when going up in the history. Also, the indentation for
output should be computed from the lenght of the last prompt line, and not
the whole prompt. Here is the prompt used:</p>
<pre class="literal-block">
argv = ["-pi1",
        "Ki=${globals()['Spectro'].Ki} Kf=${globals()['Spectro'].Kf}\n\
        mode=${globals()['Spectro'].driveMode} flipper=${globals()['Spectro'].flipperBeam}\n\
        PyMAD@${os.path.expandvars('$PYRO_NS_HOSTNAME')}>>> ",
        "-po",
        " ",
        "-profile",
        "pymad"]
</pre>
</li>
<li><p class="first">Access to the command-line interpreter, to have <strong>IPython</strong> interprets code
has if it was entered through keyboard (ie make difference between magic
commands and normal python code). Can be done with the ipshell.IP.push()
method, but it does not work. This will be used in the magic 'do' method.
At the moment, it can only interprets magic commands (using magic() method).</p>
</li>
<li><p class="first">Status bar at the bottom line. Like prompt, should be dynamically refreshde.</p>
</li>
</ol>
<p>Open points are 3 (for obj.method? syntax), 5, 6 (for multi-line prompt), 7, 8.</p>
</div>
</div>
</body>
</html>