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:
parent
66f99f3974
commit
75e6914df1
3 changed files with 138 additions and 50 deletions
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
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:
|
||||
finalmodule.__setattr__(attr, getattr(tocombine, attr))
|
||||
# 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:
|
||||
if inspect.ismethod(attr) == False and inspect.isbuiltin(attr) == False and inspect.isclass(attr) == False:
|
||||
finalmodule.__setattr__(attr, getattr(tocombine, attr))
|
||||
return finalmodule
|
||||
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
|
Loading…
Add table
Reference in a new issue