How to use command in API with a hyphen?

Following the Mikrotik wiki here: http://wiki.mikrotik.com/wiki/Manual:API
Using Python to add a bridge and enable spanning tree on it.

For example

rez = apiros.runCommand(“interface/bridge/add”, name = “Trunk”, protocol-mode = “stp”)

Gives an error of:

“There is an error in your expression: ***keyword can’t be an expression”

NB. The code has been a little modified, I am using IDLE.

'''
Created on Oct 18, 2013

@author: dmcken

Initially copied from http://wiki.mikrotik.com/wiki/Manual:API#Example_client
'''
import pprint
import sys, time, md5, binascii, socket, select

class APIError(Exception):
    def __init__(self, message, category = -1):
        self.message = message
        self.category = category
    def __str__(self):
        return repr(self.message)


class ApiRos:
    """
    Routeros api
    """

    def __init__(self, ip):
        sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sk.connect((ip, 8728))

        self.sk = sk
        self.currenttag = 0

    def login(self, username, pwd):
        for _, attrs in self.talk(["/login"]):
            chal = binascii.unhexlify(attrs['=ret'])
        md = md5.new()
        md.update('\x00')
        md.update(pwd)
        md.update(chal)
        self.talk(["/login", "=name=" + username,
                   "=response=00" + binascii.hexlify(md.digest())])

    def talk(self, words):
        if self.writeSentence(words) == 0:
            return
        r = []
        while 1:
            i = self.readSentence();
            if len(i) == 0:
                continue
            reply = i[0]
            attrs = {}
            for w in i[1:]:
                j = w.find('=', 1)
                if (j == -1):
                    attrs[w] = ''
                else:
                    attrs[w[:j]] = w[j + 1:]
            r.append((reply, attrs))
            if reply == '!done':
                return r

    def writeSentence(self, words):
        ret = 0
        for w in words:
            self.writeWord(w)
            ret += 1
        self.writeWord('')
        return ret

    def readSentence(self):
        r = []
        while 1:
            w = self.readWord()
            if w == '':
                return r
            r.append(w)

    def writeWord(self, w):
        # print "<<< " + w
        self.writeLen(len(w))
        self.writeStr(w)

    def readWord(self):
        ret = self.readStr(self.readLen())
        # print ">>> " + ret
        return ret

    def writeLen(self, l):
        if l < 0x80:
            self.writeStr(chr(l))
        elif l < 0x4000:
            l |= 0x8000
            self.writeStr(chr((l >> 8) & 0xFF))
            self.writeStr(chr(l & 0xFF))
        elif l < 0x200000:
            l |= 0xC00000
            self.writeStr(chr((l >> 16) & 0xFF))
            self.writeStr(chr((l >> 8) & 0xFF))
            self.writeStr(chr(l & 0xFF))
        elif l < 0x10000000:
            l |= 0xE0000000
            self.writeStr(chr((l >> 24) & 0xFF))
            self.writeStr(chr((l >> 16) & 0xFF))
            self.writeStr(chr((l >> 8) & 0xFF))
            self.writeStr(chr(l & 0xFF))
        else:
            self.writeStr(chr(0xF0))
            self.writeStr(chr((l >> 24) & 0xFF))
            self.writeStr(chr((l >> 16) & 0xFF))
            self.writeStr(chr((l >> 8) & 0xFF))
            self.writeStr(chr(l & 0xFF))

    def readLen(self):
        c = ord(self.readStr(1))
        if (c & 0x80) == 0x00:
            pass
        elif (c & 0xC0) == 0x80:
            c &= ~0xC0
            c <<= 8
            c += ord(self.readStr(1))
        elif (c & 0xE0) == 0xC0:
            c &= ~0xE0
            c <<= 8
            c += ord(self.readStr(1))
            c <<= 8
            c += ord(self.readStr(1))
        elif (c & 0xF0) == 0xE0:
            c &= ~0xF0
            c <<= 8
            c += ord(self.readStr(1))
            c <<= 8
            c += ord(self.readStr(1))
            c <<= 8
            c += ord(self.readStr(1))
        elif (c & 0xF8) == 0xF0:
            c = ord(self.readStr(1))
            c <<= 8
            c += ord(self.readStr(1))
            c <<= 8
            c += ord(self.readStr(1))
            c <<= 8
            c += ord(self.readStr(1))
        return c

    def writeStr(self, str):
        n = 0;
        while n < len(str):
            r = self.sk.send(str[n:])
            if r == 0: raise RuntimeError, "connection closed by remote end"
            n += r

    def readStr(self, length):
        ret = ''
        while len(ret) < length:
            s = self.sk.recv(length - len(ret))
            if s == '': raise RuntimeError, "connection closed by remote end"
            ret += s
        return ret

    def getSocket(self):
        return self.sk

    def runCommand(self, command, **arguments):
        '''
        Run a command attempting to keep as close to the command line version,
        while maintaining an easy to use library.
        '''

        apiMessage = [command]

        if arguments != None:
            apiMessage += ["={0}={1}".format(k, v) for k, v in arguments.items()]

        rez = self.talk(apiMessage)

        # print rez

        # Remove the !done at the end of the list.
        if rez[len(rez) - 1][0] == '!done':
            doneVal = rez.pop()

        # Check for error conditions (Need to make this more efficient).
        trapVal = filter(lambda x: x[0] == '!trap', rez)
        if trapVal != []:
            trapVal = trapVal.pop()
            if 'category' in trapVal:
                category = trapVal[1]['=category']
            else:
                category = -1

            # print "TrapVal = {0}".format(trapVal[1])
            raise APIError(trapVal[1]['=message'], category)

        # Extract the data itself
        data = map(lambda x: x[1], rez)
        if data == []:
            if doneVal[1] != {}:
                data = doneVal[1]['=ret']

        return data

if __name__ == '__main__':
    apiros = ApiRos('172.18.20.2')
    apiros.login('admin', '')


    rez = apiros.runCommand("interface/bridge/add", name = "Trunk", protocol-mode = "stp")
    pprint.pprint(rez)

Which Python client exactly is this one?

None of those in the list at the bottom of the API page seems to contain a “runCommad” method.

Hi, thanks for the reply. I have edited my post to include the modified code.

Hi all,

Problem solved using double underscores to replace hyphen (minus sign).

Just add commands to the end and have fun!!

Code

'''
Created on Oct 18, 2013

@author: dmcken

Initially copied from http://wiki.mikrotik.com/wiki/Manual:API#Example_client
'''
import pprint
import sys, time, md5, binascii, socket, select

class APIError(Exception):
    def __init__(self, message, category = -1):
        self.message = message
        self.category = category
    def __str__(self):
        return repr(self.message)


class ApiRos:
    """
    Routeros api
    """

    def __init__(self, ip):
        sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sk.connect((ip, 8728))

        self.sk = sk
        self.currenttag = 0

    def login(self, username, pwd):
        for _, attrs in self.talk(["/login"]):
            chal = binascii.unhexlify(attrs['=ret'])
        md = md5.new()
        md.update('\x00')
        md.update(pwd)
        md.update(chal)
        self.talk(["/login", "=name=" + username,
                   "=response=00" + binascii.hexlify(md.digest())])

    def talk(self, words):
        if self.writeSentence(words) == 0:
            return
        r = []
        while 1:
            i = self.readSentence();
            if len(i) == 0:
                continue
            reply = i[0]
            attrs = {}
            for w in i[1:]:
                j = w.find('=', 1)
                if (j == -1):
                    attrs[w] = ''
                else:
                    attrs[w[:j]] = w[j + 1:]
            r.append((reply, attrs))
            if reply == '!done':
                return r

    def writeSentence(self, words):
        ret = 0
        for w in words:
            self.writeWord(w)
            ret += 1
        self.writeWord('')
        return ret

    def readSentence(self):
        r = []
        while 1:
            w = self.readWord()
            if w == '':
                return r
            r.append(w)

    def writeWord(self, w):
        # print "<<< " + w
        self.writeLen(len(w))
        self.writeStr(w)

    def readWord(self):
        ret = self.readStr(self.readLen())
        # print ">>> " + ret
        return ret

    def writeLen(self, l):
        if l < 0x80:
            self.writeStr(chr(l))
        elif l < 0x4000:
            l |= 0x8000
            self.writeStr(chr((l >> 8) & 0xFF))
            self.writeStr(chr(l & 0xFF))
        elif l < 0x200000:
            l |= 0xC00000
            self.writeStr(chr((l >> 16) & 0xFF))
            self.writeStr(chr((l >> 8) & 0xFF))
            self.writeStr(chr(l & 0xFF))
        elif l < 0x10000000:
            l |= 0xE0000000
            self.writeStr(chr((l >> 24) & 0xFF))
            self.writeStr(chr((l >> 16) & 0xFF))
            self.writeStr(chr((l >> 8) & 0xFF))
            self.writeStr(chr(l & 0xFF))
        else:
            self.writeStr(chr(0xF0))
            self.writeStr(chr((l >> 24) & 0xFF))
            self.writeStr(chr((l >> 16) & 0xFF))
            self.writeStr(chr((l >> 8) & 0xFF))
            self.writeStr(chr(l & 0xFF))

    def readLen(self):
        c = ord(self.readStr(1))
        if (c & 0x80) == 0x00:
            pass
        elif (c & 0xC0) == 0x80:
            c &= ~0xC0
            c <<= 8
            c += ord(self.readStr(1))
        elif (c & 0xE0) == 0xC0:
            c &= ~0xE0
            c <<= 8
            c += ord(self.readStr(1))
            c <<= 8
            c += ord(self.readStr(1))
        elif (c & 0xF0) == 0xE0:
            c &= ~0xF0
            c <<= 8
            c += ord(self.readStr(1))
            c <<= 8
            c += ord(self.readStr(1))
            c <<= 8
            c += ord(self.readStr(1))
        elif (c & 0xF8) == 0xF0:
            c = ord(self.readStr(1))
            c <<= 8
            c += ord(self.readStr(1))
            c <<= 8
            c += ord(self.readStr(1))
            c <<= 8
            c += ord(self.readStr(1))
        return c

    def writeStr(self, str):
        n = 0;
        while n < len(str):
            r = self.sk.send(str[n:])
            if r == 0: raise RuntimeError, "connection closed by remote end"
            n += r

    def readStr(self, length):
        ret = ''
        while len(ret) < length:
            s = self.sk.recv(length - len(ret))
            if s == '': raise RuntimeError, "connection closed by remote end"
            ret += s
        return ret

    def getSocket(self):
        return self.sk

    def runCommand(self, command, **arguments):
        '''
        Run a command attempting to keep as close to the command line version,
        while maintaining an easy to use library.
        '''

        apiMessage = [command]

        if arguments != None:
            apiMessage += ["={0}={1}".format(k.replace('__', '-'), v) for k, v in arguments.items()]

        rez = self.talk(apiMessage)

        # print rez

        # Remove the !done at the end of the list.
        if rez[len(rez) - 1][0] == '!done':
            doneVal = rez.pop()

        # Check for error conditions (Need to make this more efficient).
        trapVal = filter(lambda x: x[0] == '!trap', rez)
        if trapVal != []:
            trapVal = trapVal.pop()
            if 'category' in trapVal:
                category = trapVal[1]['=category']
            else:
                category = -1

            # print "TrapVal = {0}".format(trapVal[1])
            raise APIError(trapVal[1]['=message'], category)

        # Extract the data itself
        data = map(lambda x: x[1], rez)
        if data == []:
            if doneVal[1] != {}:
                data = doneVal[1]['=ret']

        return data

if __name__ == '__main__':
    apiros = ApiRos('192.168.74.106')
    apiros.login('admin', '')

    rez = apiros.runCommand("interface/bridge/add", name = "Trunk", protocol__mode = "stp")
    pprint.pprint(rez)

you could write how to use this and i then could add it to wiki, if you are ok with that.

runCommand was added and commented at each step. Only print commands were supported before with no arguments. The problem was that hyphenated commands were being interpreted as minus signs by python. Double underscores were chosen but you can change to whatever you feel in runCommand method.

if arguments != None:
apiMessage += [“={0}={1}”.format(k.replace(‘__’, ‘-’), v) for k, v in arguments.items()]


So double underscores were used to replace minus in the argument.

This can be used for example as a core API for interacting with the MT and imported to different scripts.

Example name this script APIxx.py and import into different scripts.

import APIxx
apiros = APIxx.ApiRos(‘192.168.88.1’)
apiros.login(‘admin’, ‘’)

#Replace any (-) in hyphenated commands with a (__) double underscore like this:

rez = apiros.runCommand(“interface/bridge/add”, name = “Trunk”, protocol__mode = “stp”)

#add your commands here in ther same format

rez = apiros.runCommand(“INSERT COMMANDS HERE”)

pprint.pprint(rez)

in lua i use something like this:

[“argument-with-hyphen-in-it”] = “some value for argument”

there should be a way on how you can set up this in more usable way as dictionary

Python certainly has dictionaries.

I’d guess chris96910 decided to not use them because of their arbitrary-ness, i.e. keys and values alike are treated as plain strings, rather than being “special” in any way… IMHO, using a dictionary is totally OK in the absence of a compile time allowed set, since an IDE can’t assist you either way.

Please help with setting a value..

apiros.runCommand("/ip/firewall/filter/set",'.id'="*21",dst__port="81")
apiros.runCommand("/ip/firewall/filter/set",__id="*21",dst__port="81)
apiros.runCommand("/ip/firewall/filter/set",id="*21",dst__port="81)
apiros.runCommand("/ip/firewall/filter/set",_id="*21",dst__port="81")

None of them works..