Issue #2540 - finish implementing the python override capability. The pure-python solution will be checked in later.

Amend: addressed review comments
Amend: update DR number

Change-Id: I5ae0c1140e0c90be002e3e3711fc4311ce1a05f0

Former-commit-id: 315ca002de4045f0d7ae6b298012365a052a7430
This commit is contained in:
Bryan Kowal 2013-11-06 19:28:33 -06:00
parent 66f99f3974
commit 75e6914df1
3 changed files with 138 additions and 50 deletions

View file

@ -30,12 +30,16 @@
# Date Ticket# Engineer Description
# ------------ ---------- ----------- --------------------------
# 03/12/13 mnash Initial Creation.
# 11/06/13 2086 bkowal Add the containing directory to the
# PYTHONPATH long enough to import
# the module.
#
#
#
import os
import imp
import sys
from com.raytheon.uf.common.localization import LocalizationContext_LocalizationType as JavaLocalizationType, LocalizationContext_LocalizationLevel as JavaLocalizationLevel
@ -80,7 +84,19 @@ 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)
return imp.load_module(filename, fp, pathname, description)
module = imp.load_module(filename, fp, pathname, description)
if addedToPath:
sys.path.remove(directory)
return module

View file

@ -28,6 +28,7 @@
# Date Ticket# Engineer Description
# ------------ ---------- ----------- --------------------------
# 03/18/13 mnash Initial Creation.
# 11/08/13 2086 bkowal Declare lists using [] instead of list()
#
#
#
@ -110,7 +111,7 @@ class PathManager(IPathManager.IPathManager):
else :
jfiles = self.jpathManager.listStaticFiles(name, extArr, recursive, filesOnly)
if jfiles is not None :
files = list()
files = []
for file in jfiles :
files.append(LocalizationFile(file))
return files
@ -121,7 +122,7 @@ class PathManager(IPathManager.IPathManager):
@summary: This method returns the list of available levels.
'''
jLevels = self.jpathManager.getAvailableLevels()
levels = list()
levels = []
for level in jLevels :
levels.append(level.name())
return levels

View file

@ -28,17 +28,19 @@
# Date Ticket# Engineer Description
# ------------ ---------- ----------- --------------------------
# 03/12/13 mnash Initial Creation.
# 11/04/13 2086 bkowal Updated to merge classes - both legacy and non-legacy.
# Minimum to Maximum level of retrieval can now be specified.
#
#
#
import os
import imp
import inspect
import types
from PathManager import PathManager
import LocalizationUtil
def override(name, loctype):
def importModule(name, loctype='COMMON_STATIC', level=None):
"""
Takes a name (filename and localization path) and the localization type and finds the
file and overrides it, and returns the module
@ -46,17 +48,34 @@ def override(name, loctype):
Args:
name : the name and path of the file in localization
loctype : a string representation of the localization type
level : a string representation of the localization level (BASE, SITE, etc.)
Returns:
a module that has all the correct methods after being overridden
"""
pathManager = PathManager()
tieredFiles = pathManager.getTieredLocalizationFile(loctype, name)
levels = pathManager.getAvailableLevels()
lfiles = list()
for level in levels :
if tieredFiles.has_key(level) :
lfiles.append(tieredFiles[level].getPath())
availableLevels = pathManager.getAvailableLevels()
if level == None:
levels = availableLevels
else:
# ensure that the specified level is actually a legitimate level
if level not in availableLevels:
raise LookupError('An invalid level has been specified!')
levels = []
try:
levelIdx = availableLevels.index(level)
levels = availableLevels[:levelIdx + 1]
except ValueError:
# ignore; the exception should never be thrown, we verify that the specified level
# is valid in the previous if statement
pass
lfiles = []
for _level in levels :
if _level in tieredFiles:
lfiles.append(tieredFiles[_level].getPath())
themodule = _internalOverride(lfiles)
return themodule
@ -75,44 +94,96 @@ def _internalOverride(files):
for module in files :
# load each module, temporarily
tmpmodule = LocalizationUtil.loadModule(module)
the_module = _combineMembers(tmpmodule, themodule)
themodule = _combineMembers(tmpmodule, themodule)
return themodule
def _combineMembers(tocombine, finalmodule):
"""
Loops over the necessary parts of each module and decides how to combine them
Args:
tocombine : the module to combine in
finalmodule : the module that is being combined into
Returns:
a new python module that was created above
"""
# get the functions
members = inspect.getmembers(tocombine, inspect.isfunction)
for member in members :
finalmodule.__setattr__(member[0], member[1])
# get the classes
classes = inspect.getmembers(tocombine, inspect.isclass)
for clazz in classes:
finalmodule.__setattr__(clazz[0], clazz[1])
def _combineMembers(tocombine, combinationresult):
for attr in dir(tocombine):
if attr.startswith('__') == False or attr.startswith('_') == False:
if hasattr(finalmodule, attr):
if isinstance(attr, dict):
# simply update dicts with the new keys
getattr(finalmodule, attr).update(getattr(tocombine, attr))
if isinstance(attr, list):
listattr = getattr(tocombine, attr)
for i in listattr:
# override each element in the list if it exists
getattr(finalmodule, attr)[i] = listattr[i]
else :
finalmodule.__setattr__(attr, getattr(tocombine, attr))
else :
if inspect.ismethod(attr) == False and inspect.isbuiltin(attr) == False and inspect.isclass(attr) == False:
finalmodule.__setattr__(attr, getattr(tocombine, attr))
return finalmodule
if attr.startswith('__') or attr.startswith('_') or isType(attr, types.BuiltinFunctionType):
# skip
continue
# is the element a class?
if isType(getattr(tocombine, attr), types.ClassType):
combinationresult = _mergeClasses(tocombine, combinationresult, attr)
else:
# absolute override
combinationresult = _mergeAttributes(tocombine, combinationresult, attr)
return combinationresult
def _mergeClasses(source, target, className):
sourceClass = getattr(source, className)
targetClass = getattr(target, className, None)
if (targetClass == None):
return _mergeAttributes(source, target, className)
legacyMode = (hasattr(sourceClass, '__class__') == False)
# verify that both classes are either legacy for current style.
if ((hasattr(targetClass, '__class__') == False) != legacyMode):
raise Exception("A legacy python class cannot be merged with a non-legacy python class!")
# ensure that the classes are not exactly the same (breaks the legacy merge).
if compareClasses(sourceClass, targetClass):
# nothing to merge
return target
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 isType(attr, types.BuiltinFunctionType) or isType(attr, types.MethodType) or \
attr.startswith('__') or attr.startswith('_'):
continue
# do we need to worry about nested classes?
if isType(getattr(sourceClass, attr), types.ClassType):
target = _mergeClasses(source, target, attr)
attributeName = className + '.' + attr
target = _mergeAttributes(source, target, attributeName)
# 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 isType(object, type):
return type(object) == type
def compareClasses(clazz1, clazz2):
clazz1Attr = dir(clazz1)
clazz2Attr = dir(clazz2)
if (len(clazz1Attr) != len(clazz2Attr)):
return False
i = 0
while i < len(clazz1Attr):
# compare the names
if (clazz1Attr[i] != clazz2Attr[i]):
return False
# compare the attributes directly
attr1 = getattr(clazz1, clazz1Attr[i])
attr2 = getattr(clazz2, clazz2Attr[i])
if (attr1 != attr2):
return False
i += 1
return True
def _mergeAttributes(source, target, attributeName):
mergeDirective = 'target.' + attributeName + ' = source.' + attributeName
exec(mergeDirective)
return target