ISCE_INSAR/components/iscesys/Component/FactoryInit.py

410 lines
19 KiB
Python
Executable File

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Copyright 2010 California Institute of Technology. ALL RIGHTS RESERVED.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# United States Government Sponsorship acknowledged. This software is subject to
# U.S. export control laws and regulations and has been classified as 'EAR99 NLR'
# (No [Export] License Required except when exporting to an embargoed country,
# end user, or in support of a prohibited end use). By downloading this software,
# the user agrees to comply with all applicable U.S. export laws and regulations.
# The user has the responsibility to obtain export licenses, or other export
# authority as may be required before exporting this software to any 'EAR99'
# embargoed foreign country or citizen of those countries.
#
# Author: Giangi Sacco
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from __future__ import print_function
from inspect import getmodule
from getopt import getopt
import xml.etree.ElementTree as ET
# remember to keep this updated. would be better the have an import of
# components
## JEB: Cannot remove these until we have unittestin for each app.
from isceobj import *
from iscesys import *
from stdproc import *
from mroipac import *
from isceobj.XmlUtil.XmlUtil import XmlUtil
from iscesys.Component.InitFromDictionary import InitFromDictionary
# This class provides a set of methods that allow objects to be initialized
# from command line. The standard way is to create a FactoryInit object and
# invoke the initFactory(arglist) where the arglist is the argument list
# passed at command line which can be obtained directly from sys.argv.
# The first argument could be the name of an xml file. This file contains the
# objects to be initialized and the initializers adopted.
# A possible example of such a file is: \n
#\verbatim
#<component>
# <name>nameOfThisComponent</name>
# <property>
# <name>FirstObjectName</name>
# <factoryname>NameOfTheClassOfTheObject</factoryname>
# <factorymodule>NameOfTheFileWhereTheClassIs</factorymodule>
# <factorylocation>DirectoryWhereTheFileIs</factorylocation>
# <initclass>NameOfTheClassOfTheInitilaizer</initclass>
# <initmodule>NameOfTheFileOfTheInitializer</initmodule>
# <initlocation>LocationWhereTheFileOfTheInitializerIs</initlocation>
# <value>ValuePassedToTheInitializer</value>
# </property>
# <property>
# <name>SecondObjectName</name>
# <factoryname>NameOfTheClassOfTheObject</factoryname>
# <factorymodule>NameOfTheFileWhereTheClassIs</factorymodule>
# <factorylocation>DirectoryWhereTheFileIs</factorylocation>
# <initclass>NameOfTheClassOfTheInitilaizer</initclass>
# <initmodule>NameOfTheFileOfTheInitializer</initmodule>
# <initlocation>LocationWhereTheFileOfTheInitializerIs</initlocation>
# <value>ValuePassedToTheInitializer</value>
# </property>
#</component>
#\endverbatim
# The factory name can be omitted if there is only one class defined in the
# "factorymodule" file. If the "initlocation" is moroisys.Component, then it
# could be omitted as well or set to "default". If the "initclass" is omitted
# then the InitFromDictionary is understood. The "value" indicates the object
# that the initializer needs. For instance if the "initclass" is
# InitFromXmlFile, then "value" is the name of the xml file from where the
# object is going to be initialized. If "initclass" is InitFromDictionary,
# then value will be a python dictionary. All the locations could be specified
# as directories (i.e. names separated by forward slashes) or as python paths
# (i.e. names separated by dots).\n
# The rest of the commad line arguments is a list of keyword/value pair
# starting with --name. All the keywords are the same as the one shown in
# the above example with the modification <keyword> ==> --keyword, so
# <name> ==> --name, <factoryname> ==> --factoryname and so on.
# After each keyword a value must be specified.\n
# The arguments following the xml initialization file, will overwrite the
# values of the objects already specified in such a file.
# Note: from command line put in quotes the value following the keyword
# --value. For instace for a dictionary write
#\verbatim
# --value "{'VARIABLE1':value1,'VARIABLE2':value2}"
# or
# --value '{"VARIABLE1":value1,"VARIABLE2":value2}'
#\endverbatim
#or for a filename
#\verbatim
# --value "filename" or --value 'filename'
#\endverbatim
# Note also that if a quantity inside the dictionary is a string, then use
# different quotes from the ones enclosing the dictionary. Any other
# combination seems to fail. It has to do with the interpretation of the
# shell of single and double quotes.
class FactoryInit(object):
## More Better format for initFactory
def init_factory(self, *argv):
return self.initFactory(argv)
## Invoke this method by passing the argument list that can be obtained
## from the sys module by invoking sys.argv. The object specified in
## the initlialization xml file and in the following argumnt list will
## be initialized.
## @param argv command line argument list.
def initFactory(self, argv):
if isinstance(argv, basestring):
argv = [argv]
if not argv:
raise ValueError('Error. The argument list is empty')
argList = []
# separate the arg for each component. there is always the name
# followed by the other info
argComp = []
for arg in argv:
if arg == '--name':
# append previous. the first time could be the xml file with
# default that are superseeded by the command line list
argList.append(argComp)
argComp = ['--name']
else:
argComp.append(arg)
#add lat one
argList.append(argComp)
for arg in argList:
if arg:
if arg[0] == '--name':# is a component
# for the command line it will make it easier to change
# parameters using a dictionary
self.defaultInitModule = 'InitFromDictionary'
#all long args i.e. preceeded by --
optlist, args = getopt(arg, '', self.listOfArgs)
# put the result in a dictionary format in which the --
# is removed from the key
tmpDict = {}
name = ''
for pair in optlist:
if pair[0] == '--name': #name is always the first one
name = pair[1]
else:
tmpDict[pair[0][2:]] = pair[1] #remove the -- from pair[0]
self.optDict[name] = tmpDict
if args:# must be empty
raise ValueError('Error. Not expecting single argument')
else: #first part with file info
self.fileInit = arg[0]
#use initFromXmlFile as default
self.defaultInitModule = 'InitFromXmlFile'
self.initComponentFromFile()
# loop though the list of components that need to be updated after
# being initialized from file. otherwise create a new one
for key, comp in self.optDict.items():
#the component already exists -> update it
if key in self.dictionaryOfComponents:
instance = self.dictionaryOfComponents[key]
self.factoryInitComponent(key, comp, instance)
else:
self.factoryInitComponent(key, comp)
def initComponentFromFile(self):
objXmlUtil = XmlUtil()
retDict = objXmlUtil.createDictionary(
objXmlUtil.readFile(self.fileInit)
)
# each component "key" has a dictionary where the keys are the
# listOfArgs values (not necessarily all)
for key, comp in retDict.items():
self.factoryInitComponent(key, comp)
return
# if the string contained in obj is an actual object, when is exec there is
# no problem. if it was supposed to be a string, the name will not be
# defined aand an exception is thrown. put in a function to reduce chance
# that the name is actually defined (smaller scope)
def isStr(self, obj):
""" A function always called with str() arguemnt-- so
what happens- what do I do?"""
retVal = False
try:
exec('a = ' + obj)
except:
retVal = True
# raw_input("<"+str(retVal)+"|"+obj+">")
return retVal
def factoryInitComponent(self,name,comp, *args, **kwargs):
#Python 3 will make this unnecessary with its new function syntax to
#indicate end of positional arguments so that keyword arguments
# can not suck up extra positional arguments
# (see http://www.python.org/dev/peps/pep-3102/)
# for now it is necessary that all named arguments appear in kwargs,
# including the known instanceObj.
if kwargs.has_key('instanceObj'):
instanceObj = kwargs.pop('instanceObj')
else:
instanceObj = None
initDictionary = {} #dictionary with the initializers
instanceInit = None
value = None
# admit the possibility of not ititializing at this point. if value
# does not exist that just instanciate the object with the factory
# name and do not initialize
if 'value' in comp:
value = comp['value']
initLocation = ''
if 'initlocation' in comp:
if comp['initlocation'] == 'default':
initLocation = self.defaultInitLocation
else:
initLocation = comp['initlocation']
else:
initLocation = self.defaultInitLocation
initLocation = initLocation.replace('/', '.')
initModule = ''
if 'initmodule' in comp:
initModule = comp['initmodule']
else:
initModule = self.defaultInitModule
if initModule.endswith('.py'):
initModule = initModule.replace('.py','')
try:
command = 'from ' + initLocation + ' import ' + initModule
exec(command)
except ImportError:
print('Error. Cannot import the module',
initModule,'from',initLocation)
raise ImportError
initClass = None
if 'initclass' in comp:
initClass = comp['initclass']
instance = None
if self.isStr(str(value)):
exec('instance = ' + initModule + '.' + initClass + '(value)')
else:
exec('instance = ' + initModule + '.' + initClass + '(' + str(value) + ')')
instanceInit = instance
else:
exec('listMembers = dir(' + initModule + ')')
instance = None
#the following finds the initilizers
for member in listMembers:
#given only the file where the class initializer is
# defined,get all the members in that file, then
try:# try to instantiate the object from that members and, if it exists, see if that object was defined in that file i.e. initModule
if self.isStr(str(value)):
exec('instance = ' + initModule + '.' + member + '(value)')
else:
exec('instance = ' + initModule + '.' + member + '(' + str(value) + ')')
modName = getmodule(instance).__name__
modNameList = modName.split('.')#just want the last part
modName = modNameList.pop()
if modName == initModule:# found right object. create instance
instanceInit = instance
break
except Exception:# the instantiation failed
continue
if instanceObj:
instanceObj.initComponent(instanceInit)
else:
#do the same thing for the object that needs to be instantiated
factoryLocation = None
factoryModule = None
if 'factorylocation' in comp:#if present use it otherwise allow to specify like package1.package2.....packageN.factoryModule
#and extract the necessary information from the factoryModule
factoryLocation = comp['factorylocation']
factoryLocation = factoryLocation.replace("/",".")
try:
factoryModule = comp['factorymodule']
except KeyError:
print('The \'factorymodule\' keyword is not present for the component',name)
raise KeyError
if factoryModule.endswith('.py'):
factoryModule = factoryModule.replace('.py','')
try:
command = 'from ' + factoryLocation + ' import ' + factoryModule
exec(command)
except ImportError:
print('Error. Cannot import the module',factoryModule,'from',factoryLocation)
raise ImportError
else:
try:
factoryModule = comp['factorymodule']
except KeyError:
#print('The \'factorymodule\' keyword is not present for the component',name)
#raise KeyError
factoryModule = None
if not factoryModule == None:
if factoryModule.endswith('.py'):
factoryModule = factoryModule.replace('.py','')
factoryModule = factoryModule.replace("/",".")
splitFactoryModule = factoryModule.rpartition(".") #split from last "." in a 3-tuple containing first part, "." and last second part
factoryLocation = splitFactoryModule[0]
factoryModule = splitFactoryModule[2]
try:
command = 'from ' + factoryLocation + ' import ' + factoryModule
exec(command)
except ImportError:
#if also acquiring the factoryLocation from the factoryModule didn't work
# try to see if the factory name is sufficient
factoryModule = None
pass
#print('Error. Cannot import the module',factoryModule,'from',factoryLocation)
#raise ImportError
factoryName = None
if 'factoryname' in comp:
factoryName = comp['factoryname']
#instance = None
if factoryModule == None:# than assume that factory name is actually a factory method that does the import and returns the right object
exec('instanceObj = ' + factoryName + '(*args,**kwargs)')
else:
exec('instanceObj = ' + factoryModule + '.' + factoryName + '(*args,**kwargs)')
instanceObj.initComponent(instanceInit)
self.dictionaryOfComponents[name] = instanceObj
else:
exec('listMembers = dir(' + factoryModule + ')')
#instance = None
#the following finds the initilizers
for member in listMembers:
#given only the file where the class initializer is defined,get all the members in that file, then
try:# try to instantiate the object from that members and, if it exists, see if that object was defined in that file i.e. factoryModule
exec('instanceObj = ' + factoryModule + '.' + member + '()')
modName = getmodule(instanceObj).__name__
modNameList = modName.split('.')#just want the last part
modName = modNameList.pop()
if modName == factoryModule:# found right object. crate instance
self.dictionaryOfComponents[name] = instanceObj
instanceObj.initComponent(instanceInit)
break
except Exception:# the instantiation failed
continue
else:# if there is no value keyword that assume that the object doen not need to be init, at least at this time. moreover here we assume that factoryName is actually a factory method
try:
factoryName = comp['factoryname']
except KeyError:
print('The \'factoryname\' keyword is not present for the component',name)
raise KeyError
exec('instanceObj = ' + factoryName + '(*args,**kwargs)')
self.dictionaryOfComponents[name] = instanceObj
##
#Set a different default "initlocation". The default one is iscesys.Component
def setDefaultInitLocation(self,default):
self.defaultInitLocation = default
##
# Get an instance of the object "factoryname". The name of the instance is the one used in the initialization xml file (<name>ObjectName</name>) and/or in the command line --name.
#@param name name of the particular object.
def getComponent(self,name):
try:
return self.dictionaryOfComponents[name]
except KeyError:
print('The requested component',name,'is not present')
raise KeyError
pass
## debug counter to see if it is being used
_count = 0
## Default init location
defaultInitLocation = 'iscesys.Component'
## Default initializer
defaultInitModule = 'InitFromDictionary'
## list of args
listOfArgs = ['name=','value=', 'factoryname=', 'factorymodule=',
'factorylocation=','initclass=','initlocation=',
'initmodule=']
def __init__(self):
self.optDict = {}
self.fileInit = ''
self.dictionaryOfComponents = {}
self._count + 1
return None