diff options
Diffstat (limited to 'intern/python/modules/mcf/utils/tempclassmodule.py')
-rw-r--r-- | intern/python/modules/mcf/utils/tempclassmodule.py | 251 |
1 files changed, 251 insertions, 0 deletions
diff --git a/intern/python/modules/mcf/utils/tempclassmodule.py b/intern/python/modules/mcf/utils/tempclassmodule.py new file mode 100644 index 00000000000..9fe6eed3918 --- /dev/null +++ b/intern/python/modules/mcf/utils/tempclassmodule.py @@ -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 + + +
\ No newline at end of file |