Initial revision
This commit is contained in:
251
intern/python/modules/mcf/utils/tempclassmodule.py
Normal file
251
intern/python/modules/mcf/utils/tempclassmodule.py
Normal file
@@ -0,0 +1,251 @@
|
||||
'''
|
||||
Generate module for holding temporary classes which
|
||||
will be reconstructed into the same module to allow
|
||||
cPickle and the like to properly import them.
|
||||
|
||||
Note: You _must_ pickle a reference to the tempclassmodule
|
||||
_before_ you pickle any instances which use the classes stored
|
||||
in the module! Also, the classes cannot reference anything
|
||||
in their dictionary or bases tuples which are not normally
|
||||
pickleable (in particular, you can't subclass a class in the
|
||||
same tempclassmodule or a tempclassmodule which you cannot
|
||||
guarantee will be loaded before the dependent classes. (i.e.
|
||||
by guaranteeing they will be pickled first)
|
||||
'''
|
||||
import new, time, string, sys, types
|
||||
|
||||
def buildModule(packagename, basename, rebuild=None, initialcontents=None):
|
||||
'''
|
||||
Dynamically build a module or rebuild one, generates
|
||||
a persistent ID/name if not rebuilding. The persistent
|
||||
ID is the value of basename+`time.time()` with the decimal
|
||||
point removed (i.e. a long string of digits). Packagename
|
||||
must be an importable package! Will raise an ImportError
|
||||
otherwise. Also, for easy reconstitution, basename must not
|
||||
include any decimal points.
|
||||
|
||||
initialcontents is a dictionary (or list) of elements which will be
|
||||
added to the new module.
|
||||
'''
|
||||
if rebuild == None:
|
||||
timestamp = `time.time()`
|
||||
decpos = string.find(timestamp,'.')
|
||||
basename = basename+timestamp[:decpos]+timestamp[decpos+1:]
|
||||
name = string.join((packagename, basename), '.')
|
||||
a = {}
|
||||
b = {}
|
||||
try: # see if we've already loaded this module...
|
||||
mod = __import__( name, {},{}, string.split( name, '.'))
|
||||
if initialcontents:
|
||||
_updateFrom(mod, initialcontents)
|
||||
return mod.__name__, mod
|
||||
except ImportError:
|
||||
pass
|
||||
mod = new.module(name)
|
||||
sys.modules[name] = mod
|
||||
# following is just to make sure the package is loaded before attempting to alter it...
|
||||
__import__( packagename, {}, {}, string.split(packagename) )
|
||||
## exec 'import %s'%(packagename) in a, b ### Security Risk!
|
||||
setattr(sys.modules[ packagename ], basename, mod)
|
||||
# now do the update if there were initial contents...
|
||||
if initialcontents:
|
||||
_updateFrom(mod, initialcontents)
|
||||
return name, mod
|
||||
|
||||
def buildClassIn(module, *classargs, **namedclassargs):
|
||||
'''
|
||||
Build a new class and register it in the module
|
||||
as if it were really defined there.
|
||||
'''
|
||||
print module, classargs, namedclassargs
|
||||
namedclassargs["__temporary_class__"] = 1
|
||||
newclass = new.classobj(classargs[0], classargs[1], namedclassargs)
|
||||
newclass.__module__ = module.__name__
|
||||
setattr(module, newclass.__name__, newclass)
|
||||
return newclass
|
||||
|
||||
def addClass(module, classobj):
|
||||
'''
|
||||
Insert a classobj into the tempclassmodule, setting the
|
||||
class' __module__ attribute to point to this tempclassmodule
|
||||
'''
|
||||
classobj.__module__ = module.__name__
|
||||
setattr(module, classobj.__name__, classobj)
|
||||
setattr( classobj, "__temporary_class__", 1)
|
||||
|
||||
def delClass(module, classobj):
|
||||
'''
|
||||
Remove this class from the module, Note: after running this
|
||||
the classobj is no longer able to be pickled/unpickled unless
|
||||
it is subsequently added to another module. This is because
|
||||
it's __module__ attribute is now pointing to a module which
|
||||
is no longer going to save its definition!
|
||||
'''
|
||||
try:
|
||||
delattr(module, classobj.__name__)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def _packageName(modulename):
|
||||
decpos = string.rfind(modulename, '.')
|
||||
return modulename[:decpos], modulename[decpos+1:]
|
||||
|
||||
def _updateFrom(module, contentsource):
|
||||
'''
|
||||
For dealing with unknown datatypes (those passed in by the user),
|
||||
we want to check and make sure we're building the classes correctly.
|
||||
'''
|
||||
# often will pass in a protoNamespace from which to update (during cloning)
|
||||
if type(contentsource) in ( types.DictType, types.InstanceType):
|
||||
contentsource = contentsource.values()
|
||||
# contentsource should now be a list of classes or class-building tuples
|
||||
for val in contentsource:
|
||||
if type(val) is types.ClassType:
|
||||
try:
|
||||
addClass(module, val)
|
||||
except:
|
||||
pass
|
||||
elif type(val) is types.TupleType:
|
||||
try:
|
||||
apply(buildClassIn, (module,)+val)
|
||||
except:
|
||||
pass
|
||||
|
||||
def deconstruct(templatemodule):
|
||||
'''
|
||||
Return a tuple which can be passed to reconstruct
|
||||
in order to get a rebuilt version of the module
|
||||
after pickling. i.e. apply(reconstruct, deconstruct(tempmodule))
|
||||
is the equivalent of doing a deepcopy on the tempmodule.
|
||||
'''
|
||||
## import pdb
|
||||
## pdb.set_trace()
|
||||
classbuilder = []
|
||||
for name, classobj in templatemodule.__dict__.items():
|
||||
if type(classobj) is types.ClassType: # only copy class objects, could do others, but these are special-purpose modules, not general-purpose ones.
|
||||
classbuilder.append( deconstruct_class( classobj) )
|
||||
## import pdb
|
||||
## pdb.set_trace()
|
||||
return (templatemodule.__name__, classbuilder)
|
||||
## except AttributeError:
|
||||
## print templatemodule
|
||||
## print classbuilder
|
||||
|
||||
def deconstruct_class( classobj ):
|
||||
'''
|
||||
Pull apart a class into a tuple of values which can be used
|
||||
to reconstruct it through a call to buildClassIn
|
||||
'''
|
||||
if not hasattr( classobj, "__temporary_class__"):
|
||||
# this is a regular class, re-import on load...
|
||||
return (classobj.__module__, classobj.__name__)
|
||||
else:
|
||||
# this is a temporary class which can be deconstructed
|
||||
bases = []
|
||||
for classobject in classobj.__bases__:
|
||||
bases.append( deconstruct_class (classobject) )
|
||||
return (classobj.__name__, tuple (bases), classobj.__dict__)
|
||||
|
||||
|
||||
def reconstruct(modulename, classbuilder):
|
||||
'''
|
||||
Rebuild a temporary module and all of its classes
|
||||
from the structure created by deconstruct.
|
||||
i.e. apply(reconstruct, deconstruct(tempmodule))
|
||||
is the equivalent of doing a deepcopy on the tempmodule.
|
||||
'''
|
||||
## import pdb
|
||||
## pdb.set_trace()
|
||||
mname, newmod = apply(buildModule, _packageName(modulename)+(1,) ) # 1 signals reconstruct
|
||||
reconstruct_classes( newmod, classbuilder )
|
||||
return newmod
|
||||
|
||||
def reconstruct_classes( module, constructors ):
|
||||
'''
|
||||
Put a class back together from the tuple of values
|
||||
created by deconstruct_class.
|
||||
'''
|
||||
classes = []
|
||||
import pprint
|
||||
pprint.pprint( constructors)
|
||||
for constructor in constructors:
|
||||
if len (constructor) == 2:
|
||||
module, name = constructor
|
||||
# this is a standard class, re-import
|
||||
temporarymodule = __import__(
|
||||
module,
|
||||
{},{},
|
||||
string.split(module)+[name]
|
||||
)
|
||||
classobject =getattr (temporarymodule, name)
|
||||
else:
|
||||
# this is a class which needs to be re-constructed
|
||||
(name, bases,namedarguments) = constructor
|
||||
bases = tuple( reconstruct_classes( module, bases ))
|
||||
classobject = apply (
|
||||
buildClassIn,
|
||||
(module, name, bases), # name and bases are the args to the class constructor along with the dict contents in namedarguments
|
||||
namedarguments,
|
||||
)
|
||||
classes.append (classobject)
|
||||
return classes
|
||||
|
||||
|
||||
def destroy(tempmodule):
|
||||
'''
|
||||
Destroy the module to allow the system to do garbage collection
|
||||
on it. I'm not sure that the system really does do gc on modules,
|
||||
but one would hope :)
|
||||
'''
|
||||
name = tempmodule.__name__
|
||||
tempmodule.__dict__.clear() # clears references to the classes
|
||||
try:
|
||||
del(sys.modules[name])
|
||||
except KeyError:
|
||||
pass
|
||||
packagename, modname = _packageName(name)
|
||||
try:
|
||||
delattr(sys.modules[ packagename ], modname)
|
||||
except AttributeError:
|
||||
pass
|
||||
del( tempmodule ) # no, I don't see any reason to do it...
|
||||
return None
|
||||
|
||||
|
||||
def deepcopy(templatemodule, packagename=None, basename=None):
|
||||
'''
|
||||
Rebuild the whole Module and it's included classes
|
||||
(just the classes). Note: This will _not_ make instances
|
||||
based on the old classes point to the new classes!
|
||||
The value of this function is likely to be minimal given
|
||||
this restriction. For pickling use deconstruct/reconstruct
|
||||
for simple copying just return the module.
|
||||
'''
|
||||
name, classbuilder = deconstruct( templatemodule )
|
||||
if packagename is None:
|
||||
tp, tb = _packageName( name )
|
||||
if packagename is None:
|
||||
packagename = tp
|
||||
if basename is None:
|
||||
basename = tb
|
||||
newmod = buildModule(packagename, basename, initialcontents=classbuilder )
|
||||
return newmod
|
||||
|
||||
if __name__ == "__main__":
|
||||
def testPickle ():
|
||||
import mcf.vrml.prototype
|
||||
name, module = buildModule( 'mcf.vrml.temp', 'scenegraph' )
|
||||
buildClassIn( module, 'this', () )
|
||||
buildClassIn( module, 'that', (mcf.vrml.prototype.ProtoTypeNode,) )
|
||||
## import pdb
|
||||
## pdb.set_trace()
|
||||
import pprint
|
||||
pprint.pprint( deconstruct( module ))
|
||||
name,builder = deconstruct( module )
|
||||
destroy( module)
|
||||
return reconstruct(name, builder)
|
||||
t = testPickle()
|
||||
print t
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user