251 lines
8.1 KiB
Python
251 lines
8.1 KiB
Python
'''
|
|
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
|
|
|
|
|
|
|