Can you run Python and call Sikuli from there? --- use XML-RPC

Asked by Neil Stoker on 2010-09-09

*** see related question: https://answers.launchpad.net/sikuli/+question/162676/+edit
*** see faq 1331
*** a pure Java Solution (by Enix Shen https://launchpad.net/~enix12enix)
      at https://github.com/enix12enix/sikuliserver

------------------------------------------------------------------------------------

Is it possible to run Python scripts (Python, not Jython) that somehow import / call Sikuli functionality from there?

The reason I ask is that I've got some code that uses various Python modules and I don't think I can get them to run under Jython (which Sikuli uses; I could be wrong in that supposition as I'm new to Jython, but some Google searches suggested this was the case)

I found some answers that hint at doing this sort of thing, but they are not that explicit in what they're saying and I get the impression that sometimes in a few of them they say Python when they mean Jython (I realise they share the same syntax, but they're not identical in terms of being able to import libraries like BeautifulSoup etc as far as I can tell)

Some examples that seem related:
How to include Sikuli in other python / jython programs
https://answers.launchpad.net/sikuli/+question/108782

Running tests using Python's unittest
https://answers.launchpad.net/sikuli/+question/100436

Any help or pointers gratefully appreciated

Thanks,
Neil

Question information

Language:
English Edit question
Status:
Solved
For:
Sikuli Edit question
Assignee:
RaiMan Edit question
Last query:
2010-09-09
Last reply:
2011-06-30
RaiMan (raimund-hocke) said : #1

The Sikuli environment cannot be imported directly into Python, but there may be an interesting solution, that works like a charm and is easy to implement/use: XML-RPC

Just make your scripts in Sikuli in a granularity you need, put everything you want to call in def()'s, add an XML-RPC server with the def()'s registered, put it in a .skl and start it. It waits for calls to your def()'s.

In your python just use XML-RPC client requests to trigger the processing of your Sikuli's.

example:

the sever is a Sikuli script:
def myWorkflow(parm):
    popup("OK - from SimpleXMLRPCServer (" + str(parm) + ")")
    return "OK"

from SimpleXMLRPCServer import SimpleXMLRPCServer as Server
srv = Server(("127.0.0.1", 1337)) # as an example on the same machine
if not srv: exit(1)
srv.register_function(myWorkflow)
srv.serve_forever()

save it to a .skl and run it. It will wait for requests.

On the client side (in your case Python) use:
import xmlrpclib
cli = xmlrpclib.ServerProxy("http://127.0.0.1:1337")
retval = cli.workflow("Just to say hello from Python")

running this snippet you will see the popup from your Sikuli script.

There are some restrictions on the use of parameter types, but the basics at least between Python and Sikuli work.

The investment, you have to do, is to have a <srv.register_function(def-name)> for every def() you want to call. So at the bottom you would have to implement your own "api" if you want to use the atomic parts of Sikuli.

The overhead in time and resources seems to be negligible.

Tested fully on Mac (Sikuli, Python and Applescript as clients). On Win7 at least the server works (client test over the local network from Mac to Win machine) - don't have Python on my Win7 - will test it with Java - but think it is no problem.

Dirk (dps42) said : #2

I tested the client on Win 7, Python 2.7, 32 Bit version, here is the traceback:

Traceback (most recent call last):
  File "c:\Users\...\sikuli-Docs\sikuliTest.py", line 3, in <module>
    retval = cli.workflow("Just to say hello from Python")
  File "c:\Python27-32bit\lib\xmlrpclib.py", line 1224, in __call__
    return self.__send(self.__name, args)
  File "c:\Python27-32bit\lib\xmlrpclib.py", line 1570, in __request
    verbose=self.__verbose
  File "c:\Python27-32bit\lib\xmlrpclib.py", line 1264, in request
    return self.single_request(host, handler, request_body, verbose)
  File "c:\Python27-32bit\lib\xmlrpclib.py", line 1297, in single_request
    return self.parse_response(response)
  File "c:\Python27-32bit\lib\xmlrpclib.py", line 1468, in parse_response
    return u.close()
  File "c:\Python27-32bit\lib\xmlrpclib.py", line 793, in close
    raise Fault(**self._stack[0])
xmlrpclib.Fault: <Fault 1: '<type \'exceptions.Exception\'>:method "workflow" is not supported'>
Process terminated with an exit code of 1

Any suggestions?

RaiMan (raimund-hocke) said : #3

Sorry for the faulty description.

If you have done it exactly as I wrote it down, then you have to say in the client:

import xmlrpclib
cli = xmlrpclib.ServerProxy("http://127.0.0.1:1337")
retval = cli.myWorkflow("Just to say hello from Python")

since with the server above you have registerd a def() called myWorkflow()

sorry again. good luck.

come back if you have problems. I know it works ;-)

More information can be found in FAQ 1331

Dirk (dps42) said : #4

...aaaaannnnd that works perfectly thanks. I should have spotted it myself.

Michael Wei (weizhuyu) said : #5

Hi All,
I try this soultion in my machine, click() and capture() functions works well, but when I try to invoke find() function in sikuli, got errors below:
xmlrpclib.Fault: <Fault 1: "<type 'exceptions.TypeError'>:cannot marshal <type 'org.sikuli.script.Match'> objects">

Do you have any suggestion here?

Thanks

RaiMan (raimund-hocke) said : #6

a find() operation returns a match object.

Without any additional actions, only basic variable types like numbers and strings are supported at the XML-RPC.

in this case the server tries to return a match object to the client, but this cannot be mapped (called marshalling in this context) to the return structures (read more: http://docs.python.org/library/xmlrpclib.html#module-xmlrpclib)

So since you cannot do anything with a match object in Python, just use str(find()) witch returns the string, that would be printed in Sikuli when saying "print find()".

Michael Wei (weizhuyu) said : #7

Many thanks for help, RaiMan. Now it works perfect. I wrote a class Shell(InteractiveConsole) and redirect stdin/stdou/stderr to a function and register this function as xmlrpc function. Now we can use xmlrpc client to send Jython command by string to Interactive shell and return the result rpc client.

RaiMan (raimund-hocke) said : #8

congratulations :-))

could you post the class Shell(InteractiveConsole) here?

Michael Wei (weizhuyu) said : #9

Sure, some codes is from net user, hope someone could get better idea from it.
Copied here, please save it to c:\InteractiveConsole.py and run it in command (for windows)
>>>java -cp "%SIKULI_HOME%\sikuli-script.jar" org.python.util.jython c:\InteractiveConsole.py

#! /usr/bin/env python
#coding=utf-8
import sys, time
from SimpleXMLRPCServer import SimpleXMLRPCServer as Server
from code import InteractiveConsole
#from sikuli.Sikuli import *

#java -jar c:/sikuli-script.jar -s c:\InteractiveConsole.py
#java -cp c:/sikuli-script.jar org.python.util.jython c:\InteractiveConsole.py

class FileCacher:
    "Cache the stdout text so we can analyze it before returning it"
    def __init__(self): self.reset()
    def reset(self): self.out = []
    def write(self,line): self.out.append(line)
    def flush(self):
        output = ''.join(self.out)
        self.reset()
        return output

class Shell(InteractiveConsole):
    "Wrapper around Python that can filter input/output to the shell"
    def __init__(self):
        self.stdout = sys.stdout
        self.stderr = sys.stderr
        self.cache = FileCacher()
        InteractiveConsole.__init__(self)
        return

    def get_output(self): sys.stdout = self.cache
    def get_errput(self): sys.stderr = self.cache
    def return_output(self): sys.stdout = self.stdout
    def return_errput(self): sys.stderr = self.stderr

    def push(self,line):
        self.get_output()
        self.get_errput()

        # you can filter input here by doing something like
        # line = filter(line)
        InteractiveConsole.push(self,line)

        self.return_output()
        self.return_errput()

        output = self.cache.flush()
        # you can filter the output here by doing something like
        # output = filter(output)
        return output

def runcmd(str):
    print str
    return sh.push(str)

if __name__ == '__main__':

    server_name = "127.0.0.1"
    server_port = 1337
    global sh

    srv = Server((server_name, server_port)) # as an example on the same machine

    #print the init information
    if not srv:
        print "Fail to start SimpleXMLRPCServer %s:%s" %(server_name, server_port)
        exit(1)
    else:
        print "Started SimpleXMLRPCServer: %s:%s" %(server_name, server_port)
        print time.ctime()+"\n"

    sh = Shell()
    runcmd("from sikuli.Sikuli import *")

    srv.register_function(runcmd)
    srv.serve_forever()

RaiMan (raimund-hocke) said : #10

great. thanks.

Enix Shen (enix12enix) said : #11

I create something to remote excute sikuli script.

Hope it is useful

See here:

https://github.com/enix12enix/sikuliserver

daluu (cuuld) said : #12

This concept of Sikuli over XML-RPC is a good option for use across language platforms besides Python and Java, and is also an option for integration with Robot Framework as a test library (implemented as Jython or Java remote library).

daluu (cuuld) said : #13

If one prefers to run Sikuli operations via API rather than Sikuli script, then can also look at this:

http://code.google.com/p/simplesikuli

Aravind (a4aravind) said : #14

Sorry to interfere here... If I'm running the sikuli script(server) & python scripts(client) in different machines, what changes should I make in the code (in your reply #1)

I have tried changing the code like the following:

srv = Server(("server_machine_ip", 8000)) # in the server code

and

cli = xmlrpclib.ServerProxy("http://server_machine_ip:8000") # in the client code

but doesn't seems to be working fine...Thanks in advance !

Aravind (a4aravind) said : #15

Yeaaaah...with the above modifications in the code, finally it is working !!! The eclipse launch was causing a big delay all the time before...Thanks a lot

Anubhav (foruanubhav) said : #16

Anubhav suggests this article as an answer to your question:
Hi Raiman,
I have followed the steps provided and able to run sikuli script through client python program.
I want to integrate Sikuli and Selenium-python with Eclipse IDE. So i want to execute the python scipt through Eclipse Python interpretor.

Can you please guide how can i do this ? Any help will be appreciated.
FAQ #1331: “Instantly start Sikuli scripts from anywhere using XML-RPC”.

Rahul Kumar (rahulkumar) said : #17

@Dirk (dps42)
What is the thing you spotted that Raiman has suggested, when you were getting "xmlrpclib.Fault: <Fault 1: '<type \'exceptions.Exception\'>:method "workflow" is not supported'>" and you got it working?

I am starting the server by running server script on sikuli IDE on windows 10, python 2.7, sikuli 1.1.1. If IDE is still running, and I run client code, it throws this error. So I am force closing IDE by ALT+SHIFT C. This stops the error, but then running client code waits forever, for which I have to kill the port.

So, what is it that got it working for you?

@RaiMan (raimund-hocke),
Please look into this.

Rahul Kumar (rahulkumar) said : #18

oh!!! I see.

Rahul Kumar (rahulkumar) said : #19

Now I am getting:

Fault: <Fault 1: "<type 'exceptions.NameError'>:global name 'popup' is not defined">

Rahul Kumar (rahulkumar) said : #20

I got it working! I thought why to start from sikuli IDE. so I directly clicked on server file. That started the server for sure, but did not had library to support the method call. I wonder if I could import the libraries of sikuli somwhere or point it to look at sikuli library