Variable values not carrying over to functions called from separate scripts

Asked by James D Bartlett III

Edit: I've recreated the problem in 3 test scripts, which are much simpler than my whole project. But if we can solve the issue in my test files, I can apply the solution to the actual project. If you started looking through the mountains of code I posted before, I'm sorry to have wasted your time.

My scripts are as follows:

Test.sikuli:
import os,sys
from Test_Lib import *
from Test_Function import *
testvar1 = "TEST1"

print(testvar1)
print(testvar2)

printTest1()
printTest2()

Test_Lib.sikuli:
testvar2 = "TEST2"

Test_Function.sikuli:
def printTest1():
    print(testvar1)
def printTest2():
    print(testvar2)

Here's the problem:

When I execute Test.sikuli, I get the following output:
TEST1
[error] script [ Test ] stopped with error in line 7
[error] NameError ( name 'testvar2' is not defined )

Obviously, the variable testvar1 is getting through. But testvar2 is not defined.

When I had all of these functions and variables stored in a single script, it all worked flawlessly. But now that I've broken out the functions of the script into their own modules, and called them from one another, the variable values don't carry over from Test into the printTest2() function it calls from Test_Function. How can I make it so that when I set a variable value in the one script, that value carries forward into all scripts and functions called henceforth? I played around a bit with using the "global" keyword, but it doesn't seem to help. Please be gentle with me. I'm still a Python beginner. Where have I gone wrong?

Additional info:
SikuliX Version: 1.1.0
Operating System: Windows 10
Java(TM) SE Runtime Environment: build 1.8.0_73-b02

Question information

Language:
English Edit question
Status:
Answered
For:
SikuliX Edit question
Assignee:
No assignee Edit question
Last query:
Last reply:
Revision history for this message
RaiMan (raimund-hocke) said :
#1

global does not work across modules.

To make things more reliable, you should not use
from xxx import *

but
import xxx

and then use
xxx.someVariable

so it is always clear, in what module the requested name should be found.

In your case, you need to import Test_Lib also in Test_Function and you should always use functions, to set and get values, that are defined in a module.

This is how I would do it:

# ... Test.sikuli:
import Test_Lib
import Test_Function

testvar1 = "TEST1"

print(testvar1)
print(Test_Lib.testvar2)

Test_Function.setTestvar1(testvar1) # to make the value available in the module Test_Function
Test_Function.printTest1()
Test_Function.printTest2()

# ... Test_Lib.sikuli:
testvar2 = "TEST2"

Test_Function.sikuli:
import Test_Lib
def setTestvar1(someValue):
    global testvar1 # global within scope of Test_Function
    testvar1 = someValue
def printTest1():
    print(testvar1)
def printTest2():
    print(testvar2)

Revision history for this message
James D Bartlett III (jamesdbartlett) said :
#2

Hm. It would appear that I failed to capture the complexity of my problem with the example code. Let me explain what I'm trying to do, and hopefully that will help more.

I'm using Sikuli to automate the process of posting about a half-dozen different ads for my handyman business to a local website that specializes in that kind of thing. So all of the dynamic variables (things like the ad title, body, location of photos to upload, etc) are stored in the individual script for each ad. For the purposes of this discussion, we'll call it AdX.sikuli. All of the ad scripts end with a call to the static function stored in a separate module (Ad_Function.sikuli), which plugs the dynamic variables for whichever ad script called it into the places where it needs to fill in the forms on the website. All of the parts that don't change per-ad, such as which button or link to click on, are pulled in from another module (Ad_Lib.sikuli).

The problem with doing it the way you suggested is that I can't put the dynamic variable values into Ad_Function or Ad_Lib, because the content for each ad changes all the time. I was trying to set it up so that I can add more scripts at a later date, without ever touching the main code. Eventually, I'll probably have a few dozen ads running at various times, and I'd like to be able to delegate the job of creating new ad content and setting up the task scheduler to an assistant, who I wouldn't necessarily trust to navigate through the code and replace variables without messing anything up. At least with the AdX.sikuli setup, I can basically just tell them, "Clone the ad script, then just replace everything in between the quotation marks," and if they mess up, they've only changed a clone, not the original source code.

Ad_Function can always call the static variables from Ad_Lib without any trouble, because Ad_Lib is static. But since AdX is really Ad1, Ad2, Ad3, etc... I need a way to tell Ad_Function to grab the values of the dynamic variables from whichever script called it. Is there a way to do that dynamically?

Revision history for this message
RaiMan (raimund-hocke) said :
#3

the usual way, when you have such a challenge (if I understand right), is to apply parameters with the called function, that are filled with the dynamic variables at the time they are called.

# main.sikuli (scheduler)
import adsFunctions
adsFunctions.ads1("content1", "content2", "content3")
adsFunctions.ads1("content1a", "content2a", "content3a")
adsFunctions.ads1("content1b", "content2b", "content3b")

# asFunctions.sikuli
def ads1(cont1, cont2, cont3):
    print "content1:", cont1
    print "content2:", cont2
    print "content3:", cont3

hence each job, that has to be done the same way, but with different content, uses the same function, which is called with different parameter values, that reflect the dynamic content.

This is one of the basic software design principles: DRY
... don't repeat yourself.

the scheduler (main) itself then could be designed in a way, that it reads some input, where each line represents a job, to be done:
e.g. a CSV file with these columns FunctionToUse, Parameter1, Parameter2, Parameter3, Parameter4

the file for the above example:
ads1,content1,content2,content3
ads1,content1a,content2a,content3a
ads1,content1a,content2a,content3a

# main
jobs = open("jobs.txt")
for line in jobs.readlines():
   parms = line.strip().split()
   if parms[0] == "ads1":
      adsFunctions.ads1(parms[1], parms[2], parms[3])

so to add a new job, that already has a function available , you simply have to revise your file jobs.text and run main: no need to touch any code, until the workflow changes.

You might even use an Excel sheet as input, that has a line for each job, since SikuliX has the module xlrd bundled, that allows, to read from an Excel sheet.

If you are thinking about assistants, then you should never design the workflow in a way, that they have to fiddle around with code. Instead they should use some tool to fill in the schedule information (what, when, how, ...) and the dynamic content.
The easiest tool is a text file, that is used by your main script, to execute what should be done based on the schedule content.
A more complex solution would be a database with one or more input masks (a basic solution is even possible with Excel this way).
... and you might setup a WordPress website, that could be used this way (provided you use the appropriate plugins and setup the input masks).

Revision history for this message
James D Bartlett III (jamesdbartlett) said :
#4

I appreciate the idea about the Excel sheet. Unfortunately, I need the body text of the ads to contain more complex formatting than what I can get away with in Excel. For right now, I'm using an external text file, and launching an instance of Notepad with the path to the file, then doing a select-all, copying it to the clipboard, closing Notepad, and then pasting it into the body text box in the ad. It's a really lame way to do it, I know, but it works. I'll be improving this code at some point in the future, and maybe I'll find a way to import the necessary text into the clipboard directly, bypassing the Notepad step, but for now, this works.

However, I keep running into this problem where it says "[error] AttributeError ( 'module' object has no attribute 'NAME' )" where NAME is the function or variable that I'm trying to bring in from Test_Function.sikuli. I've done the imports just as you described, but can't seem to get it to pull the necessary functions and variables from the other scripts. Do you think the underscores in the file names are causing Sikuli to miss the other files?

Revision history for this message
James D Bartlett III (jamesdbartlett) said :
#5

OK, I tried removing the underscores from the file names, and that seems to have solved that problem. But now I'm having the issue where any standard Sikuli command, like switchApp(), won't work when part of a function called in as a module. The error message says "[error] NameError ( global name 'switchApp' is not defined )"

Example code that reproduces this error:

 # Test.sikuli:
import os,sys,TestFunction
var1 = "VAR1"
var2 = "VAR2"
TestFunction.runTest(var1, var2, var3)

# TestFunction.sikuli:
from sikuli import *
def runTest(var1, var2, var3):
    from TestLib import *
    switchApp('firefox')
    wait(5)
    print var1
    print var2
    print var3

# TestLib.sikuli
var3 = "VAR3"

Revision history for this message
RaiMan (raimund-hocke) said :
#6
Revision history for this message
RaiMan (raimund-hocke) said :
#7

#comment #4
you can use Python features to read the content of the external file into a variable and the use paste() to get it into the text field.

principally (not tested)

extFile = open(absPathToFile)
content = extFile.read()
extFile.close()
paste(target, content)

Can you help with this problem?

Provide an answer of your own, or ask James D Bartlett III for more information if necessary.

To post a message you must log in.