<?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>1289</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-needs">
<h1><a name="ipython-needs">3) <strong>IPython</strong> needs</a></h1>
<p>In order to give users all these powerfull features, <strong>PyMAD</strong> needs to
interacts very closely with <strong>IPython</strong>. In the actual state of IPython we patch
some internal classes, by rebinding methods to custom ones. This is not very
clean, and can lead to problems if internal structures of new ipyton releases
change.</p>
<p>So, here is the main <strong>PyMAD</strong> interactions needed:</p>
<ol class="arabic">
<li><p class="first">Catch custom <em>PyMADError</em> exceptions (now done with rebinding
IPython.iplib.InteractiveShell.runcode() method), <strong>with the possibility to
get the inital text code involved in the exception</strong>. For the moment, in the
runcode() method, we only get the code object, from which it is impossible to
retreive the text. Here is the code used:</p>
<pre class="literal-block">
def runcode(self, code_obj):
    &quot;&quot;&quot;Execute a code object.

    When an exception occurs, self.showtraceback() is called to display a
    traceback.&quot;&quot;&quot;
    log = Logger()
    message = MessageLogger()

    # Set our own excepthook in case the user code tries to call it
    # directly, so that the IPython crash handler doesn't get triggered
    old_excepthook,sys.excepthook = sys.excepthook, self.excepthook
    try:
        try:
            exec code_obj in self.locals
        finally:
            # Reset our crash handler in place
            sys.excepthook = old_excepthook

    except SystemExit, message:
        if str(message)=='IPythonExit':
            raise
        else:
            self.resetbuffer()
            self.showtraceback()
            warn( __builtin__.exit,level=1)
            
    # We just add this few lines
    #except AttributeError, message:
        #print message
        # TODO: Use the same syntax (color) as IPython
        
    except PyMADError, exc:
        message.error(exc.standardStr())
        #log.exception(&quot;Console execution error&quot;)
   
    except Pyro.errors.ConnectionClosedError:
        message.critical(&quot;Pyro connexion closed&quot;)
        log.exception(&quot;Console execution error&quot;)
        # TODO: get the object and call rebindURI()

    except:
        self.showtraceback()
    else:
        if code.softspace(sys.stdout, 0):
            print
</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 added a new matcher by
adding a IPython.iplib.MagicCompleter.proxy_matches() method, and insert this
matcher in ipshell.IP.Completer.matchers list. The new matcher 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). Give
the possibility to return all matchers, or only the no None first. Here is
the code used:</p>
<pre class="literal-block">
def proxy_matches(self, text, state):
    &quot;&quot;&quot; Get the attribute of a remove Pyro object.
    &quot;&quot;&quot;
    log = Logger('client')

    # Another option, seems to work great. Catches things like ''.&lt;tab&gt;
    m = re.match(r&quot;(\S+(\.\w+)*)\.(\w*)$&quot;, text)

    if not m:
        return []
    expr, attr = m.group(1, 3)
    matches = []
    try:
        object = eval(expr, self.namespace)
        if isinstance(object, Pyro.core.DynamicProxyWithAttrs):
            words = object.getAvailableAttributes()
            #if hasattr(object,'__class__'):
                #words.append('__class__')
                #words = words + get_class_members(object.__class__)
            matches = []
            n = len(attr)
            if words:
                for word in words:
                    if word[:n] == attr and word != &quot;__builtins__&quot;:
                        matches.append(&quot;%s.%s&quot; % (expr, word))

    except NameError:
        pass
        
    except Pyro.errors.ConnectionClosedError:
        log.error(&quot;Connexion closed&quot;)
        object.adapter.rebindURI() # Should be moved to runcode()
        matches = [&quot;&quot;]

    return matches

ipshell.IP.Completer.proxy_matches = new.instancemethod(proxy_matches,
                                                        ipshell.IP.Completer)
ipshell.IP.Completer.matchers.insert(0, ipshell.IP.Completer.proxy_matches)
</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 'object?' syntaxe. This could be done on the
same idea: calling a special method on the object, method returning the doc
of our remote object).</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).</p>
</li>
<li><p class="first">Prevent objects from beeing deleted by <em>del</em> keyword <a class="footnote-reference" href="#id3" id="id1" name="id1">[1]</a>.</p>
</li>
<li><p class="first">Prompt modification at run-time <a class="footnote-reference" href="#id4" id="id2" name="id2">[2]</a>.</p>
</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).</p>
</li>
</ol>
<table class="docutils footnote" frame="void" id="id3" rules="none">
<colgroup><col class="label" /><col /></colgroup>
<tbody valign="top">
<tr><td class="label"><a class="fn-backref" href="#id1" name="id3">[1]</a></td><td>Can be done with pre-filters. Have to be tested, but should work.</td></tr>
</tbody>
</table>
<table class="docutils footnote" frame="void" id="id4" rules="none">
<colgroup><col class="label" /><col /></colgroup>
<tbody valign="top">
<tr><td class="label"><a class="fn-backref" href="#id2" name="id4">[2]</a></td><td>Can be done with prompt_specials_color dict. Have to be tested,
but should work.</td></tr>
</tbody>
</table>
</div>
</div>
</body>
</html>