Modo Socket comms wrapper

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:

1
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.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# 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