[request] support Python import for reusable scripts

Bug #627986 reported by RaiMan
34
This bug affects 3 people
Affects Status Importance Assigned to Milestone
SikuliX
Fix Released
Undecided
Unassigned

Bug Description

This report is related to different questions and requests that ask for a feature to import reusable code in a manner like using the Python import feature. a summary is reported in FAQ 1114 (https://answers.launchpad.net/sikuli/+faq/1114), but it is not possible to comment there.

MY REQUEST is, to implement the following somehow into Sikuli, so that it can be controlled by setting a preference "library path" or giving an arg like "lib=a-path" to a .skl. when the script is running, this path should be availble (e.g. Env.getLibraryPath()) and sys.path should be set accordingly. So a simple "from myLib import *" should work as expected.

Following is based on Sikuli 10.2 and works on both Mac 10.6 (my primary system) and Win7. Just using the Sikuli IDE, no handling of any .py files.

(tip: no need for import sys or import time, since this is already done during the initialization of Sikuli, just use it)

--- create a script that contains some functions, that you want to reuse and save it somewhere (e.g. myLib.sikuli)
example content:
def myFunction()
    print "Hello from myLib"

--- to use your reusable script try in the IDE
sys.path.append("absolute-path-to-myLib.sikuli")
# Mac e.g. "/Users/myName/myLib.sikuli"
# Windows e.g. "c:\\myStuff\\myLib.sikuli"
from myLib import * # your functions are available in the local namespace now
myFunction() # Hello ... shows up in the message area

If you want to use Sikuli functions in your reusable code, your myLib needs an additional import:
from sikuli.Sikuli import * # should be the first line in myLib.sikuli

--- If you are using images in your myLib script, these will be stored in myLib.sikuli. But at runtime they will not be available, since the path of your main script is where Sikuli looks for the image files.

Solution:
the image references have to be qualified to point to myLib.Sikuli:
example content of myLib.sikuli:
from sikuli.Sikuli import *
imagePath = sys.path[-1] + "/" # (Windows: "\\")
# obviously the path to myLib.Sikuli ;-)
def myFunction()
    return find(imagePath + "1283333068882.png")

1283333068882.png would be replaced by its thumbnail, when editing myLib.sikuli in the IDE.

--- If you want to import more than on .sikuli, you have to append each one to sys.path and give different imagePath's.
e.g.
sys.path.append("absolute-path-to-myLib1.sikuli")
from myLib1 import * # your functions are available in the local namespace now
sys.path.append("absolute-path-to-myLib2.sikuli")
from myLib2 import * # your functions are available in the local namespace now

and in myLib1:
myLib1Path = sys.path[-1] + "/" # (Windows: "\\")
and in myLib2:
myLib2Path = sys.path[-1] + "/" # (Windows: "\\")

Tip: setup a naming convention for your reusables.

+++ CAUTION +++ when testing in the IDE: Due to the fact, that sys.path is bound to the life of the IDE, it is not reset when running the same script again (your path is appended again). Same goes for the import: no reload on rerun. So if you make changes to your myLib, you have to stop and restart the IDE.
If you want to avoid the sys.path problem, code instead:
myLib = "absolute-path-to-myLib.sikuli"
if not myLib in sys.path: sys.path.append(myLib)

All this is no problem, when running your script as .skl, since in this case everything starts from scratch at every rerun.

--- Additional info, if you want to deal with .py files directly:
sys.path contains a path, that points to a directory of the sikuli installation, that does not exist (don't know why, where and how, but I guess it is some standard that is set when initializing a PythonInterpreter based on the actual Java classpath):
Mac: /Applications/Sikuli-IDE.app/Contents/Resources/Java/Lib
Windows: C:\\Program Files\\Sikuli\\Lib
if you have a standard installation of Sikuli.
If you now move .py-files (e.g. myLib.py from our myLib.sikuli above) into this directory (after creating it ;-), you can use "from myLib import *" without any manipulation of sys.path.
This can be used, when you want to add some basic additions to Sikuli or helpers (e.g. a function, that supports the handling of sys.path) that you use in many of your scripts.

--- next step
test this with python modules, that are not available in the Jython environment (PIL, pyTesser, ...)

Revision history for this message
RaiMan (raimund-hocke) wrote :

It is still not possible to use any python libraries, that rely directly on c-type dynamic libraries(.dll, .pyd, .so, ...) (e.g. PIL will not run with the image core functions since these are written in C and so will not any other library that relies on PIL like pyTesser).

These C modules have either to be ported to Java or bridged via JNI, which is not the case for PIL yet.

Revision history for this message
Tsung-Hsiang Chang (vgod) wrote :

Thanks for reporting this. I will see if I can come up with a solution for this problem. :)

Revision history for this message
Tsung-Hsiang Chang (vgod) wrote :

In Sikuli X, .sikuli source will be able to be imported as a python module using the standard import statement of Python.

Changed in sikuli:
milestone: none → x1.0-rc1
status: New → Fix Committed
Revision history for this message
Timothy Alexander (dragonfyre13) wrote :

Do you mean Python, or Jython?

Also, does this mean that I will be able to hook sikuli with other python/jython code? For example, I have a completely seperate test framework I would love to be able to do some serious GUI automation with. Will I be able to import sikuli's API, and use the library calls to achieve the same effect as if I wrote the code via sikuli IDE?

Revision history for this message
Tsung-Hsiang Chang (vgod) wrote :

I meant Jython.

Besides, on Jython/Java platforms, you already can call sikuli's API in your test framework with Sikuli 0.10.2 now. I'm sure Raiman has a detailed post related to this topic. Maybe he can give you a few references as your starting points.

Revision history for this message
RaiMan (raimund-hocke) wrote :

@Timothy

--- 1.
if you want to use existing Python code and implement calls to sikuli features in your script, your python scripts have to be run with the Jython interpreter. You may install it as standalone or use the plugged-in jython, available e.g. in NetBeans or Eclipse.

--- 2.
If you currently are running your python scripts with the Python interpreter, you first have to make sure, that your scripts and used modules are Jython compliant (especially C-based Python modules, may be not available in a Jython version). The unit testing framework is fully supported.

--- 3.
To run scripts: To prepare your Jython environment, put sikuli-script.jar in the Java class-path and sikuli-script.jar/Lib in Python's sys.path. In your scripts you need a "from sikuli.Sikuli import *" at the beginning of your scripts. Additionally you have to assure, that the native libraries of openCV, that come with Sikuli are found on your system (usually it should work, if you just reference the things at their places in the Sikuli program folder).

--- 4.
To develop scripts: If you are using some IDE like NetBeans or Eclipse in the moment, you might want to integrate access to Sikuli. To get some impression look at: https://answers.launchpad.net/sikuli/+question/128341

You are welcome to come back with additional questions

Revision history for this message
Bulkan (bulkan) wrote :

Hi all,

Im trying to import a "common" sikuli script using the following Python code.

common = os.path.join(os.path.dirname(getBundlePath()), 'common.sikuli')
if not common in sys.path: sys.path.append(common)
from common import *

The import is successful but my functions in the common module use methods like click, find, wait etc... which is available to the script that is importing the common module but I get the following error when I call a function from it

NameError: global name 'click' is not defined

I understand that the click function is not available to the common module that I've imported, but my question is where are these methods coming from, they are just there implicitly.

Any help appreciated.

Cheers

Revision history for this message
RaiMan (raimund-hocke) wrote :

I guess you missed the
from sikuli.Sikuli import *

in your common.sikuli

Revision history for this message
RaiMan (raimund-hocke) wrote :

@Bulkan: from another post I guess you are using Sikuli X. So you can import a .sikuli as such:

commonPath = os.path.dirname(getBundlePath())
if not common in sys.path: sys.path.append(commonPath)
from common import *

Revision history for this message
RaiMan (raimund-hocke) wrote :

@ Bulkan: I just realized, you do not subscribe this. I will post it to you directly.

the whole answer:

--- I guess you missed the
from sikuli.Sikuli import *

in your common.sikuli

--- from another post I guess you are using Sikuli X. So you can import a .sikuli as such:
commonPath = os.path.dirname(getBundlePath())
if not commonPath in sys.path: sys.path.append(commonPath)
from common import *

Revision history for this message
Bulkan (bulkan) wrote :

@RaiMan Thanks for that, working now. I checked the documentation again and it is actually mentioned there in the prerequisites section that I didn't see.

RaiMan (raimund-hocke)
Changed in sikuli:
status: Fix Committed → Fix Released
To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.