Merge "Issue #2712 - improved class merging in Python Overrider. Created PythonOverriderInterface." into development

Former-commit-id: 3191110606 [formerly 4487c75c40] [formerly 86b8322391] [formerly 3191110606 [formerly 4487c75c40] [formerly 86b8322391] [formerly 517147005f [formerly 86b8322391 [formerly 17528714e06a31a2397a9b485d4d83fcc91d1bb0]]]]
Former-commit-id: 517147005f
Former-commit-id: 3ec51fd2f0 [formerly 66ebd12fbb] [formerly 28e72bac9992bdc9b99c1baf770aa7fb87b8125b [formerly 9d848f27e1]]
Former-commit-id: f71b31803d32f72ca198e6e5a1475d6b1dd70668 [formerly 7f407b7efd]
Former-commit-id: 09a7251956
This commit is contained in:
Nate Jensen 2014-01-20 16:09:54 -06:00 committed by Gerrit Code Review
commit 70b03cb69d
4 changed files with 133 additions and 25 deletions

View file

@ -29,6 +29,9 @@
# Date Ticket# Engineer Description
# ------------ ---------- ----------- --------------------------
# 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
@ -39,19 +42,16 @@ def loadModule(filename):
@summary: This function takes a filename and find the module,
loads it and returns that module
'''
addedToPath = False
path = os.path.splitext(filename)[0]
directory = os.path.dirname(filename)
# ensure the module containing directory is on the python path.
if sys.path.count(directory) == 0:
sys.path.append(directory)
addedToPath = True
filename = os.path.split(path)[1]
fp, pathname, description = imp.find_module(filename)
module = imp.load_module(filename, fp, pathname, description)
if addedToPath:
sys.path.remove(directory)
sys.path.insert(0, directory)
try:
filename = os.path.split(path)[1]
fp, pathname, description = imp.find_module(filename)
module = imp.load_module(filename, fp, pathname, description)
finally:
sys.path.pop(0)
return module

View file

@ -30,11 +30,14 @@
# Date Ticket# Engineer Description
# ------------ ---------- ----------- --------------------------
# 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
def _internalOverride(files):
@ -111,29 +114,41 @@ def _mergeClasses(source, target, className):
for attr in dir(sourceClass):
# include private attributes because this is a class?
# methods cannot just be merged into a class, so skip them.
if isinstance(attr, types.BuiltinFunctionType) \
or isinstance(attr, types.MethodType) \
or attr.startswith('__') or attr.startswith('_'):
continue
if isinstance(getattr(sourceClass, attr), types.BuiltinFunctionType) \
or isinstance(getattr(sourceClass, attr), types.MethodType) \
or attr.startswith('__'):
continue
# do we need to worry about nested classes?
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)
attributeName = className + '.' + attr
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)
def _buildMergeDirective(className, legacyMode):
if (legacyMode):
return 'source.' + className + '.__bases__ = (target.' + className + ',)'
else:
return 'source.' + className + ' = type("' + className + \
'", (target.' + className + ',), dict(source.' + className + '.__dict__))'
def _buildReplaceClassMethodDirective(className, methodName):
replaceDirective = 'setattr(source.' + className + ', "' + methodName + '", method)'
return replaceDirective
def _compareClasses(clazz1, clazz2):
clazz1Attr = dir(clazz1)

View file

@ -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)

View file

@ -34,7 +34,8 @@
# 10/20/08 njensen Initial Creation.
# 01/17/13 1486 dgilling Make a new-style class.
# 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 = []
return returnList
def addImportError(self, error):
self.__importErrors.append(error)
def reloadModule(self, moduleName):
if sys.modules.has_key(moduleName):
# From the python documentation: