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 | ||
|  | 
 | ||
|  | 
 | ||
|  | 	 |