[HowTo] Run one script from another using import

Created by RaiMan on on 2013-10-27
Keywords:

This is a bit tricky.
... but just read to the end of this story ;-)

lets say, we have 2 scripts currently.
script1.sikuli
script2.sikuli

which are both in the same folder.

Then usually these scripts are written so they can be run standalone and each script contains the needed images.

This means they are doing something when run, because they contain executable statements at indent level 0 like
find(some_image)
popup("we are running")

(an executable script must always have at least one statement at indent level 0)

*** Now what is import doing:
lets say we have this:

# content of script1.sikuli
popup("hello, I am script1")

# content of script2.sikuli
popup("hello, I am script2")

If you run both scripts on their own, you will get the popups as expected.

Now lets import script2 in script1 (both should be in same folder, so nothing else has to be done)

# content of script1.sikuli
popup("hello, I am script1")
import script2

imported scripts in Sikuli always need the line
from sikuli import *
in the first line to work correctly (take it as a must for now, you might understand why at the end ;-)

# content of script2.sikuli
from sikuli import *
popup("hello, I am script2")

If we now run the script1 in the IDE, we get both popups as expected.
Now we rerun script1 ... and we get only popup1 ???

What happened?
An import loads the imported script and runs the code at indent level 0 only the first time it is executed.
If it is executed a second time (e.g. in a loop, it is just skipped).

So the normal usage of import is to have "callable" snippets in the imported script (which are called functions in Python)

*** So lets make the popup in script2 repeatable:

# content of script2.sikuli
def myPop(): # this defines a function
    popup("hello, I am script2")

We save both scripts in the IDE.

Now we need to restart the IDE for now (see below, how to make IDE aware of changed sub-script content).

We only get popup1, because a function is not executable code on indent level 0, hence it is not executed on import.
A function must be called, to execute.

lets change script1:
# content of script1.sikuli
popup("hello, I am script1")
import script2
script2.myPop() # call function myPop in script2

If we now run script1, we get both popups a s expected and we get it on every rerun.

If we want to not use the script2. notation
# content of script1.sikuli
popup("hello, I am script1")
from script2 import * # all names in script2 are now known here (hence myPop is known)
myPop() # call function myPop in script2

Using this variant you must be careful with naming to avoid name clashes (last seen definition wins!)

*** But I made changes and cannot see them working ??
When working in SikuliX IDE, you usually have script1 and script2 open and make changes on both scripts.
We learned, that an import is not executed on rerun. But there is a function, that forces a re-import:
reload(script2)

lets change script1:
# content of script1.sikuli
popup("hello, I am script1")
import script2
reload(script2)
script2.myPop() # call function myPop in script2

If we now make changes in script2 and save them, these changes will be reflected at rerun of script1.

This is how it has to be written for the second import variant:
# content of script1.sikuli
popup("hello, I am script1")
import script2
reload(script2)
from script2 import *
myPop() # call function myPop in script2

There is one important thing to be aware:
at the first run the code at indent level 0 in script2 is executed twice (at import and at reload), which does not make any harm, since we do not have any indent level 0 code in script2

*** But lets go back to our initial situation:
We want to combine the 2 original scripts using import and concurrently edit in both scripts in the IDE session.
So lets apply our lessons learned:

# content of script1.sikuli
popup("hello, I am script1")
import script2
reload(script2)

# content of script2.sikuli
from sikuli import *
popup("hello, I am script2")

Now at the very first run after starting the IDE, we get
popup1
popup2
popup2

and on rerun
popup1
popup2

... and we know why.

So we change script1 and purge the reload():
# content of script1.sikuli
popup("hello, I am script1")
import script2

... and we now know, that this can only be run once in the IDE.
If we made any changes to script2, we have to close and restart the IDE.

An alternative is, to run the script1 from command line, which can be done in parallel to the editing of the scripts in the IDE (be sure to always save the changes you made before repeating the commandline run).

So we now also know, how we can rerun the script2 at different places in script1:
At the places we want script2 to run again, we simply put a
reload(script2)

*** And now at the end a hack, how to use the IDE only for concurrent editing and rerun without the seen problems:

# content of script1.sikuli
popup("hello, I am script1")
try:
    if we_are_in_rerun:
        reload(script2)
except:
    we_are_in_rerun = True
    import script2

How does it work?
at the first run the variable we_are_in_rerun is not yet defined (not seen by the interpreter yet), so we get an exception that we catch with except:, set the variable we_are_in_rerun to True (know it is known) and make the initial import.
If we now rerun the script, the variable we_are_in_rerun is known, so the reload is done and the except: branch is skipped.

*** Conclusion:
It is principally possible to use import to run another script from a script, but that is more an exceptional situation, because we did not plan from the beginning to call other scripts from a script.

If we had it planned according to the design patterns, the imported scripts would only consist of function and class definitions and would (normally ;-) not contain any executable code at indent level 0.