Modo Socket comms wrapper

From The Foundry MODO SDK wiki
Jump to: navigation, search

ModoSock.py

A python wrapper for modo's telnet comms. Uses 'raw' socket mode and implements several convenience methods based on the existing modo lx module methods (eval(), eval1() & evaN(). If you import this module like this:

import modosock as lx

then you can write code in your external script which should, with very little modification, transfer directly into a modo script and run. For example, if you started a regular python shell and imported the modosock module as lx yopu should be able to prototype code live from the shell and copy/paste it into a file to run inside modo.

# python
 
################################################################################
#
# modosock.py
#
# Version: 1.002
#
# Author: Gwynne Reddick
#
# Description: Wrapper for modo socket connection. uses modo's raw mode
# 
#
# Usage: Instantiate ModoSock class with hostname and port number. Use one of
#        the three eval commands to send command strings to modo. The eval
#        commands operate like their lx.eval counterparts
#
# Last Update 19:02 06/06/10 
#
################################################################################
 
import socket
 
# defines the end of a transmit from modo, it's actually the prompt character
# sent after the last result
_END = '> \0'  
 
# status codes
_ERROR = -1
_OK = 1
_INFO = 2
 
 
class ModoSockError(Exception):
    pass
 
 
class ModoError(ModoSockError):
    """Raised when an error message is received from modo
 
    Attributes:
        message  -  the error message sent from modo
        command  - the command that was executed
 
    """
    def __init__(self, command, value):
        self.value = value
        self.command = command
    def __str__(self):
        return '%s\ncommand string: %s' % (self.value, self.command)
 
    def get_error(self):
        return '%s\ncommand string: %s' % (self.value, self.command)
 
 
class UnrecognisedLineError(ModoSockError):
    """Raised when an incoming line is found that doesn't start with one of the
    known line start characters. This probably means that the current line is a
    continuation of the previous one.
 
    Attributes:
        command   -  command that was sent to modo
        prevline  -  text of line that was received before the error line
        currline  -  text of line that threw the error
 
    """
 
    def __init__(self, command, prevline, currline):
        self.command = command
        self.prevline = prevline
        self.currline = currline
 
    def __str__(self):
        return 'command: %s\nresult: %s\n%s' % (self.command, self.prevline, self.currline)
 
    def get_error(self):
        return 'command: %s\nresult: %s\n%s' % (self.command, self.prevline, self.currline)
 
 
class ModoSock(object):
    """Raw socket communication class.
 
    Wraps a socket connection for cummunicating with modo in raw mode. Implements
    three methods that work/behave like their lx module counterparts.
 
    """
 
    def __init__(self, host, port):
        try:
            self._con = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self._con.connect((host, port))
        except:
            raise
        self.status = _OK
        self.message = ''
        self._con.recv(1024)  # eat the first prompt character
 
    def close(self):
        """Close the connection with modo.
 
        Be sure to call this at the end of any script
 
        """
 
        self._con.close()
 
    def eval(self, command):
        """Send a command to modo.
 
        Unlike the regular lx.eval command in modo, this implementation does not
        return a value. It should therefore be used for executing in 'command'
        mode, ie for executing commands in modo that do not return a value - so don't
        use for queries!!!
 
        """
 
        result = self._get_result(command)
 
    def eval1(self, command):
        """Send a command to modo. Behaves like lx.eval1
 
        Return value is always either a singleton or None. If modo returns more
        than one result only the first will be returned by this function
 
        """
 
        result = self._get_result(command)
        if self.status == _OK:
            return result[0]
 
    def evalN(self, command):
        """Send a command to modo. Behaves like lx.evalN
 
        Return value is always either a list or None.
 
        """
 
        result = self._get_result(command)
        if self.status == _OK:
            return result
 
    def _get_result(self, command):
        result = []
        # send command
        self._con.sendall('%s\0' % command)
        alldata = ''
        # collect data
        while 1:
            data = self._con.recv(1024)
            if not data: break
            if _END in data:
                alldata += data[:data.find(_END)]
                break
            alldata += data
        # process data
        alldata = alldata.split('\0')
        alldata.remove('')  # remove trailing blank line from alldata
        for item in alldata:
            if item.startswith('- error'):
                # modo has returned an error, set self.status to error and
                # self.message to the result value so they can be retrieved by
                # by calling scripts and then raise an error
                self.status = _ERROR
                self.message = item[2:]
                raise ModoError(command, item[2:])
            elif item[0] in ['#','!','@']:
                self.status = _INFO
                self.message = item
                break
            elif item.startswith('+ ok'):
                self.status = _OK
            elif item.startswith(':'):
                result.append(item[2:])
            else:
                raise UnrecognisedLineError(command, alldata[alldata.index(item)-1], item)
        return result