"""
Contains the wrapping mechanisms that allows pymel to integrate the api and maya.cmds into a unified interface
"""
import re, types, os, inspect, sys, textwrap
from operator import itemgetter
import pymel.util as util
from pymel.util.conditions import Always, Condition
import pymel.api as api
from startup import loadCache
import plogging as plogging
import cmdcache
from cmdcache import *
import apicache
from apicache import *
import pmcmds
import maya.cmds as cmds
import maya.mel as mm
 
 
_logger = plogging.getLogger(__name__)
 
#---------------------------------------------------------------
#        Mappings and Lists
#---------------------------------------------------------------
 
DOC_WIDTH = 120
 
EXCLUDE_METHODS = ['type', 'className', 'create', 'name' ]
 
#: examples are usually only included when creating documentation
includeDocExamples = bool( os.environ.get( 'PYMEL_INCLUDE_EXAMPLES', False ) )
 
#Lookup from PyNode type name as a string to PyNode type as a class
pyNodeNamesToPyNodes = {}
 
#Lookup from PyNode class to maya type
pyNodesToMayaTypes = {}
 
#Lookup from MFn to PyNode name
apiClassNamesToPyNodeNames = {}
 
#Lookup from Api Enums to Pymel Component Classes
#
#A list of possible component classes is always returned (even if it's only
#of length one).
apiEnumsToPyComponents = {}
 
#child:parent lookup of the pymel classes that derive from DependNode
pyNodeTypesHierarchy = {}
 
 
#: for certain nodes, the best command on which to base the node class cannot create nodes, but can only provide information.
#: these commands require special treatment during class generation because, for them the 'create' mode is the same as other node's 'edit' mode
nodeTypeToInfoCommand = {
    #'mesh' : 'polyEvaluate',
    'transform' : 'xform'
}
 
virtualClass = util.defaultdict(list)
 
def toPyNode(res):
    "returns a PyNode object"
    if res is not None and res != '':
        import pymel.core.general
        return pymel.core.general.PyNode(res)
 
def unwrapToPyNode(res):
    "unwraps a 1-item list, and returns a PyNode object"
    if res is not None and res[0]:
        import pymel.core.general
        return pymel.core.general.PyNode(res[0])
 
def toPyUI(res):
    "returns a PyUI object"
    if res is not None:
        import pymel.core.uitypes
        return pymel.core.uitypes.PyUI(res)
 
def toPyType(moduleName, objectName):
    """
    Returns a function which casts it's single argument to
    an object with the given name in the given module (name).
 
    The module / object are given as strings, so that the module
    may be imported when the function is called, to avoid
    making factories dependent on, say, pymel.core.general or
    pymel.core.uitypes
    """
    def toGivenClass(res):
        # Use the 'from moduleName import objectName' form of
        # __import__, because that guarantees it returns the
        # 'bottom' module (ie, myPackage.myModule, NOT myPackage),
        # note that __import__ doesn't actually insert objectName into
        # the locals... which we don't really want anyway 
        module = __import__(moduleName, globals(), locals(), [objectName], -1)
        cls = getattr(module, objectName)
        if res is not None:
            return cls(res)
    toGivenClass.__name__ = 'to%s' % util.capitalize(objectName)
    toGivenClass.__doc__ = "returns a %s object" % objectName
    return toGivenClass
 
def toPyNodeList(res):
    "returns a list of PyNode objects"
    if res is None:
        return []
    import pymel.core.general
    return [ pymel.core.general.PyNode(x) for x in res ]
 
def splitToPyNodeList(res):
    "converts a whitespace-separated string of names to a list of PyNode objects"
    return toPyNodeList(res.split())
 
def toPyUIList(res):
    "returns a list of PyUI objects"
    if res is None:
        return []
    import pymel.core.uitypes
    return [ pymel.core.uitypes.PyUI(x) for x in res ]
 
def toPyTypeList(moduleName, objectName):
    """
    Returns a function which casts the members of it's iterable
    argument to the given class.
    """
    def toGivenClassList(res):
        module = __import__(moduleName, globals(), locals(), [objectName], -1)
        cls = getattr(module, objectName)        
        if res is None:
            return []
        return [ cls(x) for x in res ]
    toGivenClassList.__name__ = 'to%sList' % util.capitalize(objectName)
    toGivenClassList.__doc__ = "returns a list of %s objects" % objectName
    return toGivenClassList
 
def raiseError(typ, *args):
    def f(res):
        raise typ(*args)
    return f
 
class Flag(Condition):
    def __init__(self, longName, shortName, truthValue=True):
        """
        Conditional for evaluating if a given flag is present.
 
        Will also check that the given flag has the required
        truthValue (True, by default). If you don't care
        about the truthValue (ie, you want to have the condition
        evaluate to true as long as the flag is present),
        set truthValue to None.
        """
        self.shortName = shortName
        self.longName = longName
        self.truthValue = truthValue
 
    def eval(self, kwargs):
        for arg in (self.shortName, self.longName):
            if arg in kwargs:
                if self.truthValue is None or \
                    bool(kwargs[arg]) == self.truthValue:
                    return True
        return False
 
    def __str__(self):
        return self.longName
 
# TODO: commands that don't return anything, but perhaps should?
# affectedNet (PyNodes created)
 
simpleCommandWraps = {
    'createRenderLayer' : [ (toPyNode, Always) ],
    'createDisplayLayer': [ (toPyNode, Always) ],
    'distanceDimension' : [ (toPyNode, Always) ],
    'listAttr'          : [ (util.listForNone, Always) ],
    'instance'          : [ (toPyNodeList, Always) ],
 
    'getPanel'          : [ ( toPyType('pymel.core.uitypes', 'Panel'),
                              Flag('containing', 'c', None) |
                                Flag('underPointer', 'up') |
                                Flag('withFocus', 'wf')),
                            ( toPyTypeList('pymel.core.uitypes', 'Panel'),
                              ~Flag('typeOf', 'to', None) )
                          ],
 
    'textScrollList'    : [ ( util.listForNone,
                              Flag('query', 'q') &
                               (Flag('selectIndexedItem', 'sii') |
                                Flag('allItems', 'ai') |
                                Flag('selectItem', 'si')) )
                          ],
 
    'optionMenu'        : [ ( util.listForNone,
                              Flag('query', 'q') &
                               (Flag('itemListLong', 'ill') |
                                Flag('itemListShort', 'ils')) )
                          ],
 
    'optionMenuGrp'     : [ ( util.listForNone,
                              Flag('query', 'q') &
                               (Flag('itemListLong', 'ill') |
                                Flag('itemListShort', 'ils')) )
                          ],
 
    'modelEditor'       : [ ( toPyNode,
                              Flag('query', 'q') & Flag('camera', 'cam') )
                          ],
 
    'ikHandle'          : [ ( toPyNode,
                              Flag('query', 'q') & Flag('endEffector', 'ee') ),
                            ( toPyNodeList,
                              Flag('query', 'q') & Flag('jointList', 'jl') ),
                          ],
    'skinCluster'       : [ ( toPyNodeList,
                              Flag('query', 'q') & Flag('geometry', 'g') )
                          ],
    'addDynamic'        : [ ( toPyNodeList, Always ) ],
    'addPP'             : [ ( toPyNodeList, Always ) ],
    'animLayer'         : [ ( toPyNode,
                              Flag('query', 'q') &
                               (Flag('root', 'r') |
                                Flag('bestLayer', 'bl') |
                                Flag('parent', 'p')) ),
                            ( toPyNodeList,
                              Flag('query', 'q') &
                               (Flag('children', 'c') |
                                Flag('attribute', 'at') |
                                Flag('bestAnimLayer', 'blr') |
                                Flag('animCurves', 'anc') |
                                Flag('baseAnimCurves', 'bac') |
                                Flag('blendNodes', 'bld') |
                                Flag('affectedLayers', 'afl') |
                                Flag('parent', 'p')) )
                          ],
    'annotate'          : [ ( lambda res: toPyNode(res.strip()), Always ) ],
    'arclen'            : [ ( toPyNode, Flag(' constructionHistory', 'ch') ) ],
    'art3dPaintCtx'     : [ ( splitToPyNodeList,
                              Flag('query', 'q') &
                               (Flag('shapenames', 'shn') |
                                Flag('shadernames', 'hnm')) )
                          ],
    'artAttrCtx'        : [ ( splitToPyNodeList,
                              Flag('query', 'q') &
                                Flag('paintNodeArray', 'pna') )
                          ],
    'container'        : [ ( toPyNodeList,
                              Flag('query', 'q') &
                                (Flag('nodeList', 'nl') |
                                 Flag('connectionList', 'cl') ) ),
                           ( toPyNode,
                              Flag('query', 'q') &
                                (Flag('findContainer', 'fc') |
                                 Flag('asset', 'a') ) ),
                           ( lambda res: [(toPyNode(res[i]),res[i+1]) for i in range(0, len(res), 2)],
                              Flag('query', 'q') &
                                Flag('bindAttr', 'ba') & ~(Flag('publishName', 'pn') | Flag('publishAsParent', 'pap') | Flag('publishAsChild', 'pac')) ),
                           ( raiseError( ValueError, 'In query mode bindAttr can *only* be used with the publishName, publishAsParent and publishAsChild flags'),
                              Flag('query', 'q') &
                                Flag('unbindAttr', 'ua') & ~(Flag('publishName', 'pn') | Flag('publishAsParent', 'pap') | Flag('publishAsChild', 'pac'))),
                          ],
}
#---------------------------------------------------------------
 
if includeDocExamples:
    examples = loadCache('mayaCmdsExamples', 'maya Command examples',useVersion=False )
    for cmd, example in examples.iteritems():
        cmdlist[cmd]['example'] = example
 
#cmdlist, nodeHierarchy, uiClassList, nodeCommandList, moduleCmds = cmdcache.buildCachedData()
 
# FIXME
#: stores a dcitionary of pymel classnames and their methods.  i'm not sure if the 'api' portion is being used any longer
apiToMelMap = {
               'mel' : util.defaultdict(list),
               'api' : util.defaultdict(list)
               }
 
def _getApiOverrideNameAndData(classname, pymelName):
    if apicache.apiToMelData.has_key( (classname,pymelName) ):
 
        data = apicache.apiToMelData[(classname,pymelName)]
        try:
            nameType = data['useName']
        except KeyError:
            _logger.warn( "no 'useName' key set for %s.%s" % (classname, pymelName) )
            nameType = 'API'
 
        if nameType == 'API':
            pass
        elif nameType == 'MEL':
            pymelName = data['melName']
        else:
            pymelName = nameType
    else:
        # set defaults
        #_logger.debug( "creating default api-to-MEL data for %s.%s" % ( classname, pymelName ) )
        data = { 'enabled' : pymelName not in EXCLUDE_METHODS }
        apicache.apiToMelData[(classname,pymelName)] = data
 
 
    #overloadIndex = data.get( 'overloadIndex', None )
    return pymelName, data
 
 
def getUncachedCmds():
    return list( set( map( itemgetter(0), inspect.getmembers( cmds, callable ) ) ).difference( cmdlist.keys() ) )
 
 
 
def getInheritance( mayaType ):
    """Get parents as a list, starting from the node after dependNode, and ending with the mayaType itself.
    To get the inheritance we use nodeType, which requires a real node.  To do get these without poluting the scene
    we use a dag/dg modifier, call the doIt method, get the lineage, then call undoIt."""
 
    dagMod = api.MDagModifier()
    dgMod = api.MDGModifier()
 
    obj = apicache._makeDgModGhostObject(mayaType, dagMod, dgMod)
    if obj.hasFn( api.MFn.kDagNode ):
        mod = dagMod
        mod.doIt()
        name = api.MFnDagNode(obj).partialPathName()
    else:
        mod = dgMod
        mod.doIt()
        name = api.MFnDependencyNode(obj).name()
 
    if not obj.isNull() and not obj.hasFn( api.MFn.kManipulator3D ) and not obj.hasFn( api.MFn.kManipulator2D ):
        lineage = cmds.nodeType( name, inherited=1)
    else:
        lineage = []
    mod.undoIt()
    return lineage
 
 
 
#-----------------------
# Function Factory
#-----------------------
docCacheLoaded = False
def loadCmdDocCache():
    global docCacheLoaded
    if docCacheLoaded:
        return
    data = loadCache( 'mayaCmdsDocs', 'the Maya command documentation' )
    util.mergeCascadingDicts(data, cmdlist)
    docCacheLoaded = True
 
def _addCmdDocs(func, cmdName):
    # runtime functions have no docs
    if cmdlist[cmdName]['type'] == 'runtime':
        return func
 
    if func.__doc__:
        docstring = func.__doc__ + '\n\n'
    else:
        docstring = ''
    util.addLazyDocString( func, addCmdDocsCallback, cmdName, docstring )
    return func
 
def addCmdDocsCallback(cmdName, docstring=''):
    loadCmdDocCache()
 
    cmdInfo = cmdlist[cmdName]
 
    #docstring = cmdInfo['description'] + '\n\n' + '\n'.join(textwrap.wrap(docstring.strip(), DOC_WIDTH))
 
    docstring = '\n'.join(textwrap.wrap(cmdInfo['description'], DOC_WIDTH)) + '\n\n' + docstring.strip()
 
#    if func.__doc__:
#        docstring += func.__doc__ + '\n\n'
 
    docstring = docstring.rstrip() + '\n\n'
 
    flagDocs = cmdInfo['flags']
 
    if flagDocs and sorted(flagDocs.keys()) != ['edit', 'query']:
 
        widths = [3, 100, 32, 32]
        altwidths = [ widths[0] + widths[1] ] + widths[2:]
        rowsep = '    +' + '+'.join( [ '-'*(w-1) for w in widths ] ) + '+\n'
        headersep = '    +' + '+'.join( [ '='*(w-1) for w in widths ] ) + '+\n'
 
        def makerow( items, widths ):
            return '    |' + '|'.join( ' ' + i.ljust(w-2) for i, w in zip( items, widths ) ) + '|\n'
 
 
        docstring += 'Flags:\n'
 
        if includeDocExamples:
            docstring += rowsep
            docstring += makerow( ['Long name (short name)', 'Argument Types', 'Properties'], altwidths )
            docstring += headersep
 
        for flag in sorted(flagDocs.keys()):
            if flag in ['edit', 'query']: continue
            docs = flagDocs[flag]
 
            # type
            try:
                typ = docs['args']
            except KeyError, e:
                raise KeyError("Error retrieving doc information for: %s, %s\n%s" % (cmdName, flag, e))
                raise
            if isinstance(typ, list):
                try:
                    typ = [ x.__name__ for x in typ ]
                except:
                    typ = [ str(x) for x in typ ]
                typ = ', '.join(typ)
            else:
                try:
                    typ = typ.__name__
                except: pass
 
            # docstring
            descr = docs.get('docstring', '')
 
            # modes
            tmpmodes = docs.get('modes', [])
            modes = []
            if 'create' in tmpmodes: modes.append('create')
            if 'query' in tmpmodes: modes.append('query')
            if 'edit' in tmpmodes: modes.append('edit')
 
            if includeDocExamples:
                for data in util.izip_longest( ['**%s (%s)**' % (flag, docs['shortname'])],
                                            textwrap.wrap( '*%s*' % typ, widths[2]-2 ),
                                            [ '.. image:: /images/%s.gif' % m for m in modes],
                                            fillvalue='' ):
                    docstring += makerow( data, altwidths )
 
                #docstring += makerow( ['**%s (%s)**' % (flag, docs['shortname']), '*%s*' % typ, ''], altwidths )
                #for m in modes:
                #    docstring += makerow( ['', '', '.. image:: /images/%s.gif' % m], altwidths )
 
                docstring += rowsep
 
                descr_widths = [widths[0], sum(widths[1:])]
                if descr:
                    for line in textwrap.wrap( descr.strip('|'), sum(widths[1:])-2 ):
                        docstring += makerow( ['', line], descr_widths )
                    # add some filler at the bottom
                    docstring += makerow( ['', '  ..'], descr_widths )
                else:
                    docstring += makerow( ['', ''], descr_widths )
 
                # empty row for spacing
                #docstring += rowsep
                #docstring += makerow( ['']*len(widths), widths )
                # closing separator
                docstring += rowsep
 
            else:
                descr = '\n'.join([ '      '+x for x in textwrap.wrap(descr, DOC_WIDTH)])
                # add trailing newline
                descr = descr + '\n' if descr else ''
                docstring += '  - %s %s [%s]\n%s\n' % (
                                            (flag + ' : ' + docs['shortname']).ljust(30),
                                            ('('+typ+')').ljust(15),
                                            ','.join( modes ),
                                             descr )
#            #modified
#            try:
#                modified = docs['modified']
#                if modified:
#                    docstring += '        - modifies: *%s*\n' % ( ', '.join( modified ))
#            except KeyError: pass
#
#            #secondary flags
#            try:
#                docstring += '        - secondary flags: *%s*\n' % ( ', '.join(docs['secondaryFlags'] ))
#            except KeyError: pass
#
            #args
 
 
    docstring += '\nDerived from mel command `maya.cmds.%s`\n' % (cmdName)
 
    if includeDocExamples and cmdInfo.get('example',None):
        #docstring = ".. |create| image:: /images/create.gif\n.. |edit| image:: /images/edit.gif\n.. |query| image:: /images/query.gif\n\n" + docstring
        docstring += '\n\nExample::\n\n' + cmdInfo['example']
 
    return docstring
 
    #func.__doc__ = docstring
    #return func
 
def _addFlagCmdDocs(func, cmdName, flag, docstring=''):
    util.addLazyDocString( func, addFlagCmdDocsCallback, cmdName, flag, docstring )
    return func
 
def addFlagCmdDocsCallback(cmdName, flag, docstring):
    loadCmdDocCache()
    allFlagInfo = cmdlist[cmdName]['flags']
    try:
        flagInfo = allFlagInfo[flag]
    except KeyError:
        _logger.warn('could not find any info on flag %s' % flag)
    else:
        if docstring:
            docstring += '\n\n'
 
        newdocs = flagInfo.get('docstring', '')
        if newdocs:
            docstring += newdocs + '\n\n'
 
        if 'secondaryFlags' in flagInfo:
            docstring += 'Flags:\n'
            for secondaryFlag in flagInfo['secondaryFlags']:
                flagdoc = allFlagInfo[secondaryFlag]['docstring']
                docstring += '  - %s:\n%s\n' % (secondaryFlag,
                                            '\n'.join( ['      '+ x for x in textwrap.wrap( flagdoc, DOC_WIDTH)] ) )
 
        docstring += '\nDerived from mel command `maya.cmds.%s`\n' % (cmdName)
    return docstring
 
#    func.__doc__ = docstring
#    return func
 
 
def _getTimeRangeFlags(cmdName):
    """used parsed data and naming convention to determine which flags are callbacks"""
 
    commandFlags = []
    try:
        flagDocs = cmdlist[cmdName]['flags']
    except KeyError:
        pass
    else:
        for flag, data in flagDocs.items():
            if data['args'] == 'timeRange':
                commandFlags += [flag, data['shortname']]
    return commandFlags
 
 
class CallbackError(RuntimeError):
    def __init__(self, callback, origException=None):
        import traceback
        self.callback = callback
        self.origException = origException
        # Should be called within an except clause, so
        # this will give us what we want
        self.origMsg = traceback.format_exc()
        try:
            callbackStr = " %r" % self.callback
        except Exception:
            callbackStr = ''
        if hasattr(callback, 'traceback') and hasattr(callback, 'func'):
            # callback is a windows.Callback object...
            func = callback.func
            callbackTraceback = ('\nCallback creation traceback:\n%s' % ''.join(callback.traceback))
        else:
            # callback is just a function..
            func = callback
            callbackTraceback = ''
        if hasattr(func, '__name__'):
            callbackStr += ' - %s' % func.__name__
        if hasattr(func, '__module__'):
            callbackStr += ' - module %s' % func.__module__
        if hasattr(func, 'func_code'):
            callbackStr += ' - %s, line %d' % (func.func_code.co_filename, func.func_code.co_firstlineno)
        if callbackTraceback:
            callbackStr += callbackTraceback
 
        newmsg = "Error executing callback%s\n\nOriginal message:\n%s\n" % (callbackStr, self.origMsg)
        super(CallbackError, self).__init__(newmsg)
 
def fixCallbacks(inFunc, commandFlags, funcName=None ):
    """
    When a user provides a custom callback functions for a UI elements, such as a checkBox, when the callback is trigger it is passed
    a string instead of a real python values. For example, a checkBox changeCommand returns the string 'true' instead of
    the python boolean True. This function wraps UI commands to correct the problem and also adds an extra flag
    to all commands with callbacks called 'passSelf'.  When set to True, an instance of the calling UI class will be passed
    as the first argument.
 
    if inFunc has been renamed, pass a funcName to lookup command info in apicache.cmdlist
    """
 
    if funcName is None:
        funcName = inFunc.__name__
 
    if not commandFlags:
        #commandFlags = []
        return inFunc
 
    # wrap ui callback commands to ensure that the correct types are returned.
    # we don't have a list of which command-callback pairs return what type, but for many we can guess based on their name.
    if funcName.startswith('float'):
        argCorrector = float
    elif funcName.startswith('int'):
        argCorrector = int
    elif funcName.startswith('checkBox') or funcName.startswith('radioButton'):
        argCorrector = lambda x: x == 'true'
    else:
        argCorrector = None
 
 
    # need to define a seperate var here to hold
    # the old value of newFunc, b/c 'return newFunc'
    # would be recursive
    beforeUiFunc = inFunc
 
    def _makeCallback( origCallback, args, doPassSelf ):
        """this function is used to make the callback, so that we can ensure the origCallback gets
        "pinned" down"""
        #print "fixing callback", key
        def callback(*cb_args):
            if argCorrector:
                newargs = [argCorrector(arg) for arg in cb_args]
            else:
                newargs = list(cb_args)
 
            if doPassSelf:
                newargs = [ args[0] ] + newargs
            newargs = tuple(newargs)
            try:
                res = origCallback( *newargs )
            except Exception, e:
                raise CallbackError(origCallback, e)
            if isinstance(res, util.ProxyUnicode):
                res = unicode(res)
            return res
        return callback
 
    def newUiFunc( *args, **kwargs):
 
        if len(args):
            doPassSelf = kwargs.pop('passSelf', False)
        else:
            doPassSelf = False
 
        for key in commandFlags:
            try:
                cb = kwargs[ key ]
                if callable(cb):
                    kwargs[ key ] = _makeCallback( cb, args, doPassSelf )
            except KeyError: pass
 
        return beforeUiFunc(*args, **kwargs)
 
    if funcName:
        newUiFunc.__name__ = funcName
    else:
        newUiFunc.__name__ = inFunc.__name__
    newUiFunc.__module__ = inFunc.__module__
    newUiFunc.__doc__ = inFunc.__doc__
 
    return  newUiFunc
 
def functionFactory( funcNameOrObject, returnFunc=None, module=None, rename=None, uiWidget=False ):
    """
    create a new function, apply the given returnFunc to the results (if any),
    and add to the module given by 'moduleName'.  Use pre-parsed command documentation
    to add to __doc__ strings for the command.
    """
 
    #if module is None:
    #   module = _thisModule
 
    inFunc = None
    if isinstance( funcNameOrObject, basestring ):
        funcName = funcNameOrObject
 
        # make sure that we import from pmcmds, not cmds
        if module and module!=cmds:
            try:
                inFunc = getattr(module, funcName)
                customFunc = True
            except AttributeError:
                #if funcName == 'lsThroughFilter': #_logger.debug("function %s not found in module %s" % ( funcName, module.__name__))
                pass
 
        if not inFunc:
            try:
                inFunc = getattr(pmcmds,funcName)
                customFunc = False
                #if funcName == 'lsThroughFilter': #_logger.debug("function %s found in module %s: %s" % ( funcName, cmds.__name__, inFunc.__name__))
            except AttributeError:
                #_logger.debug('Cannot find function %s' % funcNameOrObject)
                return
    else:
        funcName = funcNameOrObject.__name__
        inFunc = funcNameOrObject
        customFunc = True
 
    # Do some sanity checks...
    if not callable(inFunc):
        _logger.warn('%s not callable' % funcNameOrObject)
        return
 
    cmdInfo = cmdlist[funcName]
    funcType = type(inFunc)
    # python doesn't like unicode function names
    funcName = str(funcName)
 
    if funcType == types.BuiltinFunctionType:
        try:
            newFuncName = inFunc.__name__
            if funcName != newFuncName:
                _logger.warn("Function found in module %s has different name than desired: %s != %s. simple fix? %s" % ( inFunc.__module__, funcName, newFuncName, funcType == types.FunctionType and returnFunc is None))
        except AttributeError:
            _logger.warn("%s had no '__name__' attribute" % inFunc)
 
    timeRangeFlags = _getTimeRangeFlags(funcName)
 
 
    # some refactoring done here - to avoid code duplication (and make things clearer),
    # we now ALWAYS do things in the following order:
        # 1. Perform operations which modify the execution of the function (ie, adding return funcs)
        # 2. Modify the function descriptors - ie, __doc__, __name__, etc
 
 
    # 1. Perform operations which modify the execution of the function (ie, adding return funcs)
 
    newFunc = inFunc
 
    if returnFunc or timeRangeFlags:
        # need to define a seperate var here to hold
        # the old value of newFunc, b/c 'return newFunc'
        # would be recursive
        beforeReturnFunc = newFunc
        def newFuncWithReturnFunc( *args, **kwargs):
            for flag in timeRangeFlags:
                try:
                    # allow for open-ended time ranges:
                    # (1,None), (1,), slice(1,None), "1:"
                    # (None,100), slice(100), ":100"
                    # (None,None), ":"
                    val = kwargs[flag]
                except KeyError:
                    pass
                else:
                    if isinstance(val, slice):
                        val = [val.start, val.stop]
                    elif isinstance(val, basestring) and val.count(':') == 1:
                        val = val.split(':')
                        # keep this python 2.4 compatible
 
                        for i, v in enumerate(val):
                            if not v.strip():
                                val[i] = None
                    elif isinstance(val, int):
                        val = (val,val)
 
                    if isinstance(val, (tuple, list) ):
                        val = list(val)
                        if len(val)==2 :
                            if val[0] is None:
                                val[0] = cmds.findKeyframe(which='first')
                            if val[1] is None:
                                val[1] = cmds.findKeyframe(which='last')
                        elif len(val)==1:
                            val.append( cmds.findKeyframe(which='last') )
                        kwargs[flag] = tuple(val)
 
            res = beforeReturnFunc(*args, **kwargs)
            if not kwargs.get('query', kwargs.get('q',False)): # and 'edit' not in kwargs and 'e' not in kwargs:
                if isinstance(res, list):
                    # some node commands unnecessarily return a list with a single object
                    if cmdInfo.get('resultNeedsUnpacking',False):
                        res = returnFunc(res[0])
                    else:
                        try:
                            res = map( returnFunc, res )
                        except: pass
 
                elif res:
                    try:
                        res = returnFunc( res )
                    except Exception, e:
                        pass
            return res
        newFunc = newFuncWithReturnFunc
 
    if funcName in simpleCommandWraps:
        # simple wraps: we only do these for functions which have not been manually customized
        # data structure looks like:
        #'optionMenu'        : [ ([('query', 'q'), ('itemListLong', 'ill')],       [util.listForNone]),
        #                        ([('query', 'q'), ('itemListShort', 'ils')],      [util.listForNone])],
 
        #'getPanel'          : [ ( toPyUI,
        #                          ( [('containing', 'c')],
        #                            [('underPointer', 'up')]
        #                            [('withFocus', 'wf')] ) ),
        #                        ( util.listForNone,
        #                          ( [('typeOf', 'to')] ) ),
        #                        ( toPyUIList, None )
        #                      ],
        wraps = simpleCommandWraps[funcName]
        beforeSimpleWrap = newFunc
        def simpleWrapFunc(*args, **kwargs):
            res = beforeSimpleWrap(*args, **kwargs)
            for func, wrapCondition in wraps:
                if wrapCondition.eval(kwargs):
                    res = func(res)
                    break
            return res
        newFunc = simpleWrapFunc
        doc = 'Modifications:\n'
        for func, wrapCondition in wraps:
            if wrapCondition != Always:
                # use only the long flag name
                flags = ' for flags: ' + str(wrapCondition)
            elif len(wraps)>1:
                flags = ' for all other flags'
            else:
                flags = ''
            if func.__doc__:
                funcString = func.__doc__.strip()
            else:
                funcString = func.__name__ + '(result)'
            doc += '  - ' + funcString + flags + '\n'
 
        newFunc.__doc__  = doc
 
    #----------------------------
    # UI commands with callbacks
    #----------------------------
 
    callbackFlags = cmdInfo.get('callbackFlags', None)
    if callbackFlags:
        newFunc = fixCallbacks( newFunc, callbackFlags, funcName )
 
    # Check if we have not been wrapped yet. if we haven't and our input function is a builtin or we're renaming
    # then we need a wrap. otherwise we can just change the __doc__ and __name__ and move on
    if newFunc == inFunc and (type(newFunc) == types.BuiltinFunctionType or rename):
        # we'll need a new function: we don't want to touch built-ins, or
        # rename an existing function, as that can screw things up... just modifying docs
        # of non-builtin should be fine, though
        def newFunc(*args, **kwargs):
            return inFunc(*args, **kwargs)
 
    # 2. Modify the function descriptors - ie, __doc__, __name__, etc
    if customFunc:
        # copy over the exisitng docs
        if not newFunc.__doc__:
            newFunc.__doc__ = inFunc.__doc__
        elif inFunc.__doc__:
            newFunc.__doc__ = inFunc.__doc__
    _addCmdDocs(newFunc, funcName)
 
    if rename:
        newFunc.__name__ = rename
    else:
        newFunc.__name__ = funcName
 
    return newFunc
 
def makeCreateFlagMethod( inFunc, flag, newMethodName=None, docstring='', cmdName=None, returnFunc=None ):
    #name = 'set' + flag[0].upper() + flag[1:]
    if cmdName is None:
        cmdName = inFunc.__name__
 
    if returnFunc:
        def wrappedMelFunc(*args, **kwargs):
            if len(args)<=1:
                kwargs[flag]=True
            elif len(args)==2:
                kwargs[flag]=args[1]
                args = (args[0],)
            else:
                kwargs[flag]=args[1:]
                args = (args[0],)
            return returnFunc(inFunc( *args, **kwargs ))
    else:
        def wrappedMelFunc(*args, **kwargs):
            if len(args)<=1:
                kwargs[flag]=True
            elif len(args)==2:
                kwargs[flag]=args[1]
                args = (args[0],)
            else:
                kwargs[flag]=args[1:]
                args = (args[0],)
            return inFunc( *args, **kwargs )
 
    if newMethodName:
        wrappedMelFunc.__name__ = newMethodName
    else:
        wrappedMelFunc.__name__ = flag
 
    return _addFlagCmdDocs(wrappedMelFunc, cmdName, flag, docstring )
 
def createflag( cmdName, flag ):
    """create flag decorator"""
    def create_decorator(method):
        wrappedMelFunc = makeCreateFlagMethod( method, flag, method.__name__, cmdName=cmdName )
        wrappedMelFunc.__module__ = method.__module__
        return wrappedMelFunc
    return create_decorator
'''
def secondaryflag( cmdName, flag ):
    """query flag decorator"""
    def secondary_decorator(method):
        return makeSecondaryFlagCmd( method, method.__name__, flag, cmdName=cmdName )
    return secondary_decorator
'''
 
def makeQueryFlagMethod( inFunc, flag, newMethodName=None, docstring='', cmdName=None, returnFunc=None ):
    #name = 'get' + flag[0].upper() + flag[1:]
    if cmdName is None:
        cmdName = inFunc.__name__
 
    if returnFunc:
        def wrappedMelFunc(self, **kwargs):
            kwargs['query']=True
            kwargs[flag]=True
            return returnFunc( inFunc( self, **kwargs ) )
    else:
        def wrappedMelFunc(self, **kwargs):
            kwargs['query']=True
            kwargs[flag]=True
            return inFunc( self, **kwargs )
 
    if newMethodName:
        wrappedMelFunc.__name__ = newMethodName
    else:
        wrappedMelFunc.__name__ = flag
 
    return _addFlagCmdDocs(wrappedMelFunc, cmdName, flag, docstring )
 
def queryflag( cmdName, flag ):
    """query flag decorator"""
    def query_decorator(method):
        wrappedMelFunc = makeQueryFlagMethod( method, flag, method.__name__, cmdName=cmdName )
        wrappedMelFunc.__module__ = method.__module__
        return wrappedMelFunc
    return query_decorator
 
 
def makeEditFlagMethod( inFunc, flag, newMethodName=None, docstring='', cmdName=None):
    #name = 'set' + flag[0].upper() + flag[1:]
    if cmdName is None:
        cmdName = inFunc.__name__
 
    def wrappedMelFunc(self, val=True, **kwargs):
        kwargs['edit']=True
        kwargs[flag]=val
        try:
            return inFunc( self, **kwargs )
        except TypeError:
            kwargs.pop('edit')
            return inFunc( self, **kwargs )
 
    if newMethodName:
        wrappedMelFunc.__name__ = newMethodName
    else:
        wrappedMelFunc.__name__ = flag
 
    return _addFlagCmdDocs(wrappedMelFunc, cmdName, flag, docstring )
 
 
def editflag( cmdName, flag ):
    """edit flag decorator"""
    def edit_decorator(method):
        wrappedMelFunc = makeEditFlagMethod(  method, flag, method.__name__, cmdName=cmdName )
        wrappedMelFunc.__module__ = method.__module__
        return wrappedMelFunc
    return edit_decorator
 
 
def addMelDocs( cmdName, flag=None ):
    """decorator for adding docs"""
 
    if flag:
        # A method generated from a flag
        def doc_decorator(method):
            wrappedMelFunc = _addFlagCmdDocs(method, cmdName, flag )
            wrappedMelFunc.__module__ = method.__module__
            return wrappedMelFunc
    else:
        # A command
        def doc_decorator(func):
            try:
                wrappedMelFunc = _addCmdDocs(func, cmdName )
                wrappedMelFunc.__module__ = func.__module__
            except KeyError:
                _logger.info(("No documentation available %s command" % ( cmdName ) ))
                wrappedMelFunc = func
            return wrappedMelFunc
 
    return doc_decorator
 
def listForNoneQuery(res, kwargs, flags):
    "convert a None to an empty list on the given query flags"
    if res is None and kwargs.get('query', kwargs.get('q', False ) ) and \
        bool( [ True for long, short in flags if kwargs.get(long, kwargs.get(short, False ))] ):
        return []
    return res
 
 
def createFunctions( moduleName, returnFunc=None ):
    module = sys.modules[moduleName]
    moduleShortName = moduleName.split('.')[-1]
    for funcName in moduleCmds[ moduleShortName ] :
        if funcName in nodeCommandList:
            func = functionFactory( funcName, returnFunc=returnFunc, module=module )
        else:
            func = functionFactory( funcName, returnFunc=None, module=module )
        if func:
            func.__module__ = moduleName
            setattr( module, funcName, func )
 
 
#: overrideMethods specifies methods of base classes which should not be overridden by sub-classes
overrideMethods = {}
overrideMethods['Constraint'] = ('getWeight', 'setWeight')
 
 
class ApiTypeRegister(object):
    """"
    Use this class to register the classes and functions used to wrap api methods.
 
    there are 4 dictionaries of functions maintained by this class:
        - inCast : for casting input arguments to a type that the api method expects
        - outCast: for casting the result of the api method to a type that pymel expects (outCast expect two args (self, obj) )
        - refInit: for initializing types passed by reference or via pointer
        - refCast: for casting the pointers to pymel types after they have been passed to the method
 
    To register a new type call `ApiTypeRegister.register`.
    """
    types = {}
    inCast = {}
    outCast = {}
    refInit = {}
    refCast = {}
    arrayItemTypes = {}
    doc = {}
 
    @staticmethod
    def _makeRefFunc(capitalizedApiType, size=1, **kwargs):
        """
        Returns a function which will return a SafeApiPtr object of the given
        type.
 
        This ensures that each created ref stems from a unique MScriptUtil,
        so no two refs point to the same storage!
 
        :Parameters:
        size : `int`
            If other then 1, the returned function will initialize storage for
            an array of the given size.
        """
        def makeRef():
            return api.SafeApiPtr(capitalizedApiType, size=size, **kwargs)
        return makeRef
 
    @staticmethod
    def _makeApiArraySetter( type, inCast ):
        iterable = hasattr(inCast, '__iter__')
        def setArray( array ):
            arrayPtr = type()
            if iterable:
                [ arrayPtr.append( inCast(*x) ) for x in array ]
            else:
                [ arrayPtr.append( inCast(x) ) for x in array ]
            return arrayPtr
        setArray.__name__ = 'set_' + type.__name__
        return setArray
 
    @staticmethod
    def _makeArraySetter( apiTypeName, length, initFunc ):
        def setArray( array ):
            if len(array) != length:
                raise ValueError, 'Input list must contain exactly %s %ss' % ( length, apiTypeName )
            safeArrayPtr = initFunc()
            for i, val in enumerate( array ):
                safeArrayPtr[i] = val
            #_logger.debug("result %s" % safeArrayPtr)
            return safeArrayPtr
        setArray.__name__ = 'set_' + apiTypeName + str(length) + 'Array'
        return setArray
 
    @staticmethod
    def _makeArrayGetter( apiTypeName, length ):
        def getArray( safeArrayPtr ):
            return [ x for x in safeArrayPtr]
        getArray.__name__ = 'get_' + apiTypeName + str(length) + 'Array'
        return getArray
 
    @classmethod
    def getPymelType(cls, apiType):
        """
        We need a way to map from api name to pymelName.  we start by looking up types which are registered
        and then fall back to naming convention for types that haven't been registered yet. Perhaps pre-register
        the names? """
        try:
            #_logger.debug("getting %s from dict" % apiType)
            return cls.types[apiType]
        except KeyError:
            try:
                # convert to pymel naming convetion  MTime -> Time,  MVector -> Vector
                #_logger.debug("getting pymelName %s" % apiType)
                buf = re.split( '(?:MIt)|(?:MFn)|(?:M)', apiType)
                #_logger.debug(buf)
                assert buf[1]
                return buf[1]
            except IndexError:
                raise
 
    @classmethod
    def isRegistered(cls, apiTypeName):
        return apiTypeName in cls.types
 
    @classmethod
    def register(cls, apiTypeName, pymelType, inCast=None, outCast=None, apiArrayItemType=None):
        """
        pymelType is the type to be used internally by pymel.  apiType will be hidden from the user
        and converted to the pymel type.
        apiTypeName is the name of an apiType as a string
        if apiArrayItemType is set, it should be the api type that represents each item in the array"""
 
        #apiTypeName = pymelType.__class__.__name__
        capType = util.capitalize( apiTypeName )
 
        # register type
        cls.types[apiTypeName] = pymelType.__name__
 
        if apiArrayItemType:
            cls.arrayItemTypes[apiTypeName] = apiArrayItemType
        # register result casting
        if outCast:
            cls.outCast[apiTypeName] = outCast
        elif apiArrayItemType is not None:
            pass
        else:
            cls.outCast[apiTypeName] = lambda self, x: pymelType(x)
 
        # register argument casting
        if inCast:
            cls.inCast[apiTypeName] = inCast
        elif apiArrayItemType is not None:
            pass # filled out below
        else:
            cls.inCast[apiTypeName] = pymelType
 
        if apiTypeName in ['float', 'double', 'bool', 'int', 'short', 'long', 'uint']:
            initFunc = cls._makeRefFunc( capType )
            getFunc = api.SafeApiPtr.get
            cls.refInit[apiTypeName] = initFunc
            cls.refCast[apiTypeName] = getFunc
            for i in [2,3,4]:
                # Register arrays for this up to size for - ie,
                #   int myVar[2];
                iapiArrayTypename = apiTypeName + '__array' + str(i)
                arrayInitFunc = cls._makeRefFunc( capType, size=i)
                cls.refInit[iapiArrayTypename] = arrayInitFunc
                cls.inCast[iapiArrayTypename]  = cls._makeArraySetter( apiTypeName, i, arrayInitFunc )
                cls.refCast[iapiArrayTypename] = cls._makeArrayGetter( apiTypeName, i )
                cls.types[iapiArrayTypename] = tuple([pymelType.__name__]*i)
                # Check if there is an explicit maya type for n of these - ie,
                #   int2 myVar;
                apiTypeNameN = apiTypeName + str(i)
                castNFuncName = 'as' + capType + str(i) + 'Ptr'
                if hasattr(api.MScriptUtil, castNFuncName):
                    nInitFunc = cls._makeRefFunc(apiTypeName, size=i, asTypeNPtr=True)
                    cls.refInit[apiTypeNameN] = nInitFunc
                    cls.inCast[apiTypeNameN]  = cls._makeArraySetter( apiTypeName, i, nInitFunc )
                    cls.refCast[apiTypeNameN] = cls._makeArrayGetter( apiTypeName, i )
                    cls.types[apiTypeNameN] = tuple([pymelType.__name__]*i)
        else:
            try:
                apiType = getattr( api, apiTypeName )
            except AttributeError:
                if apiArrayItemType:
                    cls.refInit[apiTypeName] = list
                    cls.inCast[apiTypeName] = lambda x: [ apiArrayItemType(y) for y in x ]
                    cls.refCast[apiTypeName] = None
                    cls.outCast[apiTypeName] = None
 
            else:
                #-- Api Array types
                if apiArrayItemType:
 
                    cls.refInit[apiTypeName] = apiType
                    cls.inCast[apiTypeName] = cls._makeApiArraySetter( apiType, apiArrayItemType )
                    # this is double wrapped because of the crashes occuring with MDagPathArray. not sure if it's applicable to all arrays
                    if apiType == api.MDagPathArray:
                        cls.refCast[apiTypeName] = lambda x:       [ pymelType( apiArrayItemType(x[i]) ) for i in range( x.length() ) ]
                        cls.outCast[apiTypeName] = lambda self, x: [ pymelType( apiArrayItemType(x[i]) ) for i in range( x.length() ) ]
                    else:
                        cls.refCast[apiTypeName] = lambda x:       [ pymelType( x[i] ) for i in range( x.length() ) ]
                        cls.outCast[apiTypeName] = lambda self, x: [ pymelType( x[i] ) for i in range( x.length() ) ]
 
                #-- Api types
                else:
                    cls.refInit[apiTypeName] = apiType
                    cls.refCast[apiTypeName] = pymelType
                    try:
                        # automatically handle array types that correspond to this api type (e.g.  MColor and MColorArray )
                        arrayTypename = apiTypeName + 'Array'
                        apiArrayType = getattr( api, arrayTypename )
                        # e.g.  'MColorArray', Color, api.MColor
                        ApiTypeRegister.register(arrayTypename, pymelType, apiArrayItemType=apiType)
                    except AttributeError:
                        pass
 
 
 
ApiTypeRegister.register('float', float)
ApiTypeRegister.register('double', float)
ApiTypeRegister.register('bool', bool)
ApiTypeRegister.register('int', int)
ApiTypeRegister.register('short', int)
ApiTypeRegister.register('uint', int)
ApiTypeRegister.register('uchar', int)
#ApiTypeRegister.register('long', int)
ApiTypeRegister.register('MString', unicode )
ApiTypeRegister.register('MStringArray', list, apiArrayItemType=unicode )
ApiTypeRegister.register('MIntArray', int, apiArrayItemType=int)
ApiTypeRegister.register('MFloatArray', float, apiArrayItemType=float)
ApiTypeRegister.register('MDoubleArray', float, apiArrayItemType=float)
 
class ApiArgUtil(object):
 
    def __init__(self, apiClassName, methodName, methodIndex=0 ):
        """If methodInfo is None, then the methodIndex will be used to lookup the methodInfo from apiClassInfo"""
        self.apiClassName = apiClassName
        self.methodName = methodName
 
 
        if methodIndex is None:
            try:
                methodInfoList = apicache.apiClassInfo[apiClassName]['methods'][methodName]
            except KeyError:
                raise TypeError, "method %s of %s cannot be found" % (methodName, apiClassName)
            else:
                for i, methodInfo in enumerate( methodInfoList ):
 
                    #argInfo = methodInfo['argInfo']
 
                    #argList = methodInfo['args']
                    argHelper = ApiArgUtil(apiClassName, methodName, i)
 
                    if argHelper.canBeWrapped() :
                        methodIndex = i
                        break
 
                # if it is still None then we didn't find anything
                if methodIndex is None:
                    raise TypeError, "method %s of %s cannot be wrapped" % (methodName, apiClassName)
 
        self.methodInfo = apicache.apiClassInfo[apiClassName]['methods'][methodName][methodIndex]
        self.methodIndex = methodIndex
 
    def iterArgs(self, inputs=True, outputs=True, infoKeys=[]):
        res = []
        for argname, argtype, direction in self.methodInfo['args']:
 
            if direction == 'in':
                if not inputs:
                    continue
            else:
                if not outputs:
                    continue
 
            if infoKeys:
                arg_res = [argname]
                argInfo = self.methodInfo['argInfo'][argname]
                for key in infoKeys:
                    arg_res.append( argInfo[key] )
            else:
                arg_res = argname
            res.append( arg_res )
        return res
 
    def inArgs(self):
        return self.methodInfo['inArgs']
 
    def outArgs(self):
        return self.methodInfo['outArgs']
 
    def argList(self):
        return self.methodInfo['args']
 
    def argInfo(self):
        return self.methodInfo['argInfo']
 
    def getGetterInfo(self):
        try:
            inverse, isgetter = self.methodInfo['inverse']
            if isgetter:
                if hasattr( getattr(api, self.apiClassName), inverse ):
                    return ApiArgUtil( self.apiClassName, inverse, self.methodIndex )
        except:
            pass
 
    @staticmethod
    def isValidEnum( enumTuple ):
        if apicache.apiClassInfo.has_key(enumTuple[0]) and \
            apicache.apiClassInfo[enumTuple[0]]['enums'].has_key(enumTuple[1]):
            return True
        return False
 
    def hasOutput(self):
        if self.methodInfo['outArgs'] or self.methodInfo['returnType']:
            return True
        return False
 
    def canBeWrapped(self):
        inArgs = self.methodInfo['inArgs']
        outArgs =  self.methodInfo['outArgs']
        defaults = self.methodInfo['defaults']
        #argList = methodInfo['args']
        returnType =  self.methodInfo['returnType']
        # ensure that we can properly cast all the args and return values
        try:
            if returnType is not None:
                # Enum: ensure existence
                if isinstance( returnType, tuple ):
                    assert self.isValidEnum(returnType), '%s.%s(): invalid return enum: %s' % (self.apiClassName, self.methodName, returnType)
 
                # Other: ensure we can cast result
                else:
                    assert  returnType in ApiTypeRegister.outCast or \
                            returnType == self.apiClassName, \
                    '%s.%s(): invalid return type: %s' % (self.apiClassName, self.methodName, returnType)
 
            for argname, argtype, direction in self.methodInfo['args'] :
                # Enum
                if isinstance( argtype, tuple ):
                    assert self.isValidEnum(argtype), '%s.%s(): %s: invalid enum: %s' % (self.apiClassName, self.methodName, argname, argtype)
 
                # Input
                elif direction == 'in':
                    assert  argtype in ApiTypeRegister.inCast or \
                            defaults.has_key(argname) or \
                            argtype == self.apiClassName, \
                    '%s.%s(): %s: invalid input type %s' % (self.apiClassName, self.methodName, argname, argtype)
 
                    #if argname in ['instance', 'instanceNumber']: print '%s.%s(): %s: %r' % (self.apiClassName, self.methodName, argname, argtype)
                # Output
                elif direction == 'out':
                    assert argtype in ApiTypeRegister.refInit and argtype in ApiTypeRegister.refCast, '%s.%s(): %s: invalid output type %s' % (self.apiClassName, self.methodName, argname, argtype)
                    #try:
                    #    assert argtype.type() in refInit, '%s.%s(): cannot cast referece arg %s of type %s' % (apiClassName, methodName, argname, argtype)
                    #except AttributeError:
                    #    assert argtype in refInit, '%s.%s(): cannot cast referece arg %s of type %s' % (apiClassName, methodName, argname, argtype)
        except AssertionError, msg:
            #_logger.debug( str(msg) )
            return False
 
        #_logger.debug("%s: valid" % self.getPrototype())
        return True
 
#    def castEnum(self, argtype, input ):
#        if isinstance( input, int):
#            return input
#
#        elif input[0] != 'k' or not input[1].isupper():
#            input = 'k' + util.capitalize(input)
#            return apicache.apiClassInfo[argtype[0]]['enums'][argtype[1]].index(input)
 
    def getInputTypes(self):
        inArgs = self.methodInfo['inArgs']
        types = self.methodInfo['types']
        return [str(types[x]) for x in inArgs ]
 
    def getOutputTypes(self):
        ret = self.methodInfo['returnType']
        if ret is None:
            ret = []
        else:
            ret = [str(ret)]
 
        outArgs =  self.methodInfo['outArgs']
        types = self.methodInfo['types']
        return ret + [str(types[x]) for x in outArgs ]
 
    def getReturnType(self):
        return self.methodInfo['returnType']
 
    def getPymelName(self ):
        pymelName = self.methodInfo.get('pymelName',self.methodName)
        try:
            pymelClassName = apiClassNamesToPyNodeNames[self.apiClassName]
            pymelName, data = _getApiOverrideNameAndData( pymelClassName, pymelName )
        except KeyError:
            pass
        return pymelName
 
    def getMethodDocs(self):
        return self.methodInfo['doc']
 
    def getPrototype(self, className=True, methodName=True, outputs=False, defaults=False):
        inArgs = self.methodInfo['inArgs']
        outArgs =  self.methodInfo['outArgs']
        returnType =  self.methodInfo['returnType']
        types = self.methodInfo['types']
        args = []
 
        for x in inArgs:
            arg = str(types[x]) + ' ' + x
            if defaults:
                try:
                    #_logger.debug(self.methodInfo['defaults'][x])
                    arg += '=' + str(self.methodInfo['defaults'][x])
                except KeyError: pass
            args.append( arg )
 
        proto = "(%s)" % (', '.join( args ) )
        if methodName:
            proto = self.methodName + proto
            if className:
                proto = self.apiClassName + '.' + proto
 
 
        if outputs:
            results = []
            if returnType:
                results.append(returnType)
            for x in outArgs:
                results.append( types[x] )
 
            if len(results)==1:
                proto += ' --> ' + str(results[0])
            elif len(results):
                proto += ' --> (%s)' % ', '.join( [str(x) for x in results] )
        return proto
 
    def castInput(self, argName, input, cls):
        # enums
        argtype = self.methodInfo['types'][argName]
        if isinstance( argtype, tuple ):
            # convert enum as a string or int to an int
 
            #if isinstance( input, int):
            #    return input
 
            apiClassName, enumName = argtype
 
            try:
                return apicache.apiClassInfo[apiClassName]['enums'][enumName]['values'].getIndex(input)
            except ValueError:
                try:
                    return apicache.apiClassInfo[apiClassName]['pymelEnums'][enumName].getIndex(input)
                except ValueError:
                    raise ValueError, "expected an enum of type %s.%s: got %r" % ( apiClassName, enumName, input )
 
        elif input is not None:
#            try:
 
            f = ApiTypeRegister.inCast[argtype]
            if f is None:
                return input
 
            input = self.toInternalUnits(argName, input)
            return f( input )
#            except:
#                if input is None:
#                    # we should do a check to ensure that the default is None, but for now, just return
#                    return input
#                if argtype != cls.__name__:
#                    raise TypeError, "Cannot cast a %s to %s" % ( type(input).__name__, argtype )
#                return cls(input)
 
    def fromInternalUnits(self, result, instance=None):
        # units
        unit = self.methodInfo['returnInfo'].get('unitType',None)
        returnType = self.methodInfo['returnInfo']['type']
        #_logger.debug(unit)
        #returnType in ['MPoint'] or
        if unit == 'linear' or returnType == 'MPoint':
            unitCast = ApiTypeRegister.outCast['MDistance']
            if util.isIterable(result):
                result = [ unitCast(instance,val) for val in result ]
            else:
                result = unitCast(instance,result)
 
        # maybe this should not be hardwired here
        # the main reason it is hardwired is because we don't want to convert the w component, which we
        # would do if we iterated normally
        elif returnType == 'MPoint':
            #_logger.debug("linear")
            unitCast = ApiTypeRegister.outCast['MDistance']
            result = [ unitCast(instance,result[0]), unitCast(instance,result[1]), unitCast(instance,result[2]) ]
 
        elif unit == 'angular':
            #_logger.debug("angular")
            unitCast = ApiTypeRegister.outCast['MAngle']
            if util.isIterable(result):
                result = [ unitCast(instance,val) for val in result ]
            else:
                result = unitCast(instance,result)
        return result
 
    def toInternalUnits(self, arg, input ):
        # units
        info = self.methodInfo['argInfo'][arg]
        unit = info.get('unitType',None)
        if unit == 'linear':
            #_logger.debug("setting linear")
            unitCast = ApiTypeRegister.inCast['MDistance']
            if util.isIterable(input):
                input = [ unitCast(val).asInternalUnit() for val in input ]
            else:
                input = unitCast(input).asInternalUnit()
 
        elif unit == 'angular':
            #_logger.debug("setting angular")
            unitCast = ApiTypeRegister.inCast['MAngle']
            if util.isIterable(input):
                input = [ unitCast(val).asInternalUnit() for val in input ]
            else:
                input = unitCast(input).asInternalUnit()
 
        return input
 
    def castResult(self, instance, result ):
        returnType = self.methodInfo['returnType']
        if returnType:
            # enums
            if isinstance( returnType, tuple ):
                #raise NotImplementedError
                apiClassName, enumName = returnType
                try:
                    # TODO: return EnumValue type
 
                    # convert int result into pymel string name.
                    return apicache.apiClassInfo[apiClassName]['pymelEnums'][enumName][result]
                except KeyError:
                    raise ValueError, "expected an enum of type %s.%s" % ( apiClassName, enumName )
 
            else:
                #try:
                f = ApiTypeRegister.outCast[returnType]
                if f is None:
                    return result
 
                result = self.fromInternalUnits(result, instance)
 
                return f( instance, result )
#                except:
#                    cls = instance.__class__
#                    if returnType != cls.__name__:
#                        raise TypeError, "Cannot cast a %s to %s" % ( type(result).__name__, returnType )
#                    return cls(result)
 
 
 
    def initReference(self, argtype):
        return ApiTypeRegister.refInit[argtype]()
 
    def castReferenceResult(self,argtype,outArg):
        f = ApiTypeRegister.refCast[ argtype ]
        #_logger.debug("castReferenceResult")
        #_logger.debug( "%s %s %s" % (f, argtype, outArg) )
        if f is None:
            return outArg
 
        result = self.fromInternalUnits(outArg)
        return f( result )
 
 
 
 
    def getDefaults(self):
        "get a list of defaults"
        defaults = []
        defaultInfo = self.methodInfo['defaults']
        inArgs = self.methodInfo['inArgs']
        nargs = len(inArgs)
        for i, arg in enumerate( inArgs ):
            if arg in defaultInfo:
                default = defaultInfo[arg]
 
            # FIXME : these defaults should probably not be set here since this is supposed to be
            # a "dumb" registry of data.  perhaps move them to the controlPanel
 
            # set MSpace.Space enum to object space by default, but only if it is the last arg or
            # the next arg has a default ( i.e. kwargs must always come after args )
#            elif str(self.methodInfo['types'][arg]) == 'MSpace.Space' and \
#                (   i==(nargs-1) or ( i<(nargs-1) and inArgs[i+1] in defaultInfo )  ):
#                    default = apicache.Enum(['MSpace', 'Space', 'kWorld'])  # should be kPostTransform?  this is what xform defaults to...
 
            else:
                continue
 
            if isinstance(default, apicache.Enum ):
                # convert enums from apiName to pymelName. the default will be the readable string name
                apiClassName, enumName, enumValue = default
                try:
                    enumList = apicache.apiClassInfo[apiClassName]['enums'][enumName]['values']
                except KeyError:
                    _logger.warning("Could not find enumerator %s", default)
                else:
                    index = enumList.getIndex(enumValue)
                    default = apicache.apiClassInfo[apiClassName]['pymelEnums'][enumName][index]
            defaults.append( default )
 
        return defaults
 
    def isStatic(self):
        return self.methodInfo['static']
 
    def isDeprecated(self):
        return self.methodInfo.get('deprecated', False)
 
 
class ApiUndo:
    """
    this is based on a clever prototype that Dean Edmonds posted on python_inside_maya
    awhile back.  it works like this:
 
        - using the API, create a garbage node with an integer attribute,
          lock it and set it not to save with the scene.
        - add an API callback to the node, so that when the special attribute
          is changed, we get a callback
        - the API queue is a list of simple python classes with undoIt and
          redoIt methods.  each time we add a new one to the queue, we increment
          the garbage node's integer attribute using maya.cmds.
        - when maya's undo or redo is called, it triggers the undoing or
          redoing of the garbage node's attribute change (bc we changed it using
          MEL/maya.cmds), which in turn triggers our API callback.  the callback
          runs the undoIt or redoIt method of the class at the index taken from
          the numeric attribute.
 
    """
    __metaclass__ = util.Singleton
 
    def __init__( self ):
        self.node_name = '__pymelUndoNode'
        self.cb_enabled = True
        self.undo_queue = []
        self.redo_queue = []
 
 
    def _attrChanged(self, msg, plug, otherPlug, data):
        if self.cb_enabled\
           and (msg & api.MNodeMessage.kAttributeSet != 0) \
           and (plug == self.cmdCountAttr):
 
 
#            #count = cmds.getAttr(self.node_name + '.cmdCount')
#            #print count
            if api.MGlobal.isUndoing():
                #cmds.undoInfo(state=0)
                self.cb_enabled = False
                cmdObj = self.undo_queue.pop()
                cmdObj.undoIt()
                self.redo_queue.append(cmdObj)
                #cmds.undoInfo(state=1)
                self.cb_enabled = True
 
            elif api.MGlobal.isRedoing():
                #cmds.undoInfo(state=0)
                self.cb_enabled = False
                cmdObj = self.redo_queue.pop()
                cmdObj.redoIt()
                self.undo_queue.append(cmdObj)
                #cmds.undoInfo(state=1)
                self.cb_enabled = True
 
    def _attrChanged_85(self):
        print "attr changed", self.cb_enabled, api.MGlobal.isUndoing()
        if self.cb_enabled:
 
            if api.MGlobal.isUndoing():
                cmdObj = self.undo_queue.pop()
                print "calling undoIt"
                cmdObj.undoIt()
                self.redo_queue.append(cmdObj)
 
            elif api.MGlobal.isRedoing():
                cmdObj = self.redo_queue.pop()
                print "calling redoIt"
                cmdObj.redoIt()
                self.undo_queue.append(cmdObj)
 
    def _createNode( self ):
        """
        Create the undo node.
 
        Any type of node will do. I've chosen a 'facade' node since it
        doesn't have too much overhead and won't get deleted if the user
        optimizes the scene.
 
        Note that we don't want to use Maya commands here because they
        would pollute Maya's undo queue, so we use API calls instead.
        """
 
        self.flushUndo()
 
        dgmod = api.MDGModifier()
        self.undoNode = dgmod.createNode('facade')
        dgmod.renameNode(self.undoNode, self.node_name)
        dgmod.doIt()
 
        # Add an attribute to keep a count of the commands in the stack.
        attrFn = api.MFnNumericAttribute()
        self.cmdCountAttr = attrFn.create( 'cmdCount', 'cc',
                                           api.MFnNumericData.kInt
                                           )
 
        nodeFn = api.MFnDependencyNode(self.undoNode)
        self.node_name = nodeFn.name()
        nodeFn.addAttribute(self.cmdCountAttr)
 
        nodeFn.setDoNotWrite(True)
        nodeFn.setLocked(True)
 
        try:
            api.MMessage.removeCallback( self.cbid )
            if hasattr(self.cbid, 'disown'):
                self.cbid.disown()
        except:
            pass
        self.cbid = api.MNodeMessage.addAttributeChangedCallback( self.undoNode, self._attrChanged )
 
 
    def append(self, cmdObj ):
 
        self.cb_enabled = False
 
#        if not cmds.objExists( self.node_name ):
#            self._createNode()
 
        # Increment the undo node's command count. We want this to go into
        # Maya's undo queue because changes to this attr will trigger our own
        # undo/redo code.
        try:
            count = cmds.getAttr(self.node_name + '.cmdCount')
        except:
            self._createNode()
            count = cmds.getAttr(self.node_name + '.cmdCount')
 
        cmds.setAttr(self.node_name + '.cmdCount', count + 1)
 
        # Append the command to the end of the undo queue.
        self.undo_queue.append(cmdObj)
 
        # Clear the redo queue.
        self.redo_queue = []
 
        # Re-enable the callback.
        self.cb_enabled = True
 
    def execute( self, cmdObj, args ):
        self.cb_enabled = False
 
        if not cmds.objExists( self.node_name ):
            self._createNode()
 
        # Increment the undo node's command count. We want this to go into
        # Maya's undo queue because changes to this attr will trigger our own
        # undo/redo code.
        count = cmds.getAttr(self.node_name + '.cmdCount')
        cmds.setAttr(self.node_name + '.cmdCount', count + 1)
 
        # Execute the command object's 'doIt' method.
        res = cmdObj.doIt(args)
 
        # Append the command to the end of the undo queue.
        self.undo_queue.append(cmdObj)
 
        # Clear the redo queue.
        self.redo_queue = []
 
        # Re-enable the callback.
        self.cb_enabled = True
        return res
 
    def flushUndo( self, *args ):
        self.undo_queue = []
        self.redo_queue = []
 
apiUndo = ApiUndo()
 
class ApiUndoItem(object):
    """A simple class that reprsents an undo item to be undone or redone."""
    __slots__ = ['_setter', '_reo_args', '_undo_args' ]
    def __init__(self, setter, redoArgs, undoArgs):
        self._setter = setter
        self._reo_args = redoArgs
        self._undo_args = undoArgs
    def redoIt(self):
        self._setter(*self._reo_args)
 
    def undoIt(self):
        self._setter(*self._undo_args)
 
def wrapApiMethod( apiClass, methodName, newName=None, proxy=True, overloadIndex=None ):
    """
    create a wrapped, user-friendly API method that works the way a python method should: no MScriptUtil and
    no special API classes required.  Inputs go in the front door, and outputs come out the back door.
 
 
    Regarding Undo
    --------------
 
    The API provides many methods which are pairs -- one sets a value
    while the other one gets the value.  the naming convention of these
    methods follows a fairly consistent pattern.  so what I did was
    determine all the get and set pairs, which I can use to automatically
    register api undo items:  prior to setting something, we first *get*
    it's existing value, which we can later use to reset when undo is
    triggered.
 
    This API undo is only for PyMEL methods which are derived from API
    methods.  it's not meant to be used with plugins.  and since it just
    piggybacks maya's MEL undo system, it won't get cross-mojonated.
 
    Take `MFnTransform.setTranslation`, for example. PyMEL provides a wrapped copy of this as
    `Transform.setTranslation`.   when pymel.Transform.setTranslation is
    called, here's what happens in relation to undo:
 
        #. process input args, if any
        #. call MFnTransform.getTranslation() to get the current translation.
        #. append to the api undo queue, with necessary info to undo/redo
           later (the current method, the current args, and the current
           translation)
        #. call MFnTransform.setTranslation() with the passed args
        #. process result and return it
 
 
    :Parameters:
 
        apiClass : class
            the api class
        methodName : string
            the name of the api method
        newName : string
            optionally provided if a name other than that of api method is desired
        proxy : bool
            If True, then __apimfn__ function used to retrieve the proxy class. If False,
            then we assume that the class being wrapped inherits from the underlying api class.
        overloadIndex : None or int
            which of the overloaded C++ signatures to use as the basis of our wrapped function.
 
 
        """
 
    #getattr( api, apiClassName )
 
    apiClassName = apiClass.__name__
    try:
        method = getattr( apiClass, methodName )
    except AttributeError:
        return
 
    argHelper = ApiArgUtil(apiClassName, methodName, overloadIndex)
    undoable = True # controls whether we print a warning in the docs
 
    if newName is None:
        pymelName = argHelper.getPymelName()
    else:
        pymelName = newName
 
    if argHelper.canBeWrapped() :
 
        if argHelper.isDeprecated():
            _logger.info(  "%s.%s is deprecated" % (apiClassName, methodName) )
        inArgs = argHelper.inArgs()
        outArgs = argHelper.outArgs()
        argList = argHelper.argList()
        argInfo = argHelper.argInfo()
 
        getterArgHelper = argHelper.getGetterInfo()
 
        if argHelper.hasOutput() :
            getterInArgs = []
            # query method ( getter )
            #if argHelper.getGetterInfo() is not None:
            if getterArgHelper is not None:
                _logger.warn( "%s.%s has an inverse %s, but it has outputs, which is not allowed for a 'setter'" % (
                                                                            apiClassName, methodName, getterArgHelper.methodName ) )
 
        else:
            # edit method ( setter )
            if getterArgHelper is None:
                #_logger.debug( "%s.%s has no inverse: undo will not be supported" % ( apiClassName, methodName ) )
                getterInArgs = []
                undoable = False
            else:
                getterInArgs = getterArgHelper.inArgs()
 
 
        # create the function
        def wrappedApiFunc( self, *args ):
 
            do_args = []
            outTypeList = []
 
            undoEnabled = getterArgHelper is not None and cmds.undoInfo(q=1, state=1) and apiUndo.cb_enabled
            #outTypeIndex = []
 
            if len(args) != len(inArgs):
                raise TypeError, "%s() takes exactly %s arguments (%s given)" % ( methodName, len(inArgs), len(args) )
 
            # get the value we are about to set
            if undoEnabled:
                getterArgs = []  # args required to get the current state before setting it
                undo_args = []  # args required to reset back to the original (starting) state  ( aka "undo" )
                missingUndoIndices = [] # indices for undo args that are not shared with the setter and which need to be filled by the result of the getter
                inCount = 0
                for name, argtype, direction in argList :
                    if direction == 'in':
                        arg = args[inCount]
                        undo_args.append(arg)
                        if name in getterInArgs:
                            # gather up args that are required to get the current value we are about to set.
                            # these args are shared between getter and setter pairs
                            getterArgs.append(arg)
                            #undo_args.append(arg)
                        else:
                            # store the indices for
                            missingUndoIndices.append(inCount)
                            #undo_args.append(None)
                        inCount +=1
 
                getter = getattr( self, getterArgHelper.getPymelName() )
                setter = getattr( self, pymelName )
 
                try:
                    getterResult = getter( *getterArgs )
                except RuntimeError:
                    _logger.error( "the arguments at time of error were %r" % getterArgs)
                    raise
 
                # when a command returns results normally and passes additional outputs by reference, the result is returned as a tuple
                # otherwise, always as a list
                if not isinstance( getterResult, tuple ):
                    getterResult = (getterResult,)
 
                for index, result in zip(missingUndoIndices, getterResult ):
                    undo_args[index] = result
 
 
            inCount = totalCount = 0
            for name, argtype, direction in argList :
                if direction == 'in':
                    arg = args[inCount]
                    do_args.append( argHelper.castInput( name, arg, self.__class__ ) )
                    inCount +=1
                else:
                    val = argHelper.initReference(argtype)
                    do_args.append( val )
                    outTypeList.append( (argtype, totalCount) )
                    #outTypeIndex.append( totalCount )
                totalCount += 1
 
 
            if undoEnabled:
                undoItem = ApiUndoItem(setter, do_args, undo_args)
                apiUndo.append( undoItem )
 
            # Do final SafeApiPtr => 'true' ptr conversion
            final_do_args = []
            for arg in do_args:
                if isinstance(arg, api.SafeApiPtr):
                    final_do_args.append(arg())
                else:
                    final_do_args.append(arg)
            if argHelper.isStatic():
                result = method( *final_do_args )
            else:
                if proxy:
                    # due to the discrepancies between the API and Maya node hierarchies, our __apimfn__ might not be a
                    # subclass of the api class being wrapped, however, the api object can still be used with this mfn explicitly.
                    mfn = self.__apimfn__()
                    if not isinstance(mfn, apiClass):
                        mfn = apiClass( self.__apiobject__() )
                    result = method( mfn, *final_do_args )
                else:
                    result = method( self, *final_do_args )
            result = argHelper.castResult( self, result )
 
            if len(outArgs):
                if result is not None:
                    result = [result]
                else:
                    result = []
 
                for outType, index in outTypeList:
                    outArgVal = do_args[index]
                    res = argHelper.castReferenceResult( outType, outArgVal )
                    result.append( res )
 
                if len(result) == 1:
                    result = result[0]
                else:
                    result = tuple(result)
            return result
 
        wrappedApiFunc.__name__ = pymelName
 
        _addApiDocs( wrappedApiFunc, apiClass, methodName, overloadIndex, undoable )
 
        # format EnumValue defaults
        defaults = []
        for default in argHelper.getDefaults():
            if isinstance( default, util.EnumValue ):
                defaults.append( str(default) )
            else:
                defaults.append( default )
 
        if defaults:
            pass
            #_logger.debug("defaults: %s" % defaults)
 
        wrappedApiFunc = util.interface_wrapper( wrappedApiFunc, ['self'] + inArgs, defaults=defaults )
 
        if argHelper.isStatic():
            wrappedApiFunc = classmethod(wrappedApiFunc)
 
        return wrappedApiFunc
 
 
 
 
def addApiDocs(apiClass, methodName, overloadIndex=None, undoable=True):
    """decorator for adding API docs"""
 
    def doc_decorator(func):
        return _addApiDocs( func, apiClass, methodName, overloadIndex, undoable)
 
    return doc_decorator
 
def _addApiDocs( wrappedApiFunc, apiClass, methodName, overloadIndex=None, undoable=True):
 
    util.addLazyDocString( wrappedApiFunc, addApiDocsCallback, apiClass, methodName, overloadIndex, undoable, wrappedApiFunc.__doc__ )
    return wrappedApiFunc
 
def addApiDocsCallback( apiClass, methodName, overloadIndex=None, undoable=True, origDocstring=''):
 
    apiClassName = apiClass.__name__
 
    argHelper = ApiArgUtil(apiClassName, methodName, overloadIndex)
    inArgs = argHelper.inArgs()
    outArgs = argHelper.outArgs()
    argList = argHelper.argList()
    argInfo = argHelper.argInfo()
 
    def formatDocstring(type):
        """
        convert
        "['one', 'two', 'three', ['1', '2', '3']]"
        to
        "[`one`, `two`, `three`, [`1`, `2`, `3`]]"
 
        Enums
        this is a little convoluted: we only want api.conversion.Enum classes here, but since we can't
        import api directly, we have to do a string name comparison
        """
        if not isinstance(type, list):
            pymelType = ApiTypeRegister.types.get(type,type)
        else:
            pymelType = type
 
        if pymelType.__class__.__name__ == 'Enum':
            try:
                pymelType = pymelType.pymelName()
            except:
                try:
                    pymelType = pymelType.pymelName( ApiTypeRegister.getPymelType( pymelType[0] ) )
                except:
                    pass
                    #_logger.debug("Could not determine pymel name for %r" % repr(pymelType))
 
        doc = repr(pymelType).replace("'", "`")
        if type in ApiTypeRegister.arrayItemTypes.keys():
            doc += ' list'
        return doc
 
    # Docstrings
    docstring = argHelper.getMethodDocs()
    # api is no longer in specific units, it respect UI units like MEL
    docstring = docstring.replace( 'centimeter', 'linear unit' )
    docstring = docstring.replace( 'radian', 'angular unit' )
 
    S = '    '
    if len(inArgs):
        docstring += '\n\n:Parameters:\n'
        for name in inArgs :
            info = argInfo[name]
            type = info['type']
            typeStr = formatDocstring(type)
 
            docstring += S + '%s : %s\n' % (name, typeStr )
            docstring += S*2 + '%s\n' % (info['doc'])
            if isinstance( type, apicache.Enum ):
                apiClassName, enumName = type
                enumValues = apicache.apiClassInfo[apiClassName]['pymelEnums'][enumName].keys()
                docstring += '\n' + S*2 + 'values: %s\n' % ', '.join( [ '%r' % x for x in enumValues if x not in ['invalid', 'last' ] ] )
 
 
 
    # Results doc strings
    results = []
    returnType = argHelper.getReturnType()
    if returnType:
        rtype = formatDocstring(returnType)
        results.append( rtype )
    for argname in outArgs:
        rtype = argInfo[argname]['type']
        rtype = formatDocstring(rtype)
        results.append( rtype )
 
    if len(results) == 1:
        results = results[0]
        docstring += '\n\n:rtype: %s\n' % results
    elif results:
        docstring += '\n\n:rtype: (%s)\n' %  ', '.join(results)
 
    docstring += '\nDerived from api method `%s.%s.%s`\n' % (apiClass.__module__, apiClassName, methodName)
    if not undoable:
        docstring += '\n**Undo is not currently supported for this method**\n'
 
    if origDocstring:
        docstring = origDocstring + '\n' + docstring
 
    return docstring
 
class MetaMayaTypeWrapper(util.metaReadOnlyAttr) :
    """ A metaclass to wrap Maya api types, with support for class constants """
 
    _originalApiSetAttrs = {}
 
    class ClassConstant(object):
        """Class constant"""
        def __init__(self, value):
            self.value = value
        def __repr__(self):
            return '%s.%s(%s)' % ( self.__class__.__module__,  self.__class__.__name__, repr(self.value) )
        def __str__(self):
            return self.__repr__()
        def __get__(self, instance, owner):
            # purposedly authorize notation MColor.blue but not MColor().blue,
            # the constants are a class property and are not defined on instances
            if instance is None :
                # note that conversion to the correct type is done here
                return owner(self.value)
            else :
                raise AttributeError, "Class constants on %s are only defined on the class" % (owner.__name__)
        def __set__(self, instance, value):
            raise AttributeError, "class constant cannot be set"
        def __delete__(self, instance):
            raise AttributeError, "class constant cannot be deleted"
 
    def __new__(cls, classname, bases, classdict):
        """ Create a new class of metaClassConstants type """
 
        #_logger.debug( 'MetaMayaTypeWrapper: %s' % classname )
        removeAttrs = []
        # define __slots__ if not defined
        if '__slots__' not in classdict :
            classdict['__slots__'] = ()
        try:
            apicls = classdict['apicls']
            proxy=False
        except KeyError:
            try:
                apicls = classdict['__apicls__']
                proxy=True
            except KeyError:
                apicls = None
 
        if apicls is not None:
            #_logger.debug("ADDING %s to %s" % (apicls.__name__, classname))
            apiClassNamesToPyNodeNames[apicls.__name__] = classname
 
            if not proxy and apicls not in bases:
                #_logger.debug("ADDING BASE %s" % classdict['apicls'])
                bases = bases + (classdict['apicls'],)
            try:
                classInfo = apicache.apiClassInfo[apicls.__name__]
            except KeyError:
                _logger.info("No api information for api class %s" % ( apicls.__name__ ))
            else:
                #------------------------
                # API Wrap
                #------------------------
 
                # Find out methods herited from other bases than apicls to avoid
                # unwanted overloading
                herited = {}
                for base in bases :
                    if base is not apicls :
                        # basemro = inspect.getmro(base)
                        for attr in dir(base) :
                            if attr not in herited :
                                herited[attr] = base
 
                ##_logger.debug("Methods info: %(methods)s" % classInfo)
                # Class Methods
                for methodName, info in classInfo['methods'].items():
                    # don't rewrap if already herited from a base class that is not the apicls
                    #_logger.debug("Checking method %s" % (methodName))
 
                    try:
                        pymelName = info[0]['pymelName']
                        removeAttrs.append(methodName)
                    except KeyError:
                        pymelName = methodName
 
                    pymelName, data = _getApiOverrideNameAndData( classname, pymelName )
 
                    overloadIndex = data.get( 'overloadIndex', None )
 
                    assert isinstance( pymelName, str ), "%s.%s: %r is not a valid name" % ( classname, methodName, pymelName)
 
                    if pymelName not in herited:
                        if overloadIndex is not None:
                            if data.get('enabled', True):
                                if pymelName not in classdict:
                                    #_logger.debug("%s.%s autowrapping %s.%s usng proxy %r" % (classname, pymelName, apicls.__name__, methodName, proxy))
                                    method = wrapApiMethod( apicls, methodName, newName=pymelName, proxy=proxy, overloadIndex=overloadIndex )
                                    if method:
                                        #_logger.debug("%s.%s successfully created" % (classname, pymelName ))
                                        classdict[pymelName] = method
                                    #else: #_logger.debug("%s.%s: wrapApiMethod failed to create method" % (apicls.__name__, methodName ))
                                #else: #_logger.debug("%s.%s: skipping" % (apicls.__name__, methodName ))
                            #else: #_logger.debug("%s.%s has been manually disabled, skipping" % (apicls.__name__, methodName))
                        #else: #_logger.debug("%s.%s has no wrappable methods, skipping" % (apicls.__name__, methodName))
                    #else: #_logger.debug("%s.%s already herited from %s, skipping" % (apicls.__name__, methodName, herited[pymelName]))
 
                if 'pymelEnums' in classInfo:
                    # Enumerators
 
                    for enumName, enumList in classInfo['pymelEnums'].items():
                        #_logger.debug("adding enum %s to class %s" % ( enumName, classname ))
#                        #enum = util.namedtuple( enumName, enumList )
#                        #classdict[enumName] = enum( *range(len(enumList)) )
#                        # group into (key, doc) pairs
#                        enumKeyDocPairs = [ (k,classInfo['enums'][enumName]['valueDocs'][k] ) for k in enumList ]
#                        enum = util.Enum( *enumKeyDocPairs )
#                        classdict[enumName] = enum
                        classdict[enumName] = enumList
 
 
            if not proxy:
                #if removeAttrs:
                #    #_logger.debug( "%s: removing attributes %s" % (classname, removeAttrs) )
                def __getattribute__(self, name):
                    #_logger.debug(name )
                    if name in removeAttrs and name not in EXCLUDE_METHODS: # tmp fix
                        #_logger.debug("raising error")
                        raise AttributeError, "'"+classname+"' object has no attribute '"+name+"'"
                    #_logger.debug("getting from %s" % bases[0])
                    # newcls will be defined by the time this is called...
                    return super(newcls, self).__getattribute__(name)
 
                classdict['__getattribute__'] = __getattribute__
 
                if cls._hasApiSetAttrBug(apicls):
                    # correct the setAttr bug by wrapping the api's
                    # __setattr__ to handle data descriptors...
                    origSetAttr = apicls.__setattr__
                    # in case we need to restore the original setattr later...
                    # ... as we do in a test for this bug!
                    cls._originalApiSetAttrs[apicls] = origSetAttr
                    def apiSetAttrWrap(self, name, value):
                        if hasattr(self.__class__, name):
                            if hasattr(getattr(self.__class__, name), '__set__'):
                                # we've got a data descriptor with a __set__...
                                # don't use the apicls's __setattr__
                                return super(apicls, self).__setattr__(name, value)
                        return origSetAttr(self, name, value)
                    apicls.__setattr__ = apiSetAttrWrap
 
 
        # create the new class
        newcls = super(MetaMayaTypeWrapper, cls).__new__(cls, classname, bases, classdict)
 
        # shortcut for ensuring that our class constants are the same type as the class we are creating
        def makeClassConstant(attr):
            try:
                # return MetaMayaTypeWrapper.ClassConstant(newcls(attr))
                return MetaMayaTypeWrapper.ClassConstant(attr)
            except Exception, e:
                _logger.warn( "Failed creating %s class constant (%s): %s" % (classname, attr, e) )
        #------------------------
        # Class Constants
        #------------------------
        if hasattr(newcls, 'apicls') :
            # type (api type) used for the storage of data
            apicls  = newcls.apicls
            if apicls is not None:
                # build some constants on the class
                constant = {}
                # constants in class definition will be converted from api class to created class
                for name, attr in newcls.__dict__.iteritems() :
                    # to add the wrapped api class constants as attributes on the wrapping class,
                    # convert them to own class
                    if isinstance(attr, apicls) :
                        if name not in constant :
                            constant[name] = makeClassConstant(attr)
                # we'll need the api clas dict to automate some of the wrapping
                # can't get argspec on SWIG creation function of type built-in or we could automate more of the wrapping
                apiDict = dict(inspect.getmembers(apicls))
                # defining class properties on the created class
                for name, attr in apiDict.iteritems() :
                    # to add the wrapped api class constants as attributes on the wrapping class,
                    # convert them to own class
                    if isinstance(attr, apicls) :
                        if name not in constant :
                            constant[name] = makeClassConstant(attr)
                # update the constant dict with herited constants
                mro = inspect.getmro(newcls)
                for parentCls in mro :
                    if isinstance(parentCls, MetaMayaTypeWrapper) :
                        for name, attr in parentCls.__dict__.iteritems() :
                            if isinstance(attr, MetaMayaTypeWrapper.ClassConstant) :
                                if not name in constant :
                                    constant[name] = makeClassConstant(attr.value)
 
                # build the protected list to make some class ifo and the constants read only class attributes
                # new.__slots__ = ['_data', '_shape', '_ndim', '_size']
                # type.__setattr__(newcls, '__slots__', slots)
 
                # set class constants as readonly
#                readonly = newcls.__readonly__
#                if 'apicls' not in readonly :
#                    readonly['apicls'] = None
#                for c in constant.keys() :
#                    readonly[c] = None
#                type.__setattr__(newcls, '__readonly__', readonly)
                # store constants as class attributes
                for name, attr in constant.iteritems() :
                    type.__setattr__(newcls, name, attr)
 
            #else :   raise TypeError, "must define 'apicls' in the class definition (which Maya API class to wrap)"
 
 
        if hasattr(newcls, 'apicls') and not ApiTypeRegister.isRegistered(newcls.apicls.__name__):
            ApiTypeRegister.register( newcls.apicls.__name__, newcls )
 
        return newcls
 
    @classmethod
    def _hasApiSetAttrBug(cls, apiClass):
        """
        Maya has a bug on windows where some api objects have a __setattr__
        that bypasses properties (and other data descriptors).
 
        This tests if the given apiClass has such a bug.
        """
        class MyClass1(object):
            def __init__(self):
                self._bar = 'not set'
            def _setBar(self, val):
                self._bar = val
            def _getBar(self):
                return self._bar
            bar = property(_getBar, _setBar)
 
        class MyClass2(MyClass1, apiClass): pass
 
        foo2 = MyClass2()
        foo2.bar = 7
        # Here, on windows, MMatrix's __setattr__ takes over, and
        # (after presumabably determining it didn't need to do
        # whatever special case thing it was designed to do)
        # instead of calling the super's __setattr__, which would
        # use the property, inserts it into the object's __dict__
        # manually
        return bool(foo2.bar != 7)
 
class _MetaMayaCommandWrapper(MetaMayaTypeWrapper):
    """
    A metaclass for creating classes based on a maya command.
 
    Not intended to be used directly; instead, use the descendants: MetaMayaNodeWrapper, MetaMayaUIWrapper
    """
 
    _classDictKeyForMelCmd = None
 
    def __new__(cls, classname, bases, classdict):
        #_logger.debug( '_MetaMayaCommandWrapper: %s' % classname )
 
        newcls = super(_MetaMayaCommandWrapper, cls).__new__(cls, classname, bases, classdict)
 
        #-------------------------
        #   MEL Methods
        #-------------------------
        melCmdName, infoCmd = cls.getMelCmd(classdict)
 
 
        classdict = {}
        try:
            cmdInfo = cmdlist[melCmdName]
        except KeyError:
            pass
            #_logger.debug("No MEL command info available for %s" % melCmdName)
        else:
            try:
                cmdModule = __import__( 'pymel.core.' + cmdInfo['type'] , globals(), locals(), [''])
                func = getattr(cmdModule, melCmdName)
            except (AttributeError, TypeError):
                func = getattr(pmcmds,melCmdName)
 
            # add documentation
            classdict['__doc__'] = util.LazyDocString( (newcls, cls.docstring, (melCmdName,), {} ) )
            classdict['__melcmd__'] = staticmethod(func)
            classdict['__melcmd_isinfo__'] = infoCmd
 
            filterAttrs = ['name']+classdict.keys()
            filterAttrs += overrideMethods.get( bases[0].__name__ , [] )
            #filterAttrs += newcls.__dict__.keys()
 
            parentClasses = [ x.__name__ for x in inspect.getmro( newcls )[1:] ]
            for flag, flagInfo in cmdInfo['flags'].items():
                # don't create methods for query or edit, or for flags which only serve to modify other flags
                if flag in ['query', 'edit'] or 'modified' in flagInfo:
                    continue
 
 
                if flagInfo.has_key('modes'):
                    # flags which are not in maya docs will have not have a modes list unless they
                    # have passed through testNodeCmds
                    #continue
                    modes = flagInfo['modes']
 
                    # query command
                    if 'query' in modes:
                        methodName = 'get' + util.capitalize(flag)
                        apiToMelMap['mel'][classname].append( methodName )
 
                        if methodName not in filterAttrs and \
                                ( not hasattr(newcls, methodName) or cls.isMelMethod(methodName, parentClasses) ):
 
                            # 'enabled' refers to whether the API version of this method will be used.
                            # if the method is enabled that means we skip it here.
                            if not apicache.apiToMelData.has_key((classname,methodName)) \
                                or apicache.apiToMelData[(classname,methodName)].get('melEnabled',False) \
                                or not apicache.apiToMelData[(classname,methodName)].get('enabled',True):
                                returnFunc = None
 
                                if flagInfo.get( 'resultNeedsCasting', False):
                                    returnFunc = flagInfo['args']
 
                                if flagInfo.get( 'resultNeedsUnpacking', False):
                                    if returnFunc:
                                        # can't do:
                                        #   returnFunc = lambda x: returnFunc(x[0])
                                        # ... as this would create a recursive function!
                                        origReturnFunc = returnFunc
                                        returnFunc = lambda x: origReturnFunc(x[0])
                                    else:
                                        returnFunc = lambda x: x[0]
 
                                wrappedMelFunc = makeQueryFlagMethod( func, flag, methodName,
                                     returnFunc=returnFunc )
 
                                #_logger.debug("Adding mel derived method %s.%s()" % (classname, methodName))
                                classdict[methodName] = wrappedMelFunc
                            #else: #_logger.debug(("skipping mel derived method %s.%s(): manually disabled or overridden by API" % (classname, methodName)))
                        #else: #_logger.debug(("skipping mel derived method %s.%s(): already exists" % (classname, methodName)))
                    # edit command:
                    if 'edit' in modes or ( infoCmd and 'create' in modes ):
                        # if there is a corresponding query we use the 'set' prefix.
                        if 'query' in modes:
                            methodName = 'set' + util.capitalize(flag)
                        #if there is not a matching 'set' and 'get' pair, we use the flag name as the method name
                        else:
                            methodName = flag
 
                        apiToMelMap['mel'][classname].append( methodName )
 
                        if methodName not in filterAttrs and \
                                ( not hasattr(newcls, methodName) or cls.isMelMethod(methodName, parentClasses) ):
                            if not apicache.apiToMelData.has_key((classname,methodName)) \
                                or apicache.apiToMelData[(classname,methodName)].get('melEnabled',False) \
                                or not apicache.apiToMelData[(classname,methodName)].get('enabled', True):
                                #FIXME: shouldn't we be able to use the wrapped pymel command, which is already fixed?
                                fixedFunc = fixCallbacks( func, melCmdName )
 
                                wrappedMelFunc = makeEditFlagMethod( fixedFunc, flag, methodName)
                                #_logger.debug("Adding mel derived method %s.%s()" % (classname, methodName))
                                classdict[methodName] = wrappedMelFunc
                            #else: #_logger.debug(("skipping mel derived method %s.%s(): manually disabled" % (classname, methodName)))
                        #else: #_logger.debug(("skipping mel derived method %s.%s(): already exists" % (classname, methodName)))
 
        for name, attr in classdict.iteritems() :
            type.__setattr__(newcls, name, attr)
 
        return newcls
 
    @classmethod
    def getMelCmd(cls, classdict):
        """
        Retrieves the name of the mel command the generated class wraps, and whether it is an info command.
 
        Intended to be overridden in derived metaclasses.
        """
        return util.uncapitalize(classname), False
 
    @classmethod
    def isMelMethod(cls, methodName, parentClassList):
        """
        Deteremine if the passed method name exists on a parent class as a mel method
        """
        for classname in parentClassList:
            if methodName in apiToMelMap['mel'][classname]:
                return True
        return False
 
    @classmethod
    def docstring(cls, melCmdName):
        try:
            cmdInfo = cmdlist[melCmdName]
        except KeyError:
            #_logger.debug("No MEL command info available for %s" % melCmdName)
            classdoc = ''
        else:
            loadCmdDocCache()
            classdoc = 'class counterpart of mel function `%s`\n\n%s\n\n' % (melCmdName, cmdInfo['description'])
        return classdoc
 
class MetaMayaNodeWrapper(_MetaMayaCommandWrapper) :
    """
    A metaclass for creating classes based on node type.  Methods will be added to the new classes
    based on info parsed from the docs on their command counterparts.
    """
    completedClasses = {}
    def __new__(cls, classname, bases, classdict):
        # If the class explicitly gives it's mel node name, use that - otherwise, assume it's
        # the name of the PyNode, uncapitalized
        #_logger.debug( 'MetaMayaNodeWrapper: %s' % classname )
        nodeType = classdict.setdefault('__melnode__', util.uncapitalize(classname))
        apicache.addMayaType( nodeType )
        apicls = apicache.toApiFunctionSet( nodeType )
 
        if apicls is not None:
            if apicls in MetaMayaNodeWrapper.completedClasses:
                pass
                #_logger.debug( "%s: %s already used by %s: not adding to __apicls__" % (classname, apicls, MetaMayaNodeWrapper.completedClasses[ apicls ]) )
            else:
                #_logger.debug( "%s: adding __apicls__ %s" % (classname, apicls) )
                MetaMayaNodeWrapper.completedClasses[ apicls ] = classname
                classdict['__apicls__'] = apicls
 
        return super(MetaMayaNodeWrapper, cls).__new__(cls, classname, bases, classdict)
 
    @classmethod
    def getMelCmd(cls, classdict):
        """
        Retrieves the name of the mel command for the node that the generated class wraps,
        and whether it is an info command.
 
        Derives the command name from the mel node name - so '__melnode__' must already be set
        in classdict.
        """
        nodeType = classdict['__melnode__']
        infoCmd = False
        try:
            nodeCmd = cmdcache.nodeTypeToNodeCommand[ nodeType ]
        except KeyError:
            try:
                nodeCmd = nodeTypeToInfoCommand[ nodeType ]
                infoCmd = True
            except KeyError:
                nodeCmd = nodeType
        return nodeCmd, infoCmd
 
 
class MetaMayaUIWrapper(_MetaMayaCommandWrapper):
    """
    A metaclass for creating classes based on on a maya UI type/command.
    """
 
    def __new__(cls, classname, bases, classdict):
        # If the class explicitly gives it's mel ui command name, use that - otherwise, assume it's
        # the name of the PyNode, uncapitalized
        uiType= classdict.setdefault('__melui__', util.uncapitalize(classname))
 
        # TODO: implement a option at the cmdlist level that triggers listForNone
        # TODO: create labelArray for *Grp ui elements, which passes to the correct arg ( labelArray3, labelArray4, etc ) based on length of passed array
 
        return super(MetaMayaUIWrapper, cls).__new__(cls, classname, bases, classdict)
 
    @classmethod
    def getMelCmd(cls, classdict):
        return classdict['__melui__'], False
 
class MetaMayaComponentWrapper(MetaMayaTypeWrapper):
    """
    A metaclass for creating components.
    """
    def __new__(cls, classname, bases, classdict):
        newcls = super(MetaMayaComponentWrapper, cls).__new__(cls, classname, bases, classdict)
        apienum = getattr(newcls, '_apienum__', None)
#        print "addng new component %s - '%s' (%r):" % (newcls, classname, classdict),
        if apienum:
            if apienum not in apiEnumsToPyComponents:
                apiEnumsToPyComponents[apienum] = [newcls]
            else:
                oldEntries = apiEnumsToPyComponents[apienum]
 
                # if the apienum is already present, check if this class is a
                # subclass of an already present class
                newEntries  = []
                for oldEntry in oldEntries:
                    for base in bases:
                        if issubclass(base, oldEntry):
                            break
                    else:
                        newEntries.append(oldEntry)
                newEntries.append(newcls)
                apiEnumsToPyComponents[apienum] = newEntries
        return newcls
#
#def getValidApiMethods( apiClassName, api, verbose=False ):
#
#    validTypes = [ None, 'double', 'bool', 'int', 'MString', 'MObject' ]
#
#    try:
#        methods = apicache.apiClassInfo[apiClassName]
#    except KeyError:
#        return []
#
#    validMethods = []
#    for method, methodInfoList in methods.items():
#        for methodInfo in methodInfoList:
#            #_logger.debug(method, methodInfoList)
#            if not methodInfo['outArgs']:
#                returnType = methodInfo['returnType']
#                if returnType in validTypes:
#                    count = 0
#                    types = []
#                    for x in methodInfo['inArgs']:
#                        type = methodInfo['argInfo'][x]['type']
#                        #_logger.debug(x, type)
#                        types.append( type )
#                        if type in validTypes:
#                            count+=1
#                    if count == len( methodInfo['inArgs'] ):
#                        if verbose:
#                            _logger.info(('    %s %s(%s)' % ( returnType, method, ','.join( types ) )))
#                        validMethods.append(method)
#    return validMethods
#
#def readClassAnalysis( filename ):
#    f = open(filename)
#    info = {}
#    currentClass = None
#    currentSection = None
#    for line in f.readlines():
#        buf = line.split()
#        if buf[0] == 'CLASS':
#            currentClass = buf[1]
#            info[currentClass] = {}
#        elif buf[0].startswith('['):
#            if currentSection in ['shared_leaf', 'api', 'pymel']:
#                currentSection = buf.strip('[]')
#                info[currentClass][currentSection] = {}
#        else:
#            n = len(buf)
#            if n==2:
#                info[currentClass][currentSection][buf[0]] = buf[1]
#            elif n==1:
#                pass
#                #info[currentClass][currentSection][buf[0]] = None
#            else:
#                pass
#    f.close()
#    _logger.info(info)
#    return info
#
#def fixClassAnalysis( filename ):
#    f = open(filename)
#    info = {}
#    currentClass = None
#    currentSection = None
#    lines = f.readlines()
#    for i, line in enumerate(lines):
#        buf = line.split()
#        if buf[0] == 'CLASS':
#            currentClass = buf[1]
#            info[currentClass] = {}
#        elif buf[0].startswith('['):
#            if currentSection in ['shared_leaf', 'api', 'pymel']:
#                currentSection = buf.strip('[]')
#                info[currentClass][currentSection] = {}
#        else:
#            isAutoNamed, nativeName, pymelName, failedAutoName = re.match( '([+])?\s+([a-zA-Z0-9]+)(?:\s([a-zA-Z0-9]+))?(?:\s([a-zA-Z0-9]+))?', line ).groups()
#            if isAutoNamed and pymelName is None:
#                pymelName = nativeName
#            n = len(buf)
#
#            if n==2:
#                info[currentClass][currentSection][buf[0]] = buf[1]
#            elif n==1:
#                pass
#                #info[currentClass][currentSection][buf[0]] = None
#            else:
#                pass
#    f.close()
#    _logger.info(info)
#    return info
 
#
#def analyzeApiClasses():
#    for elem in api.apiTypeHierarchy.preorder():
#        try:
#            parent = elem.parent.key
#        except:
#            parent = None
#        analyzeApiClass( elem.key, None )
#
#def analyzeApiClass( apiTypeStr ):
#    try:
#        mayaType = apicache.apiTypesToMayaTypes[ apiTypeStr ].keys()
#        if util.isIterable(mayaType) and len(mayaType) == 1:
#            mayaType = mayaType[0]
#            pymelType = pyNodeNamesToPyNodes.get( util.capitalize(mayaType) , None )
#        else:
#            pymelType = None
#    except KeyError:
#        mayaType = None
#        pymelType = None
#        #_logger.debug("no Fn", elem.key, pymelType)
#
#    try:
#        apiClass = api.apiTypesToApiClasses[ apiTypeStr ]
#    except KeyError:
#
#        _logger.info("no Fn %s", apiTypeStr)
#        return
#
#    apiClassName = apiClass.__name__
#    parentApiClass = inspect.getmro( apiClass )[1]
#
#    _logger.info("CLASS %s %s", apiClassName, mayaType)
#
#    # get all pymelName lookups for this class and its bases
#    pymelMethodNames = {}
#    for cls in inspect.getmro( apiClass ):
#        try:
#            pymelMethodNames.update( apicache.apiClassInfo[cls.__name__]['pymelMethods'] )
#        except KeyError: pass
#    reversePymelNames = dict( (v, k) for k,v in pymelMethodNames.items() )
#
#    allApiMembers = set([ pymelMethodNames.get(x[0],x[0]) for x in inspect.getmembers( apiClass, callable )  ])
#    parentApiMembers = set([ pymelMethodNames.get(x[0],x[0]) for x in inspect.getmembers( parentApiClass, callable ) ])
#    apiMembers = allApiMembers.difference( parentApiMembers )
#
#
##
##    else:
##        if apiTypeParentStr:
##            try:
##                parentApiClass = api.apiTypesToApiClasses[elem.parent.key ]
##                parentMembers = [ x[0] for x in inspect.getmembers( parentApiClass, callable ) ]
##            except KeyError:
##                parentMembers = []
##        else:
##            parentMembers = []
##
##        if pymelType is None: pymelType = pyNodeNamesToPyNodes.get( apiClass.__name__[3:] , None )
##
##        if pymelType:
##            parentPymelType = pyNodeTypesHierarchy[ pymelType ]
##            parentPyMembers = [ x[0] for x in inspect.getmembers( parentPymelType, callable ) ]
##            pyMembers = set([ x[0] for x in inspect.getmembers( pymelType, callable ) if x[0] not in parentPyMembers and not x[0].startswith('_') ])
##
##            _logger.info("CLASS", apiClass.__name__, mayaType)
##            parentApiClass = inspect.getmro( apiClass )[1]
##            #_logger.debug(parentApiClass)
##
##            pymelMethodNames = {}
##            # get all pymelName lookups for this class and its bases
##            for cls in inspect.getmro( apiClass ):
##                try:
##                    pymelMethodNames.update( apicache.apiClassInfo[cls.__name__]['pymelMethods'] )
##                except KeyError: pass
##
##            allFnMembers = set([ pymelMethodNames.get(x[0],x[0]) for x in inspect.getmembers( apiClass, callable )  ])
##
##            parentFnMembers = set([ pymelMethodNames.get(x[0],x[0]) for x in inspect.getmembers( parentApiClass, callable ) ])
##            fnMembers = allFnMembers.difference( parentFnMembers )
##
##            reversePymelNames = dict( (v, k) for k,v in pymelMethodNames.items() )
##
##            sharedCurrent = fnMembers.intersection( pyMembers )
##            sharedOnAll = allFnMembers.intersection( pyMembers )
##            sharedOnOther = allFnMembers.intersection( pyMembers.difference( sharedCurrent) )
###            _logger.info("    [shared_leaf]")
###            for x in sorted( sharedCurrent ):
###                if x in reversePymelNames: _logger.info('    ', reversePymelNames[x], x )
###                else: _logger.info('    ', x)
##
###            _logger.info("    [shared_all]")
###            for x in sorted( sharedOnOther ):
###                if x in reversePymelNames: _logger.info('    ', reversePymelNames[x], x )
###                else: _logger.info('    ', x)
##
##            _logger.info("    [api]")
##            for x in sorted( fnMembers ):
##                if x in sharedCurrent:
##                    prefix = '+   '
###                elif x in sharedOnOther:
###                    prefix = '-   '
##                else:
##                    prefix = '    '
##                if x in reversePymelNames: _logger.info(prefix, reversePymelNames[x], x )
##                else: _logger.info(prefix, x)
##
##            _logger.info("    [pymel]")
##            for x in sorted( pyMembers.difference( allFnMembers ) ): _logger.info('    ', x)
#
#
def addPyNodeCallback( dynModule, mayaType, pyNodeTypeName, parentPyNodeTypeName):
    #_logger.debug( "%s(%s): creating" % (pyNodeTypeName,parentPyNodeTypeName) )
    try:
        ParentPyNode = getattr( dynModule, parentPyNodeTypeName )
    except AttributeError:
        #_logger.info("error creating class %s: parent class %r not in dynModule %s" % (pyNodeTypeName, parentPyNodeTypeName, dynModule.__name__))
        return
    try:
        PyNodeType = MetaMayaNodeWrapper(pyNodeTypeName, (ParentPyNode,), {'__melnode__':mayaType})
    except TypeError, msg:
        # for the error: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
        #_logger.debug("Could not create new PyNode: %s(%s): %s" % (pyNodeTypeName, ParentPyNode.__name__, msg ))
        import new
        PyNodeType = new.classobj(pyNodeTypeName, (ParentPyNode,), {})
        PyNodeType.__module__ = dynModule.__name__
        setattr( dynModule, pyNodeTypeName, PyNodeType )
    else:
        #_logger.debug(("Created new PyNode: %s(%s)" % (pyNodeTypeName, parentPyNodeTypeName)))
        PyNodeType.__module__ = dynModule.__name__
        setattr( dynModule, pyNodeTypeName, PyNodeType )
    pyNodeTypesHierarchy[PyNodeType] = ParentPyNode
    pyNodesToMayaTypes[PyNodeType] = mayaType
    pyNodeNamesToPyNodes[pyNodeTypeName] = PyNodeType
    return PyNodeType
 
def addPyNode( dynModule, mayaType, parentMayaType ):
 
    #_logger.debug("addPyNode adding %s->%s on dynModule %s" % (mayaType, parentMayaType, dynModule))
    # unicode is not liked by metaNode
    pyNodeTypeName = str( util.capitalize(mayaType) )
    parentPyNodeTypeName = str(util.capitalize(parentMayaType))
 
    try:
        dynModule[pyNodeTypeName]
    except KeyError:
        #_logger.info( "%s(%s): setting up lazy loading" % ( pyNodeTypeName, parentPyNodeTypeName ) )
        dynModule[pyNodeTypeName] = ( addPyNodeCallback,
                                   ( dynModule, mayaType, pyNodeTypeName, parentPyNodeTypeName ) )
#    else:
#        if not pyNodeTypeName in dynModule.__dict__:
#            api.addMayaType( mayaType )
#            _logger.info( "%s(%s) exists" % ( pyNodeTypeName, parentPyNodeTypeName ) )
#
#
#            PyNodeType = getattr( dynModule, pyNodeTypeName )
#            try :
#                ParentPyNode = inspect.getmro(PyNodeType)[1]
#                #print "parent:", ParentPyNode, ParentPyNode.__name__
#                if ParentPyNode.__name__ != parentPyNodeTypeName :
#                    raise RuntimeError, "Unexpected PyNode %s for Maya type %s" % (ParentPyNode, )
#            except :
#                ParentPyNode = getattr( dynModule, parentPyNodeTypeName )
#            #_logger.debug("already exists:", pyNodeTypeName, )
#            pyNodeTypesHierarchy[PyNodeType] = ParentPyNode
#            pyNodesToMayaTypes[PyNodeType] = mayaType
#            pyNodeNamesToPyNodes[pyNodeTypeName] = PyNodeType
 
    return pyNodeTypeName
 
def removePyNode( dynModule, mayaType ):
    pyNodeTypeName = str( util.capitalize(mayaType) )
    PyNodeType = pyNodeNamesToPyNodes.pop( pyNodeTypeName, None )
    PyNodeParentType = pyNodeTypesHierarchy.pop( PyNodeType, None )
    pyNodesToMayaTypes.pop(PyNodeType,None)
    #_logger.debug('removing %s from %s' % (pyNodeTypeName, dynModule.__name__))
    dynModule.__dict__.pop(pyNodeTypeName,None)
    # delete the lazy loader too, so it does not regenerate the object
    delattr(dynModule.__class__,pyNodeTypeName)
    apicache.removeMayaType( mayaType )
 
def registerVirtualClass( cls, nameRequired=False ):
    """
    Allows a user to create their own subclasses of leaf PyMEL node classes,
    which are returned by `general.PyNode` and all other pymel commands.
 
    The process is fairly simple:
        1.  Subclass a pymel node class.  Be sure that it is a leaf class, meaning that it represents an actual Maya node type
            and not an abstract type higher up in the hierarchy.
        2.  Register your subclass by calling this function
 
    :type  nameRequired: bool
    :param nameRequired: True if the _isVirtual callback requires the string name to operate on. The object's name is not always immediately
        avaiable and may take an extra calculation to retrieve.
 
    """
    validSpecialAttrs = set(['__module__','__readonly__','__slots__','__melnode__','__doc__'])
 
    # assert that we are a leaf class
    parentCls = None
    for each_cls in inspect.getmro(cls):
        # we've reached a pymel node. we're done
        if each_cls.__module__.startswith('pymel.core'):
            parentCls = each_cls
            break
        else:
            # it's a custom class: test for disallowed attributes
            specialAttrs = [ x for x in each_cls.__dict__.keys() if x.startswith('__') and x.endswith('__') ]
            badAttrs = set(specialAttrs).difference(validSpecialAttrs)
            if badAttrs:
                raise ValueError, 'invalid attribute name(s) %s: special attributes are not allowed on virtual nodes' % ', '.join(badAttrs)
 
    assert parentCls, "passed class must be a subclass of a PyNode type"
    #assert issubclass( cls, parentCls ), "%s must be a subclass of %s" % ( cls, parentCls )
 
    cls.__melnode__ = parentCls.__melnode__
 
    # filter out any pre-existing classes with the same name as this one, because leaving stale callbacks in the list will slow things down
    virtualClass[parentCls] = [ x for x in virtualClass[parentCls] if x[0].__name__ != cls.__name__]
 
    #TODO:
    # inspect callbacks to ensure proper number of args and kwargs ( create callback must support **kwargs )
    # ensure that the name of our node does not conflict with a real node
 
    # put new classes at the front of list, so more recently added ones
    # will override old definitions
    virtualClass[parentCls].insert( 0, (cls, nameRequired) )
 
#-------------------------------------------------------------------------------
 
def isValidPyNode (arg):
    return pyNodeTypesHierarchy.has_key(arg)
 
def isValidPyNodeName (arg):
    return pyNodeNamesToPyNodes.has_key(arg)
 
def toPyNodeClass( obj, default=None ):
    if isinstance( obj, int ):
        mayaType = apicache.apiEnumsToMayaTypes.get( obj, None )
        return pyNodeNamesToPyNodes.get( util.capitalize(mayaType), default )
    elif isinstance( obj, basestring ):
        try:
            return pyNodeNamesToPyNodes[ util.capitalize(obj) ]
        except KeyError:
            mayaType = apicache.apiTypesToMayaTypes.get( obj, None )
            return pyNodeNamesToPyNodes.get( util.capitalize(mayaType), default )
 
def toApiTypeStr( obj, default=None ):
    if isinstance( obj, int ):
        return apicache.apiEnumsToApiTypes.get( obj, default )
    elif isinstance( obj, basestring ):
        return apicache.mayaTypesToApiTypes.get( obj, default)
    elif isinstance( obj, util.ProxyUnicode ):
        mayaType = pyNodesToMayaTypes.get( obj, None )
        return apicache.mayaTypesToApiTypes.get( mayaType, default)
 
def toApiTypeEnum( obj, default=None ):
    if isinstance( obj, util.ProxyUnicode ):
        obj = pyNodesToMayaTypes.get( obj, None )
    return apicache.toApiTypeEnum(obj)
 
def toMayaType( obj, default=None ):
    if issubclass( obj, util.ProxyUnicode ):
        return pyNodesToMayaTypes.get( obj, default )
    return apicache.toMayaType(obj)