Merge "Issue #2712 - improved class merging in Python Overrider. Created PythonOverriderInterface." into development
Former-commit-id:517147005f
[formerly 17528714e06a31a2397a9b485d4d83fcc91d1bb0] Former-commit-id:86b8322391
This commit is contained in:
commit
4487c75c40
4 changed files with 133 additions and 25 deletions
|
@ -29,6 +29,9 @@
|
||||||
# Date Ticket# Engineer Description
|
# Date Ticket# Engineer Description
|
||||||
# ------------ ---------- ----------- --------------------------
|
# ------------ ---------- ----------- --------------------------
|
||||||
# 11/11/13 bkowal Initial Creation.
|
# 11/11/13 bkowal Initial Creation.
|
||||||
|
# 01/20/14 2712 bkowal Always add the directory path for
|
||||||
|
# the desired module to the beginning
|
||||||
|
# of the system path.
|
||||||
#
|
#
|
||||||
import os, sys, imp
|
import os, sys, imp
|
||||||
|
|
||||||
|
@ -39,19 +42,16 @@ def loadModule(filename):
|
||||||
@summary: This function takes a filename and find the module,
|
@summary: This function takes a filename and find the module,
|
||||||
loads it and returns that module
|
loads it and returns that module
|
||||||
'''
|
'''
|
||||||
addedToPath = False
|
|
||||||
|
|
||||||
path = os.path.splitext(filename)[0]
|
path = os.path.splitext(filename)[0]
|
||||||
directory = os.path.dirname(filename)
|
directory = os.path.dirname(filename)
|
||||||
# ensure the module containing directory is on the python path.
|
# ensure the module containing directory is on the python path.
|
||||||
if sys.path.count(directory) == 0:
|
sys.path.insert(0, directory)
|
||||||
sys.path.append(directory)
|
try:
|
||||||
addedToPath = True
|
filename = os.path.split(path)[1]
|
||||||
filename = os.path.split(path)[1]
|
fp, pathname, description = imp.find_module(filename)
|
||||||
fp, pathname, description = imp.find_module(filename)
|
module = imp.load_module(filename, fp, pathname, description)
|
||||||
module = imp.load_module(filename, fp, pathname, description)
|
finally:
|
||||||
|
sys.path.pop(0)
|
||||||
if addedToPath:
|
|
||||||
sys.path.remove(directory)
|
|
||||||
|
|
||||||
return module
|
return module
|
|
@ -30,11 +30,14 @@
|
||||||
# Date Ticket# Engineer Description
|
# Date Ticket# Engineer Description
|
||||||
# ------------ ---------- ----------- --------------------------
|
# ------------ ---------- ----------- --------------------------
|
||||||
# 11/12/13 bkowal Initial Creation.
|
# 11/12/13 bkowal Initial Creation.
|
||||||
|
# 01/20/14 2712 bkowal Improve python class merging. Classes
|
||||||
|
# will now truly override each other
|
||||||
|
# instead of extending each other.
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
import os, imp, types
|
import os, imp, types, inspect
|
||||||
import ModuleUtil
|
import ModuleUtil
|
||||||
|
|
||||||
def _internalOverride(files):
|
def _internalOverride(files):
|
||||||
|
@ -111,29 +114,41 @@ def _mergeClasses(source, target, className):
|
||||||
for attr in dir(sourceClass):
|
for attr in dir(sourceClass):
|
||||||
# include private attributes because this is a class?
|
# include private attributes because this is a class?
|
||||||
# methods cannot just be merged into a class, so skip them.
|
# methods cannot just be merged into a class, so skip them.
|
||||||
if isinstance(attr, types.BuiltinFunctionType) \
|
if isinstance(getattr(sourceClass, attr), types.BuiltinFunctionType) \
|
||||||
or isinstance(attr, types.MethodType) \
|
or isinstance(getattr(sourceClass, attr), types.MethodType) \
|
||||||
or attr.startswith('__') or attr.startswith('_'):
|
or attr.startswith('__'):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# do we need to worry about nested classes?
|
# do we need to worry about nested classes?
|
||||||
if isinstance(getattr(sourceClass, attr), types.ClassType) \
|
if isinstance(getattr(sourceClass, attr), types.ClassType) \
|
||||||
or isinstance(getattr(sourceClass, attr), types.TypeType):
|
or isinstance(getattr(sourceClass, attr), types.TypeType):
|
||||||
target = _mergeClasses(source, target, attr)
|
target = _mergeClasses(source, target, attr)
|
||||||
|
|
||||||
attributeName = className + '.' + attr
|
attributeName = className + '.' + attr
|
||||||
target = _mergeAttributes(source, target, attributeName)
|
target = _mergeAttributes(source, target, attributeName)
|
||||||
|
|
||||||
|
# make new class "extend" the original class
|
||||||
|
for attr in dir(targetClass):
|
||||||
|
if attr != '__init__' \
|
||||||
|
and isinstance(getattr(targetClass, attr), types.MethodType) \
|
||||||
|
and not attr in dir(sourceClass):
|
||||||
|
# complete the merge / override of methods for any method that
|
||||||
|
# the new class does not implement
|
||||||
|
|
||||||
|
# retrieve the implementation of the method (this is different from
|
||||||
|
# retrieving the method, itself)
|
||||||
|
exec('method = target.' + className + '.' + attr + '.im_func')
|
||||||
|
# copy the method implementation to the other class and give it
|
||||||
|
# the same name as the original
|
||||||
|
classMethodDirective = _buildReplaceClassMethodDirective(className, attr)
|
||||||
|
exec(classMethodDirective)
|
||||||
|
|
||||||
# complete the merge / override of methods.
|
|
||||||
exec(_buildMergeDirective(className, legacyMode))
|
|
||||||
return _mergeAttributes(source, target, className)
|
return _mergeAttributes(source, target, className)
|
||||||
|
|
||||||
def _buildMergeDirective(className, legacyMode):
|
def _buildReplaceClassMethodDirective(className, methodName):
|
||||||
if (legacyMode):
|
replaceDirective = 'setattr(source.' + className + ', "' + methodName + '", method)'
|
||||||
return 'source.' + className + '.__bases__ = (target.' + className + ',)'
|
|
||||||
else:
|
return replaceDirective
|
||||||
return 'source.' + className + ' = type("' + className + \
|
|
||||||
'", (target.' + className + ',), dict(source.' + className + '.__dict__))'
|
|
||||||
|
|
||||||
def _compareClasses(clazz1, clazz2):
|
def _compareClasses(clazz1, clazz2):
|
||||||
clazz1Attr = dir(clazz1)
|
clazz1Attr = dir(clazz1)
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
# #
|
||||||
|
# This software was developed and / or modified by Raytheon Company,
|
||||||
|
# pursuant to Contract DG133W-05-CQ-1067 with the US Government.
|
||||||
|
#
|
||||||
|
# U.S. EXPORT CONTROLLED TECHNICAL DATA
|
||||||
|
# This software product contains export-restricted data whose
|
||||||
|
# export/transfer/disclosure is restricted by U.S. law. Dissemination
|
||||||
|
# to non-U.S. persons whether in the United States or abroad requires
|
||||||
|
# an export license or other authorization.
|
||||||
|
#
|
||||||
|
# Contractor Name: Raytheon Company
|
||||||
|
# Contractor Address: 6825 Pine Street, Suite 340
|
||||||
|
# Mail Stop B8
|
||||||
|
# Omaha, NE 68106
|
||||||
|
# 402.291.0100
|
||||||
|
#
|
||||||
|
# See the AWIPS II Master Rights File ("Master Rights File.pdf") for
|
||||||
|
# further licensing information.
|
||||||
|
# #
|
||||||
|
|
||||||
|
#
|
||||||
|
# A proxy to Python Overrider that utilizes the capabilities of the
|
||||||
|
# Python RollbackMasterInterface. The objective of this class is
|
||||||
|
# to prevent the MasterInterface from completing imports because the
|
||||||
|
# MasterInterface will overwrite modules instead of merging them.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# SOFTWARE HISTORY
|
||||||
|
#
|
||||||
|
# Date Ticket# Engineer Description
|
||||||
|
# ------------ ---------- ----------- --------------------------
|
||||||
|
# 01/14/2014 #2766 bkowal Initial Creation.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
import os, sys, string, traceback
|
||||||
|
import RollbackMasterInterface
|
||||||
|
import PythonOverrider
|
||||||
|
|
||||||
|
class PythonOverriderInterface(RollbackMasterInterface.RollbackMasterInterface):
|
||||||
|
def __init__(self, scriptPath, localizationPath=None):
|
||||||
|
super(PythonOverriderInterface, self).__init__(scriptPath)
|
||||||
|
self._localizationPath = localizationPath
|
||||||
|
|
||||||
|
def importModules(self):
|
||||||
|
modulesToImport = []
|
||||||
|
|
||||||
|
for s in self._scriptPath.split(os.path.pathsep):
|
||||||
|
if os.path.exists(s):
|
||||||
|
scriptfiles = os.listdir(s)
|
||||||
|
|
||||||
|
for filename in scriptfiles:
|
||||||
|
split = string.split(filename, ".")
|
||||||
|
if len(split) == 2 and len(split[0]) > 0 and split[1] == "py" and not filename.endswith("Interface.py"):
|
||||||
|
if not split[0] in modulesToImport:
|
||||||
|
modulesToImport.append(split[0])
|
||||||
|
|
||||||
|
for moduleName in modulesToImport:
|
||||||
|
self._importModule(moduleName)
|
||||||
|
|
||||||
|
def addModule(self, moduleName):
|
||||||
|
if not moduleName in self.scripts:
|
||||||
|
self.scripts.append(moduleName)
|
||||||
|
self.reloadModules()
|
||||||
|
|
||||||
|
def reloadModules(self):
|
||||||
|
for script in self.scripts:
|
||||||
|
# first remove all references to the existing module
|
||||||
|
if sys.modules.has_key(script):
|
||||||
|
self.clearModuleAttributes(script)
|
||||||
|
sys.modules.pop(script)
|
||||||
|
|
||||||
|
# now use PythonOverrider to re-import the module
|
||||||
|
self._importModule(script)
|
||||||
|
|
||||||
|
def _importModule(self, moduleName):
|
||||||
|
scriptName = moduleName + '.py'
|
||||||
|
if self._localizationPath:
|
||||||
|
scriptName = os.path.join(self._localizationPath, scriptName)
|
||||||
|
try:
|
||||||
|
importedModule = PythonOverrider.importModule(scriptName)
|
||||||
|
except Exception, e:
|
||||||
|
msg = moduleName + "\n" + traceback.format_exc()
|
||||||
|
self.addImportError(msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not moduleName in self.scripts:
|
||||||
|
self.scripts.append(moduleName)
|
|
@ -34,7 +34,8 @@
|
||||||
# 10/20/08 njensen Initial Creation.
|
# 10/20/08 njensen Initial Creation.
|
||||||
# 01/17/13 1486 dgilling Make a new-style class.
|
# 01/17/13 1486 dgilling Make a new-style class.
|
||||||
# 09/23/13 16614 njensen Fixed reload method
|
# 09/23/13 16614 njensen Fixed reload method
|
||||||
#
|
# 01/20/14 2712 bkowal It is now possible to add errors
|
||||||
|
# from a subclass.
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
|
@ -141,6 +142,9 @@ class MasterInterface(object):
|
||||||
self.__importErrors = []
|
self.__importErrors = []
|
||||||
return returnList
|
return returnList
|
||||||
|
|
||||||
|
def addImportError(self, error):
|
||||||
|
self.__importErrors.append(error)
|
||||||
|
|
||||||
def reloadModule(self, moduleName):
|
def reloadModule(self, moduleName):
|
||||||
if sys.modules.has_key(moduleName):
|
if sys.modules.has_key(moduleName):
|
||||||
# From the python documentation:
|
# From the python documentation:
|
||||||
|
|
Loading…
Add table
Reference in a new issue