# Class Release: Load/save external variables

Discussion in 'Alibre Script' started by NateLiqGrav, Jul 5, 2019.

1. ### NateLiqGravAlibre Super User

This lets you load variables from an external file and save variables to an external file using a special class called Settings. You can load all existing variables from the file and/or you can load/create them on the fly. The variables are not saved until you use SaveAll().

Basically include requirements and the classes.
There is an example of how this works included at the end of the code.

Give it a try and let me know how it works for you.
Code:
#########################################################################################################################
#########################################################################################################################
###  requirements
#########################################################################################################################
#########################################################################################################################

Win = Windows()
import os # for working with file paths
import xml.etree.ElementTree as xml

#########################################################################################################################
#########################################################################################################################
###  classes
#########################################################################################################################
#########################################################################################################################

class Settings:
def __init__(self, xmlfile):
self._xmlfile = xmlfile
if os.path.isfile(self._xmlfile):
print('Settings file found: ' + str(self._xmlfile))
with open(self._xmlfile, 'r') as content_file:
content = content_file.read()
self._xmlroot = xml.fromstring(content)
else:
print('WARNING: Settings file NOT FOUND: ' + str(self._xmlfile))
if Win.QuestionDialog('WARNING: Settings file NOT FOUND: ' + str(self._xmlfile) + ' \nMake a new File?', 'Error'):
self._xmlroot = xml.Element("root")
else:
sys.exit()

def __getattr__(self, name):
if name == '_xmlfile' or name == '_xmlroot':
return self.__dict__[name]
else:
print('Retrieving '+ str(name))
retval = None
if self._xmlroot.findall('Settings/' + str(name)):
if self._xmlroot.find('Settings/' + str(name)).attrib.get('MyType', None):
MyType = str(self._xmlroot.find('Settings/' + str(name)).attrib['MyType'])
if MyType == 'int':
retval = int(self._xmlroot.find('Settings/' + str(name)).text)
elif MyType == 'float':
retval = float(self._xmlroot.find('Settings/' + str(name)).text)
elif MyType == 'bool':
retval = str(self._xmlroot.find('Settings/' + str(name)).text).lower() in ("yes", "true", "t", "1")
elif MyType == 'NoneType' and str(self._xmlroot.find('Settings/' + str(name)).text) == 'None':
retval = None
else:
retval = str(self._xmlroot.find('Settings/' + str(name)).text)
else:
retval = str(self._xmlroot.find('Settings/' + str(name)).text)
self.__dict__[name] = retval
return retval
else:
print(str(name) + ' does not exist and will be created without value.')
self.__dict__[name] = None
return None

def __setattr__(self, name, val):
if name == '_xmlfile' or name == '_xmlroot':
self.__dict__[name] = val
elif name == 'LoadAll' or name == 'SaveAll':
print('WARNING: CAN NOT USE ' + str(name) + ' as a variable because it is a class method')
else:
print('Updating ' + str(name))
if self._xmlroot.findall('Settings'):
a = self._xmlroot.find('Settings')
else:
a = xml.Element('Settings')
self._xmlroot.append(a)
if self._xmlroot.findall('Settings/' + str(name)):
self._xmlroot.find('Settings/' + str(name)).text = str(val)
self._xmlroot.find('Settings/' + str(name)).attrib['MyType'] = str(type(val).__name__)
else:
b = xml.SubElement(a, str(name), MyType = str(type(val).__name__))
b.text = str(val)
self.__dict__[name] = val

def LoadAll(self):
if self._xmlroot.findall('Settings'):
print('\nStart Loading All Settings:')
a = self._xmlroot.find('Settings')
for b in a:
if b.attrib.get('MyType', None):
MyType = str(b.attrib['MyType'])
if MyType == 'int':
retval = int(b.text)
elif MyType == 'float':
retval = float(b.text)
elif MyType == 'bool':
retval = str(b.text).lower() in ("yes", "true", "t", "1")
elif MyType == 'NoneType' and str(b.text) == 'None':
retval = None
else:
retval = str(b.text)
else:
retval = str(b.text)
self.__dict__[str(b.tag)] = retval
print('Loaded ' + str(type(retval).__name__) +':       ' + str(b.tag) + ' = ' + str(retval))
print('Done Loading All.\n')
return True
else:
print('\nCould not find any previous settings')
return False

def SaveAll(self):
print('Saving All Settings.')
if os.path.isfile(self._xmlfile):
os.remove(self._xmlfile)
with open(self._xmlfile, 'w') as content_file:
content_file.write(xml.tostring(self._xmlroot))

#########################################################################################################################
#########################################################################################################################
###  main program
#########################################################################################################################
#########################################################################################################################
# Your code goes down here. Everything below is just for example usage.

# first you must create a new Settings instance by passing it a file name.
MySettingsFile = r'C:\Users\Gamer\Documents\Alibre Script Library\A_Current Scripts\xml_test2.xml'
vars = Settings(MySettingsFile)

# optionally Load All variables saved in the xml files Settings section at once
worked = vars.LoadAll()
if not worked:
print('Nothing was found in the settings')
# set any default variables here
vars.Boolval = False
vars.makesomething = 125

# or just use variables like normal
if not vars.LibraryFolder:
vars.LibraryFolder = r'C:\Users\Gamer\Documents'

vars.Boolval = False
vars.makesomething = 125

# Make sure to save any changes
vars.SaveAll()


Last edited: Jul 7, 2019
ajayre likes this.
2. ### R-manSenior Member

Ok, here I am and I want to test out your idea. My knowledge and use of python is very basic so please excuse my very basic questions.

1. It looks like my variables would be in an XML file. What does that look like? (Never made an xml file before.)

2. Would all of your "requirements" and "classes" code be included fully in every script that needed access to the variables, or could it be somehow imported or included?

3. Where you say "everything below is just for example usage" I see a few options, but I'm not clear about any. So assume a simple case where all my variables were in the XML file and I made all changes by directly editing that file (nothing on the fly, no reassignments, etc). In that case it looks like I would just need to initialize MySettingsFile and successfully run vars.LoadAll() prior to proceeding with my code. Right??

Thanks in advance for your patience!

3. ### NateLiqGravAlibre Super User

If the XML file is not found at the path it will ask if you want to create it. If you say no it just exits. I'm open to suggestions for improvement.

It can be or you could also save it as a separate script (remove the example at the bottom first) and then import it your scripts. I will make an example of this tomorrow.

Initialize by creating a Settings instance (in this case named vars) with a path to the XML file:
Code:
vars = Settings(r'C:\Path\to\your\file\setting.xml')

The XML data is actually loaded into memory from the file when the class is instanced.

To be frank vars.LoadAll() is visual fluff. It does takes each variable from in the XML data in memory and assigns each their own attribute and prints out what it did.

These can be used in the form:
vars.AttributeName

However after I wrote that I have added dynamic on the fly so everything is automatically created as attributes and you can just use it.
Code:
print(vars.MyDiameter2)
TotalLength = vars.LengthA + vars.LengthB
vars.Height = 101.276
vars.MyString = "test"

If you try to get a variable that didn't exist in the XML it creates it with None for the value.
It will automatically change values and their types when they are assigned.

You can manually edit the XML file or just make a script that assigns all the variables and does a vars.SaveAll() at the end. I would do a SaveAll() at least once to see the structure I'm using for the XML file.

When saving it converts into string first.
During loading it currently only converts back into the following types:
int
float
bool
NoneType
str

Anything else is defaulted into a string.

Like I said I'll make an import example tomorrow.

4. ### R-manSenior Member

I gather from your examples that in my code all reference to stored variables would be in the form "vars.Length". Is that correct?

5. ### NateLiqGravAlibre Super User

Correct.
SettingsClassInstanceName.AttributeName

6. ### NateLiqGravAlibre Super User

It was a little more difficult than I initially thought to get it to import. I'm learning Python as I go and IronPython and AlibreScript have their own twists as well, so there is a little trial and error to this. I also fixed and cleaned up some other things. It's working but I'll need some time to test it and document it better. Sorry for the wait.

7. ### R-manSenior Member

Considering that my scripts don't create or update parameters on the fly, I can get a similar result using what I call 'Hack1".

File 'parameters.py' contains the assignment statements:
Code:
length = 44
width  = 21
height = 12
My script file imports parameters.py:
Code:
# make *SURE* vars folder is in sys.path (but only one time)
varspath = "C:\\Users\\dh\Documents\\RotoAlibre\\Alibre\\Hack1"
if varspath not in sys.path:
sys.path.insert(0, varspath)

# import the parameters file
import parameters as g  # 'g' can be anything

# is it working?
print g.length, g.width, g.height
This enables me to refer to my parameters in a similar 'prefixed' format. This is not really a 'hack' but it is limited to using parameters like constants.

Ideally I'd like to not require a prefix and have truly global parameters. I see some possibilities but it will take many more wasted hours to know if it's possible.

8. ### NateLiqGravAlibre Super User

If that works for you then use that.

I don't see a way. Plus if you could manage to make all variables global I think it would screw up built in functions and methods of AlibreScript/Python.

9. ### R-manSenior Member

I have 'Hack2' ready. It's back in the thread "How do I access variable file from a script?"