Initial revision
[blender.git] / intern / python / modules / mcf / utils / tempclassmodule.py
1 '''
2 Generate module for holding temporary classes which
3 will be reconstructed into the same module to allow
4 cPickle and the like to properly import them.
5
6 Note: You _must_ pickle a reference to the tempclassmodule
7 _before_ you pickle any instances which use the classes stored
8 in the module!  Also, the classes cannot reference anything
9 in their dictionary or bases tuples which are not normally
10 pickleable (in particular, you can't subclass a class in the
11 same tempclassmodule or a tempclassmodule which you cannot
12 guarantee will be loaded before the dependent classes. (i.e.
13 by guaranteeing they will be pickled first)
14 '''
15 import new, time, string, sys, types
16
17 def buildModule(packagename, basename, rebuild=None, initialcontents=None):
18         '''
19         Dynamically build a module or rebuild one, generates
20         a persistent ID/name if not rebuilding.  The persistent
21         ID is the value of basename+`time.time()` with the decimal 
22         point removed (i.e. a long string of digits).  Packagename
23         must be an importable package!  Will raise an ImportError
24         otherwise.  Also, for easy reconstitution, basename must not
25         include any decimal points.
26         
27         initialcontents is a dictionary (or list) of elements which will be
28         added to the new module.
29         '''
30         if rebuild == None:
31                 timestamp = `time.time()`
32                 decpos = string.find(timestamp,'.')
33                 basename = basename+timestamp[:decpos]+timestamp[decpos+1:]
34         name = string.join((packagename, basename), '.')
35         a = {}
36         b = {}
37         try: # see if we've already loaded this module...
38                 mod = __import__( name, {},{}, string.split( name, '.'))
39                 if initialcontents:
40                         _updateFrom(mod, initialcontents)
41                 return mod.__name__, mod
42         except ImportError:
43                 pass
44         mod = new.module(name)
45         sys.modules[name] = mod
46         # following is just to make sure the package is loaded before attempting to alter it...
47         __import__( packagename, {}, {}, string.split(packagename) )
48 ##      exec 'import %s'%(packagename) in a, b ### Security Risk!
49         setattr(sys.modules[ packagename ], basename, mod)
50         # now do the update if there were initial contents...
51         if initialcontents:
52                 _updateFrom(mod, initialcontents)
53         return name, mod
54
55 def buildClassIn(module, *classargs, **namedclassargs):
56         '''
57         Build a new class and register it in the module
58         as if it were really defined there.
59         '''
60         print module, classargs, namedclassargs
61         namedclassargs["__temporary_class__"] = 1
62         newclass = new.classobj(classargs[0], classargs[1], namedclassargs)
63         newclass.__module__ = module.__name__
64         setattr(module, newclass.__name__, newclass)
65         return newclass
66
67 def addClass(module, classobj):
68         '''
69         Insert a classobj into the tempclassmodule, setting the
70         class' __module__ attribute to point to this tempclassmodule
71         '''
72         classobj.__module__ = module.__name__
73         setattr(module, classobj.__name__, classobj)
74         setattr( classobj, "__temporary_class__", 1)
75
76 def delClass(module, classobj):
77         '''
78         Remove this class from the module, Note: after running this
79         the classobj is no longer able to be pickled/unpickled unless
80         it is subsequently added to another module.  This is because
81         it's __module__ attribute is now pointing to a module which
82         is no longer going to save its definition!
83         '''
84         try:
85                 delattr(module, classobj.__name__)
86         except AttributeError:
87                 pass
88
89 def _packageName(modulename):
90         decpos = string.rfind(modulename, '.')
91         return modulename[:decpos], modulename[decpos+1:]
92
93 def _updateFrom(module, contentsource):
94         '''
95         For dealing with unknown datatypes (those passed in by the user),
96         we want to check and make sure we're building the classes correctly.
97         '''
98         # often will pass in a protoNamespace from which to update (during cloning)
99         if type(contentsource) in ( types.DictType, types.InstanceType):
100                 contentsource = contentsource.values()
101         # contentsource should now be a list of classes or class-building tuples
102         for val in contentsource:
103                 if type(val) is types.ClassType:
104                         try:
105                                 addClass(module, val)
106                         except:
107                                 pass
108                 elif type(val) is types.TupleType:
109                         try:
110                                 apply(buildClassIn, (module,)+val)
111                         except:
112                                 pass
113
114 def deconstruct(templatemodule):
115         '''
116         Return a tuple which can be passed to reconstruct
117         in order to get a rebuilt version of the module
118         after pickling. i.e. apply(reconstruct, deconstruct(tempmodule))
119         is the equivalent of doing a deepcopy on the tempmodule.
120         '''
121 ##      import pdb
122 ##      pdb.set_trace()
123         classbuilder = []
124         for name, classobj in templatemodule.__dict__.items():
125                 if type(classobj) is types.ClassType: # only copy class objects, could do others, but these are special-purpose modules, not general-purpose ones.
126                         classbuilder.append( deconstruct_class( classobj) )
127 ##              import pdb
128 ##              pdb.set_trace()
129         return (templatemodule.__name__, classbuilder)
130 ##      except AttributeError:
131 ##              print templatemodule
132 ##              print classbuilder
133         
134 def deconstruct_class( classobj ):
135         '''
136         Pull apart a class into a tuple of values which can be used
137         to reconstruct it through a call to buildClassIn
138         '''
139         if not hasattr( classobj, "__temporary_class__"):
140                 # this is a regular class, re-import on load...
141                 return (classobj.__module__, classobj.__name__)
142         else:
143                 # this is a temporary class which can be deconstructed
144                 bases = []
145                 for classobject in classobj.__bases__:
146                         bases.append( deconstruct_class (classobject) )
147                 return (classobj.__name__, tuple (bases), classobj.__dict__)
148                         
149
150 def reconstruct(modulename, classbuilder):
151         '''
152         Rebuild a temporary module and all of its classes
153         from the structure created by deconstruct.
154         i.e. apply(reconstruct, deconstruct(tempmodule))
155         is the equivalent of doing a deepcopy on the tempmodule.
156         '''
157 ##      import pdb
158 ##      pdb.set_trace()
159         mname, newmod = apply(buildModule, _packageName(modulename)+(1,) ) # 1 signals reconstruct
160         reconstruct_classes( newmod, classbuilder )
161         return newmod
162
163 def reconstruct_classes( module, constructors ):
164         '''
165         Put a class back together from the tuple of values
166         created by deconstruct_class.
167         '''
168         classes = []
169         import pprint
170         pprint.pprint( constructors)
171         for constructor in constructors:
172                 if len (constructor) == 2:
173                         module, name = constructor
174                         # this is a standard class, re-import
175                         temporarymodule = __import__(
176                                 module,
177                                 {},{},
178                                 string.split(module)+[name]
179                         )
180                         classobject =getattr (temporarymodule, name)
181                 else:
182                         # this is a class which needs to be re-constructed
183                         (name, bases,namedarguments) = constructor
184                         bases = tuple( reconstruct_classes( module, bases ))
185                         classobject = apply (
186                                 buildClassIn,
187                                 (module, name, bases), # name and bases are the args to the class constructor along with the dict contents in namedarguments
188                                 namedarguments,
189                         )
190                 classes.append (classobject)
191         return classes
192         
193         
194 def destroy(tempmodule):
195         '''
196         Destroy the module to allow the system to do garbage collection
197         on it.  I'm not sure that the system really does do gc on modules,
198         but one would hope :)
199         '''
200         name = tempmodule.__name__
201         tempmodule.__dict__.clear() # clears references to the classes
202         try:
203                 del(sys.modules[name])
204         except KeyError:
205                 pass
206         packagename, modname = _packageName(name)
207         try:
208                 delattr(sys.modules[ packagename ], modname)
209         except AttributeError:
210                 pass
211         del( tempmodule ) # no, I don't see any reason to do it...
212         return None
213         
214         
215 def deepcopy(templatemodule, packagename=None, basename=None):
216         '''
217         Rebuild the whole Module and it's included classes
218         (just the classes).  Note: This will _not_ make instances
219         based on the old classes point to the new classes!
220         The value of this function is likely to be minimal given
221         this restriction.  For pickling use deconstruct/reconstruct
222         for simple copying just return the module.
223         '''
224         name, classbuilder = deconstruct( templatemodule )
225         if packagename is None:
226                 tp, tb = _packageName( name )
227                 if packagename is None:
228                         packagename = tp
229                 if basename is None:
230                         basename = tb
231         newmod = buildModule(packagename, basename, initialcontents=classbuilder )
232         return newmod
233
234 if __name__ == "__main__":
235         def testPickle ():
236                 import mcf.vrml.prototype
237                 name, module = buildModule( 'mcf.vrml.temp', 'scenegraph' )
238                 buildClassIn( module, 'this', () )
239                 buildClassIn( module, 'that', (mcf.vrml.prototype.ProtoTypeNode,) )
240 ##              import pdb
241 ##              pdb.set_trace()
242                 import pprint
243                 pprint.pprint( deconstruct( module ))
244                 name,builder = deconstruct( module )
245                 destroy( module)
246                 return reconstruct(name, builder)
247         t = testPickle()
248         print t
249
250
251