Merge "Issue #2712 - improved class merging in Python Overrider. Created PythonOverriderInterface." into development
Former-commit-id:3191110606
[formerly4487c75c40
] [formerly86b8322391
] [formerly517147005f
[formerly86b8322391
[formerly 17528714e06a31a2397a9b485d4d83fcc91d1bb0]]] Former-commit-id:517147005f
Former-commit-id: 28e72bac9992bdc9b99c1baf770aa7fb87b8125b [formerly9d848f27e1
] Former-commit-id:66ebd12fbb
This commit is contained in:
commit
3ec51fd2f0
4 changed files with 133 additions and 25 deletions
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
# 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:
|
||||
|
|
Loading…
Add table
Reference in a new issue