1
2 """
3 Provides classes and functions operating on the MayaAPI class database
4
5 :note: This module must not be auto-initialized as it assumes its parent package to
6 be present already
7 :note: The implementation is considered internal and may change any time unless stated
8 otherwise.
9 """
10 __docformat__ = "restructuredtext"
11
12 from mrv.path import make_path
13 from mrv.util import PipeSeparatedFile
14 import mrv.maya.env as env
15 import mrv.maya as mrvmaya
16
17 import maya.cmds as cmds
18 import maya.OpenMaya as api
19
20 import UserDict
21 import inspect
22 import re
23 from cStringIO import StringIO
24 import string
25 import sys
26 import os
27
28 import logging
29 log = logging.getLogger("mrv.maya.mdb")
30
31 __all__ = ("createDagNodeHierarchy", "createTypeNameToMfnClsMap", "apiModules",
32 "mfnDBPath", "cacheFilePath", "writeMfnDBCacheFiles",
33 "extractMFnFunctions", "PythonMFnCodeGenerator", "MMemberMap",
34 "MMethodDescriptor" )
39 """:return: Path to the node hierarchy file of the currently active maya version"""
40 return cacheFilePath( "nodeHierarchy", "hf", use_version = 1 )
41
47
49 """Parse a file associating node type names with the best compatible MFn function
50 set and return a dictionary with the data
51
52 :return: dict(((nodeTypeNameStr : api.MFnCls), ...)) dictionary with nodetypeName
53 MFn class mapping"""
54 typenameToClsMap = dict()
55
56 cfile = cacheFilePath( "nodeTypeToMfnCls", "map" )
57 fobj = open( cfile, 'r' )
58 pf = PipeSeparatedFile( fobj )
59
60 version = pf.beginReading( )
61 for nodeTypeName, mfnTypeName in pf.readColumnLine( ):
62 found = False
63 for apimod in apiModules():
64 try:
65 typenameToClsMap[ nodeTypeName ] = getattr( apimod, mfnTypeName )
66 found = True
67 break
68 except AttributeError:
69 pass
70
71 if not found:
72 log.debug("Couldn't find mfn class named %s" % mfnTypeName)
73
74 fobj.close()
75
76 return typenameToClsMap
77
84 """:return: tuple of api modules containing MayaAPI classes
85 :note: This takes a moment to load as it will import many api modules. Delay
86 the call as much as possible"""
87 import maya.OpenMaya as api
88 import maya.OpenMayaAnim as apianim
89 import maya.OpenMayaUI as apiui
90 import maya.OpenMayaRender as apirender
91 import maya.OpenMayaFX as apifx
92
93 return (api, apianim, apiui, apirender, apifx)
94
96 """Generate a path to a database file containing mfn wrapping information"""
97 return make_path(cacheFilePath("mfndb/"+ mfnclsname, '', use_version=False)[:-1])
98
100 """
101 :return: Path to file containing the c++ header of the given apiclass' name.
102 The file will not be verified, hence it may be inaccessible
103 :param apiname: string name, like 'MFnBase'
104 :raise ValueError: if MAYA_LOCATION is not set"""
105 p = make_path("$MAYA_LOCATION").expand_or_raise().realpath()
106 if sys.platform == 'darwin':
107 p = p.parent().parent() / "devkit"
108
109 return p / ("include/maya/%s.h" % apiname)
110
112 """Return path to cache file from which you would initialize data structures
113
114 :param use_version: if true, the maya version will be appended to the filename """
115 mfile = make_path( __file__ ).parent()
116 version = ""
117 if use_version:
118 version = cmds.about( version=1 ).split( " " )[0]
119
120 return mfile / ( "cache/%s%s.%s" % ( filename, version, ext ) )
121
123 """Extract callables from mfncls, sorted into static methods and instance methods
124 :return: tuple(list(callable_staticmethod, ...), list(callable_instancemethod, ...))"""
125 mfnfuncs = list()
126 staticmfnfuncs = list()
127 mfnname = mfncls.__name__
128 for fn, f in mfncls.__dict__.iteritems():
129 if fn.startswith('_') or fn.endswith(mfnname) or not inspect.isroutine(f):
130 continue
131
132
133 if isinstance(f, staticmethod):
134
135 staticmfnfuncs.append(getattr(mfncls, fn))
136 else:
137 mfnfuncs.append(f)
138
139
140
141 return (staticmfnfuncs, mfnfuncs)
142
144 """:return: True if the given mfncls has at least one enumeration"""
145 for n in mfncls.__dict__.keys():
146 if n.startswith('k') and n[1] in string.ascii_uppercase:
147 return True
148
149 return False
150
152 """Create a simple Memberlist of available mfn classes and their members
153 to allow a simple human-editable way of adjusting which methods will be added
154 to the Nodes.
155
156 :note: currently writes information about all known api modules"""
157 for apimod in apiModules():
158 mfnclsnames = [ clsname for clsname in dir( apimod ) if clsname.startswith( "MFn" ) ]
159 for mfnname in mfnclsnames:
160 mfncls = getattr( apimod, mfnname )
161 if not inspect.isclass(mfncls):
162 continue
163
164 mfnfile = mfnDBPath( mfnname )
165
166
167 mfnfuncs = list()
168 fstatic, finst = extractMFnFunctions(mfncls)
169 mfnfuncs.extend(fstatic)
170 mfnfuncs.extend(finst)
171
172 if not mfnfuncs:
173 continue
174
175 db = MMemberMap()
176 if mfnfile.exists():
177 db = MMemberMap( mfnfile )
178
179
180 folder = mfnfile.dirname()
181 if not folder.isdir(): folder.makedirs()
182
183
184
185 for func in mfnfuncs:
186
187
188 fname = func.__name__
189 if fname.startswith(mfnname):
190 fname = fname[len(mfnname)+1:]
191
192
193 db.createEntry(fname)
194
195
196
197 db.writeToFile( mfnfile )
198
202 """Return tuple(mobject, modifier) for the nodetype or raise RuntimeError
203 doIt has not yet been called on the modifier, hence the mobject is temporary"""
204 try:
205 mod = api.MDGModifier()
206 obj = mod.createNode(nodetype)
207 return (obj, mod)
208 except RuntimeError:
209 mod = api.MDagModifier()
210 tmpparent = mod.createNode("transform")
211 obj = mod.createNode(nodetype, tmpparent)
212 return (obj, mod)
213
217 """Returns iterator which yield tuple(nodeTypeName, MObject, modifier) triplets
218 of nodeTypes, with an MObjects instance of it, created with the given modifier,
219 one for each node type available to maya.
220
221 :note: skips manipulators as they tend to crash maya on creation ( perhaps its only
222 one which does that, but its not that important )"""
223 for nodetype in sorted(cmds.ls(nodeTypes=1)):
224
225 if 'Manip' in nodetype or nodetype.startswith('manip'):
226 continue
227
228 try:
229 obj, mod = _createTmpNode(nodetype)
230 yield nodetype, obj, mod
231 except RuntimeError:
232 log.warn("Could not create '%s'" % nodetype)
233 continue
234
237 """Generate the node-hierarchy for the current version based on all node types
238 which can be created in maya.
239
240 :return: tuple(DAGTree, typeToMFnClsNameList)
241
242 * DAGTree representing the type hierarchy
243 * list represents typeName to MFnClassName associations
244
245 :note: should only be run as part of the upgrade process to prepare MRV for a
246 new maya release. Otherwise the nodetype tree will be read from a cache"""
247 from mrv.util import DAGTree
248 from mrv.util import uncapitalize, capitalize
249 from mrv.maya.util import MEnumeration
250
251
252 root = "_root_"
253 depnode = 'dependNode'
254 depnode_list = [depnode]
255 noderoottype = 'node'
256 dagTree = DAGTree()
257 dagTree.add_edge(root, noderoottype)
258 dagTree.add_edge(noderoottype, depnode)
259
260 apiTypeToNodeTypeMap = dict()
261 mfnTypes = set()
262 sl = list()
263
264
265 mfndep = api.MFnDependencyNode()
266 def getInheritanceAndUndo(obj, modifier):
267 """Takes a prepared modifier ( doIt not yet called ) and the previously created object,
268 returning the inheritance of the obj which was retrieved before undoing
269 its creation"""
270 modifier.doIt()
271 mfndep.setObject(obj)
272 inheritance = cmds.nodeType(mfndep.name(), i=1)
273 modifier.undoIt()
274 return inheritance
275
276
277
278
279
280
281 for nodetype, obj, mod in _iterAllNodeTypes():
282 inheritance = getInheritanceAndUndo(obj, mod)
283
284 if not inheritance:
285 log.error("Failed on type %s" % nodetype)
286 continue
287
288
289
290 for parent, child in zip(depnode_list + inheritance[:-1], inheritance):
291 dagTree.add_edge(parent, child)
292
293
294
295
296
297
298 apiTypeToNodeTypeMap[obj.apiTypeStr()] = nodetype
299
300 api.MGlobal.getFunctionSetList(obj, sl)
301 for mfnType in sl:
302 mfnTypes.add(mfnType)
303
304
305
306
307
308 dagTree.add_edge(depnode, 'unknown')
309
310
311
312
313 dagTree.add_edge('transform', 'groundPlane')
314
315
316
317 dagTree.add_edge('transform', 'manipContainer')
318
319
320
321
322
323 for edge in ( (depnode, 'DependNode'),
324 ('shape', 'Shape'),
325 ('locator', 'LocatorNode'),
326 ('spring', 'SpringNode'),
327 ('transform', 'TransformNode'),
328 ('manipContainer', 'ManipContainer'),
329 ('dynBase', 'EmitterNode'),
330 ('field', 'FieldNode'),
331 ('objectSet', 'ObjectSet'),
332 ('geometryFilter', 'DeformerNode'),
333 (depnode, 'HwShaderNode'),
334 ('ikSolver', 'IkSolver'),
335 (depnode, 'ImagePlaneNode'),
336 (depnode, 'ParticleAttributeMapperNode') ):
337 dagTree.add_edge(edge[0], 'unknownPlugin'+edge[1])
338
339
340
341
342
343
344
345
346 typeToMFn = set()
347
348
349 typeToMFn.add((noderoottype, 'MFn'))
350 typeToMFn.add((depnode, 'MFnDependencyNode'))
351 typeToMFn.add(('dagContainer', 'MFnContainerNode'))
352
353 abstractMFns = ('MFnBase', )
354 failedMFnTypes = list()
355
356 modsapi = apiModules()
357 for mfnApiType in mfnTypes:
358 mfnNodePseudoType = uncapitalize(mfnApiType[1:])
359 nodeType = apiTypeToNodeTypeMap.get(mfnApiType, mfnNodePseudoType)
360
361
362
363
364 found = False
365 for nt, mfnNameCandidate in ( (mfnNodePseudoType, "MFn%s" % capitalize(mfnApiType[1:])),
366 (nodeType, "MFn%s" % capitalize(nodeType)) ):
367
368 if mfnNameCandidate in abstractMFns:
369 continue
370
371 for modapi in modsapi:
372 if hasattr(modapi, mfnNameCandidate):
373 found = True
374
375
376
377 typeToMFn.add((nodeType, mfnNameCandidate))
378 break
379
380
381
382 if found:
383 break
384
385
386
387
388 if not found and mfnApiType in apiTypeToNodeTypeMap:
389 failedMFnTypes.append(mfnApiType)
390
391
392
393
394
395
396
397
398 def unMFn(name):
399 return uncapitalize(name[3:])
400
401
402 for mfnsuffix in ("data", "component", "attribute"):
403 mfnsuffixcap = capitalize(mfnsuffix)
404 mfnnames = list()
405 for modapi in modsapi:
406 mfnnames.extend( n for n in dir(modapi) if n.endswith(mfnsuffixcap) )
407
408
409 dagTree.add_edge(root, mfnsuffix)
410
411 mfnsuffix_root = [ mfnsuffix ]
412 for mfnname in mfnnames:
413 for modapi in modsapi:
414 try:
415 mfncls = getattr(modapi, mfnname)
416 except AttributeError:
417 continue
418
419
420
421
422 if "MFn%s" % mfnsuffixcap not in ( p.__name__ for p in mfncls.mro() ):
423 continue
424
425
426 typeToMFn.add((unMFn(mfnname), mfnname))
427
428
429
430 pclsnames = [ unMFn(p.__name__) for p in list(reversed(mfncls.mro()))[2:] ]
431 for parent, child in zip(pclsnames[:-1], pclsnames[1:]):
432 dagTree.add_edge(parent, child)
433
434
435 break
436
437
438
439
440
441
442
443
444 if failedMFnTypes:
445
446
447
448
449
450
451
452 associatedMFns = ( t[1] for t in typeToMFn )
453 allMFnSetNames = list()
454 for modapi in modsapi:
455 allMFnSetNames.extend( n for n in dir(modapi) if n.startswith('MFn') and
456 not n.endswith('Ptr') and
457 not '_' in n and
458 not 'Manip' in n )
459
460
461
462 candidateMFnNames = (set(allMFnSetNames) - set(associatedMFns)) - set(abstractMFns)
463 candidateMFns = list()
464 for cn in list(candidateMFnNames):
465 for modapi in modsapi:
466 try:
467 mfncls = getattr(modapi, cn)
468
469 if not hasattr(mfncls, "type"):
470 log.debug("Skipped MFn %s as it didn't derive from MFnBase" % mfncls)
471 candidateMFnNames.discard(cn)
472 continue
473
474 candidateMFns.append(mfncls)
475 break
476 except AttributeError:
477 continue
478
479
480
481 succeededMFnNames = set()
482
483
484
485
486 enumMembers = MEnumDescriptor('Type')
487 enumMembers.extend( m for m in dir(api.MFn) if m.startswith('k') )
488 mfntypes = MEnumeration.create(enumMembers, api.MFn)
489
490 for mfncls in candidateMFns[:]:
491 try:
492 mfninst = mfncls()
493 if mfntypes.nameByValue(mfninst.type()) in failedMFnTypes:
494 continue
495
496 candidateMFns.remove(mfncls)
497 candidateMFnNames.remove(mfncls.__name__)
498 except RuntimeError:
499 continue
500
501
502
503
504
505
506 derivedMatches = list()
507 perfectMatches = list()
508 for failedApiTypeStr in failedMFnTypes:
509 nodeType = apiTypeToNodeTypeMap[failedApiTypeStr]
510 obj, mod = _createTmpNode(nodeType)
511
512 removeThisMFn = None
513 for mfncls in candidateMFns:
514 try:
515 mfninst = mfncls(obj)
516 except RuntimeError:
517 continue
518
519
520 apiTypeStr = mfntypes.nameByValue(mfninst.type())
521
522 if apiTypeStr not in failedMFnTypes:
523 removeThisMFn = mfncls
524 break
525
526
527 if apiTypeStr == failedApiTypeStr:
528 mfnname = mfncls.__name__
529 typeToMFn.add((nodeType, mfnname))
530 perfectMatches.append(mfnname)
531 removeThisMFn = mfncls
532 break
533
534
535
536
537 derivedMatches.append((apiTypeStr, mfncls.__name__))
538
539
540 if removeThisMFn is not None:
541 succeededMFnNames.add(removeThisMFn.__name__)
542 candidateMFns.remove(removeThisMFn)
543
544
545 if not candidateMFns:
546 break
547
548
549
550
551
552
553 for apiTypeStr, mfnname in filter(lambda t: t not in perfectMatches, derivedMatches):
554 typeToMFn.add((apiTypeToNodeTypeMap[apiTypeStr], mfnname))
555 succeededMFnNames.add(mfnname)
556
557
558
559
560
561
562
563
564 for nodeType, mfnname in (('subdiv', 'MFnSubd'), ):
565 typeToMFn.add((nodeType, mfnname))
566 succeededMFnNames.add(mfnname)
567
568
569
570 for mfnname in candidateMFnNames - succeededMFnNames:
571 log.warn("Could not associate MFn: %s" % mfnname)
572
573
574 return (dagTree, sorted(typeToMFn, key=lambda t: t[0]))
575
582 """Define the interface and common utility methods to generate a string defining
583 code for a given MFnMethod according to the meta data provided by an `MMethodDescriptor`.
584
585 Once instantiated, it can create any number of methods"""
586 __slots__ = 'module_dict'
588 """Intialize this instance"""
589 self.module_dict = module_dict
590
591
593 """:return: None or a function which receives the return value of our actual mfn function"""
594 if not isinstance( funcname, basestring ):
595 return funcname
596 if funcname == 'None': return None
597
598 try:
599 return self.module_dict[funcname]
600 except KeyError:
601 raise ValueError("'%s' does not exist in code generator's dictionary" % funcname )
602
603
604
605
607 """
608 :return: string containing the code for the wrapper method as configured by the
609 method descriptor
610 :param source_method_name: Original name of the method - this is the name under which
611 it was requested.
612 :param target_method_name: Name of the method in the returned code string
613 :param mfn_fun_name: original name of the MFn function
614 :param method_descriptor: instance of `MMethodDescriptor`
615 :param flags: bit flags providing additional information, depending on the actual
616 implementation. Unsupported flags are ignored."""
617 raise NotImplementedError("To be implemented in SubClass")
618
622 """Specialization to generate python code
623
624 **Flags**:
625
626 * kDirectCall:
627 If set, the call return the actual mfn method in the best case, which is
628 a call as direct as it gets. A possibly negative side-effect would be that
629 it the MFnMethod caches the function set and actual MObject/MDagPath, which
630 can be dangerous if held too long
631
632 * kIsMObject:
633 If set, the type we create the method for is not derived from Node, but
634 from MObject. This hint is required in order to generate correct calling code.
635
636 * kIsDagNode:
637 If set, the type we create the method for is derived from DagNode
638
639 * kIsStatic:
640 If set, the method to be wrapped is considered static, no self is needed, nor
641 any object.
642 NOTE: This flag is likely to be removed as it should be part of the method_descriptor,
643 for now though it does not provide that information so we pass it in.
644
645 * kWithDocs:
646 If set, a doc string will be generated the method. In future, this information
647 will come from the method descriptor. Please note that docs should only be attaced
648 in interactive modes, otherwise its a waste of memory.
649
650 """
651
652 kDirectCall, \
653 kIsMObject, \
654 kIsDagNode, \
655 kIsStatic, \
656 kWithDocs = [ 1<<i for i in range(5) ]
657
659 """Generates code as python string which can be used to compile a function. It assumes the following
660 globals to be existing once evaluated: mfncls, mfn_fun, [rvalfunc]
661 Currently supports the following data within method_descriptor:
662
663 * method_descriptor.rvalfunc
664
665 as well as all flags except kIsStatic.
666 :raise ValueError: if flags are incompatible with each other
667 """
668 if flags & self.kIsMObject and flags & self.kIsDagNode:
669 raise ValueError("kIsMObject and kIsDagNode are mutually exclusive")
670
671
672
673 sio = StringIO()
674
675 rvalfunname = ''
676 if method_descriptor.rvalfunc != 'None':
677 rvalfunname = method_descriptor.rvalfunc
678
679 sio.write("def %s(self, *args, **kwargs):\n" % target_method_name)
680
681
682
683 mfnset = "mfncls(self"
684 if flags & self.kIsDagNode:
685 mfnset += ".dagPath()"
686 elif not flags & self.kIsMObject:
687 mfnset += ".object()"
688 mfnset += ")"
689
690 if flags & self.kDirectCall:
691 curline = "\tmfninstfunc = %s.%s\n" % (mfnset, mfn_fun_name)
692 sio.write(curline)
693
694 if rvalfunname:
695 sio.write("\tmfninstfunc = lambda *args, **kwargs: rvalfun(mfninstfunc(*args, **kwargs))\n")
696
697 sio.write("\tself.%s = mfninstfunc\n" % source_method_name)
698 sio.write("\treturn mfninstfunc(*args, **kwargs)")
699 else:
700 curline = "mfn_fun(%s, *args, **kwargs)" % mfnset
701 if rvalfunname:
702 curline = "rvalfunc(%s)" % curline
703 sio.write("\treturn %s" % curline)
704
705
706 return sio.getvalue()
707
708
709
711 """:return: python function suitable to be installed on a class
712 :param mfncls: MFnFunction set class from which the method was retrieved.
713 :param mfn_fun: function as retrieved from the function set's dict. Its a bare function.
714 :note: For all other args, see `MFnCodeGeneratorBase.generateMFnClsMethodWrapper`"""
715 rvalfunc = self._toRvalFunc(method_descriptor.rvalfunc)
716 mfnfuncname = mfn_fun.__name__
717
718
719 if mfnfuncname.startswith(mfncls.__name__):
720 mfnfuncname = mfnfuncname[len(mfncls.__name__)+1:]
721
722 new_method = None
723 if flags & self.kIsStatic:
724
725 rvalfun = self._toRvalFunc(method_descriptor.rvalfunc)
726 if rvalfun is None:
727 new_method = mfn_fun
728 else:
729 fun = lambda *args, **kwargs: rvalfun(mfn_fun(*args, **kwargs))
730 fun.__name__ = target_method_name
731 new_method = fun
732
733 else:
734
735 codestr = self.generateMFnClsMethodWrapper(source_method_name, target_method_name, mfnfuncname, method_descriptor, flags)
736 code = compile(codestr, "mrv/%s" % (mfncls.__name__+".py"), "exec")
737
738
739 eval(code, locals())
740
741 new_method = locals()[target_method_name]
742
743
744 if flags & self.kWithDocs:
745 if hasattr(new_method, 'func_doc'):
746 new_method.func_doc = "%s.%s" % (mfncls.__name__, mfnfuncname)
747
748
749 return new_method
750
758 """Simplistic regex based parser which will extract information from the file
759 it was initialized with.
760
761 For now its so simple that there is no more than one method"""
762 reEnums = re.compile( r"""^\s+ enum \s+ (?P<name>\w+) \s* \{ # enum EnumName
763 (?P<members>[\(\)/\w\s,\-+="'\.\#!<\*\\]+) # match whitespace or newlines
764 \}[ \t]*;[ \t]*$ # closing brace""",
765 re.MULTILINE|re.VERBOSE)
766
767 reEnumMembers = re.compile( """
768 [\t ]{2,} # assure we don't get something within the comment
769 (k\w+)[ ]* # find kSomething
770 (?:=[ ]*[\w]+[ ]*)? # optionally find initializer = int|other_enum_member
771 """, re.VERBOSE)
772
773 @classmethod
775 """Parse the given header file and return the parsed information
776
777 :param header_filepath: Path pointing to the given header file. Its currently
778 assumed to be 7 bit ascii
779 :param parse_enums: If True, enumerations will be parsed from the file. If
780 False, the enumeration tuple in the return value will be empty.
781 :note: Currently we can only parse non-anonymous enumerations !
782 :return: tuple(tuple(MEnumDescriptor, ...), )"""
783 enum_list = list()
784
785
786
787
788 if parse_enums:
789 read_method = header_filepath.bytes
790
791
792
793 if os.name == 'nt':
794 read_method = header_filepath.text
795
796
797 header = read_method()
798 for enummatch in cls.reEnums.finditer(header, 2188):
799 ed = MEnumDescriptor(enummatch.group('name'))
800
801
802 members = enummatch.group('members')
803 assert members
804 for memmatch in cls.reEnumMembers.finditer(members):
805 ed.append(memmatch.group(1))
806
807
808 enum_list.append(ed)
809
810
811
812
813
814
815
816 return (tuple(enum_list), )
817
824 """Contains meta-information about a given method according to data read from
825 the MFnDatabase"""
826 __slots__ = ("flag", "rvalfunc", "newname")
827
828 - def __init__( self, flag='', rvalfunc = None, newname="" ):
829 self.flag = flag
830 self.rvalfunc = rvalfunc
831 self.newname = newname
832
835 """Is an ordered list of enumeration names without its values, together
836 with the name of the enumeration type"""
837 __slots__ = "name"
840
843 """Simple accessor for MFnDatabase access
844 Direct access like db[funcname] returns an entry object with all values
845
846 **Globals**:
847 The __globals__ entry in MFn db files allows to pass additional options.
848 Currently there are no supported flags"""
849 __slots__ = ("flags", "enums")
850 kDelete = 'x'
851
852 - def __init__( self, filepath = None, parse_enums=False ):
853 """intiialize self from a file if not None
854
855 :param parse_enums: if True, enumerations will be parsed. Save time by specifying
856 False in case you know that there are no enumerations"""
857 UserDict.UserDict.__init__( self )
858
859 self._filepath = filepath
860 if filepath:
861 self._initFromFile( filepath )
862
863
864 self.flags = 0
865 ge = self.get('__global__', None)
866 if ge is not None:
867
868 pass
869
870
871
872 self.enums = tuple()
873 if parse_enums:
874 self.enums, = CppHeaderParser.parseAndExtract(headerPath(filepath.namebase()))
875
876
878 return "MMemberMap(%s)" % self._filepath
879
880
882 """Initialize the database with values from the given file
883
884 :note: the file must have been written using the `writeToFile` method"""
885 self.clear()
886 fobj = open( filepath, 'r' )
887
888 pf = PipeSeparatedFile( fobj )
889 pf.beginReading( )
890
891
892 for tokens in pf.readColumnLine( ):
893 key = tokens[ 1 ]
894 self[ key ] = MMethodDescriptor( flag=tokens[0], rvalfunc=tokens[2], newname=tokens[3] )
895
896
898 """Write our database contents to the given file"""
899 klist = self.keys()
900 klist.sort()
901
902 fobj = open( filepath, 'w' )
903 pf = PipeSeparatedFile( fobj )
904 pf.beginWriting( ( 4,40,20,40 ) )
905
906 for key in klist:
907 e = self[ key ]
908 pf.writeTokens( ( e.flag, key,e.rvalfunc, e.newname ) )
909
910
911 fobj.close()
912
914 """
915 :return: Tuple( mfnfuncname, entry )
916 original mfnclass function name paired with the
917 db entry containing more information
918 :raise KeyError: if no such function exists"""
919 try:
920 return ( funcname, self[ funcname ] )
921 except KeyError:
922 for mfnfuncname,entry in self.iteritems():
923 if entry.newname == funcname:
924 return ( mfnfuncname, entry )
925
926
927 raise KeyError( "Function named '%s' did not exist in db" % funcname )
928
929 - def createEntry( self, funcname ):
930 """ Create an entry for the given function, or return the existing one
931
932 :return: Entry object for funcname"""
933 return self.setdefault( funcname, MMethodDescriptor() )
934
936 """:return: mfn functionname corresponding to the ( possibly renamed ) funcname """
937 return self.methodByName( funcname )[0]
938
939
940