Browse Source

bpdb 1.2 - Moved to BpDb.py; restructured code; added toggle/reset config; no longer strip AI/UD/OV from automatic cfg name; renamed bpGroup->bpPreset;

Ken Patel 15 years ago
parent
commit
9586ccc1f6
2 changed files with 541 additions and 363 deletions
  1. 530 0
      direct/src/showbase/BpDb.py
  2. 11 363
      direct/src/showbase/PythonUtil.py

+ 530 - 0
direct/src/showbase/BpDb.py

@@ -0,0 +1,530 @@
+
+import inspect
+import pdb
+import sys
+
+#Bpdb - breakpoint debugging system (kanpatel - 04/2010)
+class BpMan:
+    def __init__(self):
+        self.bpInfos = {}
+    
+    def partsToPath(self, parts):
+        cfg = parts.get('cfg')
+        grp = parts.get('grp')
+        id = parts.get('id','*')
+        path = ''
+        if cfg:
+            path += '%s'%(cfg,)
+            if grp or id:
+                path += '::'
+        if grp:
+            path += '%s'%(grp,)
+        if isinstance(id, int):
+            path += '(%s)'%(id,)
+        elif grp:
+            path += '.%s'%(id,)
+        else:
+            path += '%s'%(id,)
+        return path
+
+    def pathToParts(self, path=None):
+        parts = {'cfg':None, 'grp':None, 'id':None}
+
+        #verify input        
+        if not isinstance(path, type('')):
+            assert not "error: argument must be string of form '[cfg::][grp.]id'"
+            return parts
+
+        #parse cfg                
+        tokens = path.split('::')
+        if (len(tokens) > 1) and (len(tokens[0]) > 0):
+            parts['cfg'] = tokens[0]
+            path = tokens[1]
+            
+        #parse grp
+        tokens = path.split('.')
+        if (len(tokens) > 1) and (len(tokens[0]) > 0):
+            parts['grp'] = tokens[0]
+            path = tokens[1]
+
+        #parse id
+        if (len(path) > 0):
+            parts['id'] = path
+        if parts['id'] == '*':
+            parts['id'] = None
+
+        #done
+        return parts
+
+    def bpToPath(self, bp):
+        if type(bp) is type(''):
+            bp = self.pathToParts(bp)
+        return self.partsToPath(bp)
+        
+    def bpToParts(self, bp):
+        if type(bp) is type({}):
+            bp = self.partsToPath(bp)
+        return self.pathToParts(bp)
+        
+    def makeBpInfo(self, grp, id):
+        self.bpInfos.setdefault(grp, {None:{},})
+        self.bpInfos[grp].setdefault(id, {})
+
+    def getEnabled(self, bp):
+        parts = self.bpToParts(bp)
+        grp, id = parts['grp'], parts['id']
+        self.makeBpInfo(parts['grp'], parts['id'])
+        if not self.bpInfos[grp][None].get('enabled', True):
+            return False
+        if not self.bpInfos[grp][id].get('enabled', True):
+            return False
+        return True
+
+    def setEnabled(self, bp, enabled=True):
+        parts = self.bpToParts(bp)
+        grp, id = parts['grp'], parts['id']
+        self.makeBpInfo(grp, id)
+        self.bpInfos[grp][id]['enabled'] = enabled
+        return enabled
+
+    def toggleEnabled(self, bp):
+        parts = self.bpToParts(bp)
+        grp, id = parts['grp'], parts['id']
+        self.makeBpInfo(grp, id)
+        newEnabled = not self.bpInfos[grp][id].get('enabled', True)
+        self.bpInfos[grp][id]['enabled'] = newEnabled
+        return newEnabled
+
+    def getIgnoreCount(self, bp, decrement=False):
+        parts = self.bpToParts(bp)
+        grp, id = parts['grp'], parts['id']
+        self.makeBpInfo(grp, id)
+        ignoreCount = self.bpInfos[grp][id].get('ignoreCount', 0)
+        if ignoreCount > 0 and decrement:
+            self.bpInfos[grp][id]['ignoreCount'] = ignoreCount - 1
+        return ignoreCount
+
+    def setIgnoreCount(self, bp, ignoreCount=0):
+        if not isinstance(ignoreCount, int):
+            print 'error: first argument should be integer ignoreCount'
+            return
+            
+        parts = self.bpToParts(bp)
+        grp, id = parts['grp'], parts['id']
+        self.makeBpInfo(grp, id)
+        self.bpInfos[grp][id]['ignoreCount'] = ignoreCount
+        return ignoreCount
+
+    def getLifetime(self, bp):
+        parts = self.bpToParts(bp)
+        grp, id = parts['grp'], parts['id']
+        self.makeBpInfo(grp, id)
+        lifetime = self.bpInfos[grp][id].get('lifetime', -1)
+        return lifetime
+        
+    def setLifetime(self, bp, newLifetime):
+        parts = self.bpToParts(bp)
+        grp, id = parts['grp'], parts['id']
+        self.makeBpInfo(grp, id)
+        self.bpInfos[grp][id]['lifetime'] = newLifetime
+        return lifetime
+        
+    def decLifetime(self, bp):
+        parts = self.bpToParts(bp)
+        grp, id = parts['grp'], parts['id']
+        self.makeBpInfo(grp, id)
+        lifetime = self.bpInfos[grp][id].get('lifetime', -1)
+        if lifetime > 0:
+            lifetime = lifetime - 1
+        self.bpInfos[grp][id]['lifetime'] = lifetime
+        return lifetime
+
+    def getHitCount(self, bp):
+        parts = self.bpToParts(bp)
+        grp, id = parts['grp'], parts['id']
+        self.makeBpInfo(grp, id)
+        return self.bpInfos[grp][id].get('count', 0)
+        
+    def setHitCount(self, bp, newHitCount):
+        parts = self.bpToParts(bp)
+        grp, id = parts['grp'], parts['id']
+        self.makeBpInfo(grp, id)
+        self.bpInfos[grp][id]['count'] = newHitCount
+        
+    def incHitCount(self, bp):
+        parts = self.bpToParts(bp)
+        grp, id = parts['grp'], parts['id']
+        self.makeBpInfo(grp, id)
+        self.bpInfos[grp][id]['count'] = self.bpInfos[grp][id].get('count', 0) + 1
+        
+    def resetBp(self, bp):
+        parts = self.bpToParts(bp)
+        grp, id = parts['grp'], parts['id']
+        self.makeBpInfo(grp, id)
+        self.bpInfos[grp][id] = {}
+        if id is None:
+            del self.bpInfos[grp]
+    
+class BpDb:
+    def __init__(self):
+        self.enabled = True
+        self.cfgInfos = { None:True }
+        self.codeInfoCache = {}
+        self.bpMan = BpMan()
+        self.lastBp = None
+        self.pdbAliases = {}
+        self.configCallback = None
+
+    def setEnabledCallback(self, callback):
+        self.enabledCallback = callback
+
+    def verifyEnabled(self):
+        if self.enabledCallback:
+            return self.enabledCallback()
+        return True
+
+    def setConfigCallback(self, callback):
+        self.configCallback = callback
+                
+    def verifySingleConfig(self, cfg):
+        if cfg not in self.cfgInfos:
+            self.cfgInfos[cfg] = not self.configCallback or self.configCallback(cfg)
+        return self.cfgInfos[cfg]
+
+    def verifyConfig(self, cfg):
+        cfgList = choice(isinstance(cfg, tuple), cfg, (cfg,))
+        passedCfgs = [c for c in cfgList if self.verifySingleConfig(c)]
+        return (len(passedCfgs) > 0)
+
+    def toggleConfig(self, cfg):
+        newEnabled = not self.verifyConfig(cfg)
+        self.cfgInfos[cfg] = newEnabled
+        return self.cfgInfos[cfg]
+
+    def resetConfig(self, cfg):
+        self.cfgInfos.pop(cfg, None)
+
+    #setup bpdb prompt commands
+    def displayHelp(self):
+        print 'You may use normal pdb commands plus the following:'
+        #print '    cmd  [param <def>]  [cmd] does )this( with [param] (default is def)'
+        #print '    -----------------------------------------------------------------------'
+        print '    _i   [n <0> [, path=<curr>]] set ignore count for bp [path] to [n]'
+        print '    _t   [path <curr>]   toggle bp [path]'
+        print '    _tg  [grp <curr>]    toggle grp'
+        print '    _tc  [cfg <curr>]    toggle cfg'
+        print '    _z   [path <curr>]   clear all settings for bp [path]'
+        print '    _zg  [grp <curr>]    clear all settings for grp'
+        print '    _zc  [cfg <curr>]    clear all settings for cfg (restore .prc setting)'
+        print '    _h                   displays this usage help'
+        print '    _ua                  unalias these commands from pdb'
+
+    def addPdbAliases(self):
+        self.makePdbAlias('_i', 'bpdb._i(%*)')
+        self.makePdbAlias('_t', 'bpdb._t(%*)')
+        self.makePdbAlias('_tg', 'bpdb._tg(%*)')
+        self.makePdbAlias('_tc', 'bpdb._tc(%*)')
+        self.makePdbAlias('_z', 'bpdb._z(%*)')
+        self.makePdbAlias('_zg', 'bpdb._zg(%*)')
+        self.makePdbAlias('_zc', 'bpdb._zc(%*)')
+        self.makePdbAlias('_h', 'bpdb.displayHelp()')
+        self.makePdbAlias('_ua', 'bpdb.removePdbAliases()')
+
+    def makePdbAlias(self, aliasName, aliasCmd):
+        self.pdbAliases[aliasName] = aliasCmd
+        self.pdb.do_alias('%s %s'%(aliasName,aliasCmd))
+
+    def removePdbAliases(self):
+        for aliasName in self.pdbAliases.iterkeys():
+            self.pdb.do_unalias(aliasName)
+        self.pdbAliases = {}
+        print '(bpdb aliases removed)'
+
+    #handle bpdb prompt commands by forwarding to bpMan
+    def _e(self, *args, **kwargs):
+        bp = self._getArg(args, [type(''),type({}),], kwargs, ['path','bp','name',], self.lastBp)
+        enabled = self._getArg(args, [type(True),type(1),], kwargs, ['enabled','on',], True)
+        newEnabled = self.bpMan.setEnabled(bp, enabled)
+        print "'%s' is now %s."%(self.bpMan.bpToPath(bp),choice(newEnabled,'enabled','disabled'),)
+        
+    def _i(self, *args, **kwargs):
+        bp = self._getArg(args, [type(''),type({}),], kwargs, ['path','bp','name',], self.lastBp)
+        count = self._getArg(args, [type(1),], kwargs, ['ignoreCount','count','n',], 0)
+        newCount = self.bpMan.setIgnoreCount(bp, count)
+        print "'%s' will ignored %s times."%(self.bpMan.bpToPath(bp),newCount,)
+
+    def _t(self, *args, **kwargs):
+        bp = self._getArg(args, [type(''),type({}),], kwargs, ['path','bp','name',], self.lastBp)
+        newEnabled = self.bpMan.toggleEnabled(bp)
+        print "'%s' is now %s."%(self.bpMan.bpToPath(bp),choice(newEnabled,'enabled','disabled'),)
+        
+    def _tg(self, *args, **kwargs):
+        bp = self._getArg(args, [type(''),type({}),], kwargs, ['grp',], self.lastBp)
+        if type(bp) == type(''):
+            bp = {'grp':bp}
+        bp = {'grp':bp.get('grp')}
+        newEnabled = self.bpMan.toggleEnabled(bp)
+        print "'%s' is now %s."%(self.bpMan.bpToPath(bp),choice(newEnabled,'enabled','disabled'),)
+        
+    def _tc(self, *args, **kwargs):
+        bp = self._getArg(args, [type(''),type({}),], kwargs, ['cfg',], self.lastBp)
+        if type(bp) == type(''):
+            bp = {'cfg':bp}
+        bp = {'cfg':bp.get('cfg')}
+        newEnabled = self.toggleConfig(bp['cfg'])
+        print "'%s' is now %s."%(self.bpMan.bpToPath(bp),choice(newEnabled,'enabled','disabled'),)
+        
+    def _z(self, *args, **kwargs):
+        bp = self._getArg(args, [type(''),type({}),], kwargs, ['path','bp','name',], self.lastBp)
+        self.bpMan.resetBp(bp)
+        print "'%s' has been reset."%(self.bpMan.partsToPath(bp),)
+
+    def _zg(self, *args, **kwargs):
+        bp = self._getArg(args, [type(''),type({}),], kwargs, ['grp',], self.lastBp)
+        if type(bp) == type(''):
+            bp = {'grp':bp}
+        bp = {'grp':bp.get('grp')}
+        self.bpMan.resetBp(bp)
+        print "'%s' has been reset."%(self.bpMan.partsToPath(bp),)
+
+    def _zc(self, *args, **kwargs):
+        bp = self._getArg(args, [type(''),type({}),], kwargs, ['cfg',], self.lastBp)
+        if type(bp) == type(''):
+            bp = {'cfg':bp}
+        bp = {'cfg':bp.get('cfg')}
+        self.resetConfig(bp['cfg'])
+        print "'%s' has been reset."%(self.bpMan.bpToPath(bp),)
+ 
+    def _getArg(self, args, goodTypes, kwargs, goodKeys, default = None):
+        #look for desired arg in args and kwargs lists
+        argVal = default
+        for val in args:
+            if type(val) in goodTypes:
+                argVal = val
+        for key in goodKeys:
+            if key in kwargs:
+                argVal = kwargs[key]
+        return argVal
+                
+    #code for automatically determining param vals
+    def getFrameCodeInfo(self, frameCount=1):
+        #get main bits
+        stack = inspect.stack()
+        primaryFrame = stack[frameCount][0]
+
+        #todo: 
+        #frameInfo is inadequate as a unique marker for this code location
+        #caching disabled until suitable replacement is found
+        #
+        #frameInfo = inspect.getframeinfo(primaryFrame)
+        #frameInfo = (frameInfo[0], frameInfo[1])
+        #check cache
+        #codeInfo = self.codeInfoCache.get(frameInfo)
+        #if codeInfo:
+        #    return codeInfo
+        
+        #look for module name
+        moduleName = None
+        callingModule = inspect.getmodule(primaryFrame)
+        if callingModule and callingModule.__name__ != '__main__':
+            moduleName = callingModule.__name__.split()[-1] #get only leaf module name
+
+        #look for class name
+        className = None
+        for i in range(frameCount, len(stack)):
+            callingContexts = stack[i][4]
+            if callingContexts:
+                contextTokens = callingContexts[0].split()
+                if contextTokens[0] in ['class','def'] and len(contextTokens) > 1:
+                    callingContexts[0] = callingContexts[0].replace('(',' ').replace(':',' ')
+                    contextTokens = callingContexts[0].split()
+                    className = contextTokens[1]
+                    break
+        if className is None:
+            #look for self (this functions inappropriately for inherited classes)
+            slf = primaryFrame.f_locals.get('self')
+            try:
+                if slf:
+                    className = slf.__class__.__name__
+            except:
+                #in __init__ 'self' exists but 'if slf' will crash
+                pass
+
+        #get line number
+        def byteOffsetToLineno(code, byte):
+            # Returns the source line number corresponding to the given byte
+            # offset into the indicated Python code module.
+            import array
+            lnotab = array.array('B', code.co_lnotab)
+            line   = code.co_firstlineno
+            for i in range(0, len(lnotab), 2):
+                byte -= lnotab[i]
+                if byte <= 0:
+                    return line
+                line += lnotab[i+1]
+            return line
+
+        lineNumber = byteOffsetToLineno(primaryFrame.f_code, primaryFrame.f_lasti)
+        #frame = inspect.stack()[frameCount][0]
+        #lineno = byteOffsetToLineno(frame.f_code, frame.f_lasti)
+
+        codeInfo = (moduleName, className, lineNumber)
+        #self.codeInfoCache[frameInfo] = codeInfo
+        return codeInfo
+    
+    #actually deliver the user a prompt
+    def set_trace(self, bp, frameCount=1):
+        #find useful frame
+        self.currFrame = sys._getframe()
+        interactFrame = self.currFrame
+        while frameCount > 0:
+            interactFrame = interactFrame.f_back
+            frameCount -= 1
+
+        #cache this as the latest bp
+        self.lastBp = bp.getParts()
+        #set up and start debuggger
+        self.pdb = pdb.Pdb()
+        #self.pdb.do_alias('aa bpdb.addPdbAliases()')        
+        self.addPdbAliases()
+        self.pdb.set_trace(interactFrame);
+        
+    #bp invoke methods
+    def bp(self, id=None, grp=None, cfg=None, iff=True, enabled=True, test=None, frameCount=1):
+        if not (self.enabled and self.verifyEnabled()):
+            return
+        if not (enabled and iff):
+            return
+            
+        bpi = bp(id=id, grp=grp, cfg=cfg, frameCount=frameCount+1)
+        bpi.maybeBreak(test=test, frameCount=frameCount+1)
+
+    def bpCall(self,id=None,grp=None,cfg=None,iff=True,enabled=True,test=None,frameCount=1,onEnter=1,onExit=0):
+        def decorator(f):
+            return f
+
+        if not (self.enabled and self.verifyEnabled()):
+            return decorator
+        if not (enabled and iff):
+            return decorator
+        
+        bpi = bp(id=id, grp=grp, cfg=cfg, frameCount=frameCount+1)
+        if bpi.disabled:
+            return decorator
+
+        def decorator(f):
+            def wrap(*args, **kwds):
+                #create our bp object
+                dbp = bp(id=id or f.__name__, grp=bpi.grp, cfg=bpi.cfg, frameCount=frameCount+1)
+                if onEnter:
+                    dbp.maybeBreak(test=test,frameCount=frameCount+1,displayPrefix='Calling ')
+                f_result = f(*args, **kwds)
+                if onExit:
+                    dbp.maybeBreak(test=test,frameCount=frameCount+1,displayPrefix='Exited ')
+                return f_result
+                
+            wrap.func_name = f.func_name
+            wrap.func_dict = f.func_dict
+            wrap.func_doc = f.func_doc
+            wrap.__module__ = f.__module__
+            return wrap
+            
+        return decorator
+        
+    def bpPreset(self, *args, **kArgs):
+        def functor(*cArgs, **ckArgs):
+            return
+
+        if self.enabled and self.verifyEnabled():
+            argsCopy = args[:]
+            def functor(*cArgs, **ckArgs):
+                kwArgs = kArgs
+                kwArgs.update(ckArgs)
+                kwArgs.pop('static', None)
+                kwArgs['frameCount'] = ckArgs.get('frameCount',1)+1
+                return self.bp(*(cArgs), **kwArgs)
+        
+        if kArgs.get('static'):
+            return staticmethod(functor)
+        else:
+            return functor
+
+    #deprecated:
+    @staticmethod
+    def bpGroup(*args, **kArgs):
+        print "BpDb.bpGroup is deprecated, use bpdb.bpPreset instead"
+        return bpdb.bpPreset(*(args), **(kArgs))
+
+
+class bp:
+    def __init__(self, id=None, grp=None, cfg=None, frameCount=1):
+        #check early out conditions
+        self.disabled = False
+        if not bpdb.enabled:
+            self.disabled = True
+            return
+        
+        #default cfg, grp, id from calling code info
+        moduleName, className, lineNumber = bpdb.getFrameCodeInfo(frameCount=frameCount+1)
+        self.grp = grp or className or moduleName
+        self.id = id or lineNumber
+
+        #default cfg to stripped module name
+        if cfg is None and moduleName:
+            cfg = moduleName
+            if cfg.find("Distributed") != -1: #prune leading 'Distributed'
+                cfg = cfg[len("Distributed"):]
+
+        # check cfgs
+        self.cfg = cfg
+        if not bpdb.verifyConfig(self.cfg):
+            self.disabled = True
+            return
+
+    def getParts(self):
+        return {'id':self.id,'grp':self.grp,'cfg':self.cfg}
+        
+    def displayContextHint(self, displayPrefix=''):
+        contextString = displayPrefix + bpdb.bpMan.partsToPath({'id':self.id,'grp':self.grp,'cfg':self.cfg})
+        dashes = '-'*max(0, (80 - len(contextString) - 4) / 2)
+        print '<%s %s %s>'%(dashes,contextString,dashes)
+    
+    def maybeBreak(self, test=None, frameCount=1, displayPrefix=''):
+        if self.shouldBreak(test=test):
+            self.doBreak(frameCount=frameCount+1,displayPrefix=displayPrefix)
+    
+    def shouldBreak(self, test=None):
+        #check easy early out
+        if self.disabled:
+            return False
+        if test:
+            if not isinstance(test, (list, tuple)):
+                test = (test,)
+            for atest in test:
+                if not atest():
+                    return False
+
+        #check disabled conditions
+        if not bpdb.bpMan.getEnabled({'grp':self.grp,'id':self.id}):
+            return False
+        if not bpdb.verifyConfig(self.cfg):
+            return False
+
+        #check skip conditions
+        if bpdb.bpMan.getIgnoreCount({'grp':self.grp,'id':self.id},decrement=True):
+            return False
+        if bpdb.bpMan.getLifetime({'grp':self.grp,'id':self.id}) == 0:
+            return False
+
+        #all conditions go
+        return True
+        
+    def doBreak(self, frameCount=1,displayPrefix=''):
+        #accumulate hit count
+        bpdb.bpMan.decLifetime({'grp':self.grp,'id':self.id})
+        bpdb.bpMan.incHitCount({'grp':self.grp,'id':self.id})
+        
+        #setup debugger
+        self.displayContextHint(displayPrefix=displayPrefix)
+        bpdb.set_trace(self, frameCount=frameCount+1)
+

+ 11 - 363
direct/src/showbase/PythonUtil.py

@@ -54,6 +54,7 @@ from StringIO import StringIO
 import marshal
 import ElementTree as ET
 from HTMLParser import HTMLParser
+import BpDb
 
 __report_indent = 3
 
@@ -4309,371 +4310,18 @@ if __debug__:
     assert repeatableRepr({1: 'a', 2: 'b'}) == repeatableRepr({2: 'b', 1: 'a'})
     assert repeatableRepr(set([1,2,3])) == repeatableRepr(set([3,2,1]))
 
-#bpdb - breakpoint debugging system (kanpatel - 04/2010)
-class BpDb:
+#set up bpdb
+bpdb = BpDb.BpDb()
+def bpdbGetEnabled():
     enabled = True
-    lastBp = None
-    grpInfos = {}
-
-    def set_trace(self, frameCount=1):
-        #find usefule frame
-        self.currFrame = sys._getframe()
-        interactFrame = self.currFrame
-        while frameCount > 0:
-            interactFrame = interactFrame.f_back
-            frameCount -= 1
-
-        #set up and start debuggger
-        self.pdb = pdb.Pdb()
-        self.pdb.do_alias('aa bpdb.addAliases()')        
-        self.addAliases()
-        self.pdb.set_trace(interactFrame);
-        
-    def addAliases(self):
-        self.aliases = {}
-        #bpdb cmds
-        self.makeAlias('_i', 'bpdb.lastBp.ignore(%*)')
-        self.makeAlias('_t', 'bpdb.lastBp.toggle(%*)')
-        self.makeAlias('_tg', 'bpdb.lastBp.toggleGroup(%*)')
-        self.makeAlias('_z', 'bpdb.lastBp.reset(%*)')
-        self.makeAlias('_h', 'bpdb.displayHelp()')
-        self.makeAlias('_ua', 'bpdb.removeAliases()')
-
-    def makeAlias(self, aliasName, aliasCmd):
-        self.aliases[aliasName] = aliasCmd
-        self.pdb.do_alias('%s %s'%(aliasName,aliasCmd))
-
-    def removeAliases(self):
-        for aliasName in self.aliases.iterkeys():
-            self.pdb.do_unalias(aliasName)
-        self.aliases = {}
-        print '(bpdb aliases removed)'
-
-    def displayHelp(self):
-        print 'You may use normal pdb commands plus the following:'
-        #print '    cmd  [param <def>]  [cmd] does )this( with [param] (default is def)'
-        #print '    -----------------------------------------------------------------------'
-        print '    _i   [n <0> [, name=<curr>]] set ignore count for bp [name] to [n]'
-        print '    _t   [name <curr>]   toggle bp [name]'
-        print '    _tg  [grp  <curr>]   toggle bp group [grp]'
-        print '    _z   [name <curr>]   clear all settings for bp [name]'
-        print '    _h                   displays this usage help'
-        print '    _ua                  unalias these commands from pdb'
-
-    @staticmethod
-    def verifyEnabled():
-        try:
-            bpdb.enabled = __dev__
-            bpdb.enabled = config.GetBool('force-breakpoints', bpdb.enabled)
-        except:
-            pass
-        return bpdb.enabled
-
-    @staticmethod
-    def bp(id=None, grp=None, cfg=None, iff=True, test=None, frameCount=1):
-        if not bpdb.enabled or not bpdb.verifyEnabled():
-            return
-            
-        bpi = bp(id=id, grp=grp, cfg=cfg, iff=iff, frameCount=frameCount+1)
-        bpi.maybeBreak(test=test, frameCount=frameCount+1)
-
-    @staticmethod
-    def bpCall(id=None,grp=None,cfg=None,iff=True,test=None,frameCount=1,onEnter=1,onExit=0):
-        def decorator(f):
-            return f
-
-        if not bpdb.enabled or not bpdb.verifyEnabled():
-            return decorator
-        
-        bpi = bp(id=id, grp=grp, cfg=cfg, iff=iff, frameCount=frameCount+1)
-        if bpi.disabled:
-            return decorator
-
-        def decorator(f):
-            def wrap(*args, **kwds):
-                #create our bp object
-                dbp = bp(id=id or f.__name__, grp=bpi.grp, cfg=bpi.cfg, iff=iff, frameCount=frameCount+1)
-                if onEnter:
-                    dbp.maybeBreak(iff=iff,test=test,frameCount=frameCount+1,displayPrefix='Calling ')
-                f_result = f(*args, **kwds)
-                if onExit:
-                    dbp.maybeBreak(iff=iff,test=test,frameCount=frameCount+1,displayPrefix='Exited ')
-                return f_result
-                
-            wrap.func_name = f.func_name
-            wrap.func_dict = f.func_dict
-            wrap.func_doc = f.func_doc
-            wrap.__module__ = f.__module__
-            return wrap
-            
-        return decorator
-        
-    @staticmethod
-    def bpGroup(*args, **kArgs):  #rename bpGroup -> bpPreset
-        def functor(*cArgs, **ckArgs):
-            return
-
-        if bpdb.enabled and bpdb.verifyEnabled():
-            argsCopy = args[:]
-            def functor(*cArgs, **ckArgs):
-                kwArgs = kArgs
-                kwArgs.update(ckArgs)
-                kwArgs.pop('static', None)
-                kwArgs['frameCount'] = ckArgs.get('frameCount',1)+1
-                return bpdb.bp(*(cArgs), **kwArgs)
-        
-        if kArgs.get('static'):
-            return staticmethod(functor)
-        else:
-            return functor
-
-
-class bp:
-    def __init__(self, id=None, grp=None, cfg=None, iff=True, frameCount=1):
-        #check early out conditions
-        self.disabled = False
-        if not bpdb.enabled:
-            self.disabled = True
-            return
-        
-        moduleName = None
-        callingModule = inspect.getmodule(inspect.stack()[frameCount][0])
-        if callingModule and callingModule.__name__ != '__main__':
-            #get only leaf module name
-            moduleName = callingModule.__name__.split()[-1] 
-
-        #default cfg to stripped module name
-        if cfg is None and moduleName:
-            cfg = moduleName
-            #prune 'Distributed' and 'AI/UD/OV'
-            if cfg.find("Distributed") != -1:
-                cfg = cfg[len("Distributed"):]
-                cfgLen = len(cfg)
-                if cfg > 2:
-                    for suffix in ['AI','UD','OV']:
-                        suffixPos = cfg.rfind(suffix)
-                        if suffixPos == cfg - 2:
-                            cfg = cfg[:cfgLen-2]
-                            break
-
-        # determine whether we should this bp is active
-        # based on the value of cfg.
-        if cfg:
-            dConfigParamList = []
-            dConfigParams = choice(isinstance(cfg, (list,tuple)), cfg, (cfg,))
-            dConfigParamList = [param for param in dConfigParams \
-                                if ConfigVariableBool('want-bp-%s' % (param,), 0).getValue()]
-            if not dConfigParamList:
-                self.disabled = True
-                return
-
-        #default grp to context name
-        if grp is None:
-            #look for class
-            for i in range(frameCount, len(inspect.stack())):
-                callingContexts = inspect.stack()[i][4]
-                if not callingContexts:
-                    continue
-                #print i, callingContexts
-                contextTokens = callingContexts[0].split()
-                if contextTokens[0] in ['class','def'] and len(contextTokens) > 1:
-                    callingContexts[0] = callingContexts[0].replace('(',' ').replace(':',' ')
-                    contextTokens = callingContexts[0].split()
-                    className = contextTokens[1]
-                    grp = className
-                    break
-            #look for self
-            if grp is None:
-                slf = inspect.stack()[frameCount][0].f_locals.get('self')
-                try:
-                    if slf:
-                        className = slf.__class__.__name__
-                        grp = className
-                except:
-                    #in __init__ 'self' exists but 'if slf' will crash
-                    pass
-            #default to module
-            if grp is None:
-                grp = moduleName                
-
-        #default name to line number
-        if id is None:
-            def byteOffsetToLineno(code, byte):
-                # Returns the source line number corresponding to the given byte
-                # offset into the indicated Python code module.
-                import array
-                lnotab = array.array('B', code.co_lnotab)
-                line   = code.co_firstlineno
-                for i in range(0, len(lnotab), 2):
-                    byte -= lnotab[i]
-                    if byte <= 0:
-                        return line
-                    line += lnotab[i+1]
-                return line
-
-            if frameCount < len(inspect.stack()):
-                frame = inspect.stack()[frameCount][0]
-                lineno = byteOffsetToLineno(frame.f_code, frame.f_lasti)
-                id = lineno
-
-        #store this breakpoint's settings
-        self.id = id
-        self.grp = grp
-        self.cfg = cfg
-        self.iff = iff
-
-        #cache this as the latest bp
-        bpdb.lastBp = self
-
-    @staticmethod
-    def prettyName(id=None, grp=None, cfg=None, q=0):
-        prettyName = ''
-        prettyName += choice(q, "'", '')
-        if cfg:
-            prettyName += '%s'%(cfg,)
-            if grp or id:
-                prettyName += '::'
-        if grp:
-            prettyName += '%s'%(grp,)
-        if id:
-            if isinstance(id, int):
-                prettyName += '(%s)'%(id,)
-            elif grp:
-                prettyName += '.%s'%(id,)
-            else:
-                prettyName += '%s'%(id,)
-        prettyName += choice(q, "'", '')
-        return prettyName
-
-    def displayContextHint(self, displayPrefix=''):
-        contextString = displayPrefix + self.prettyName(id=self.id,grp=self.grp,cfg=self.cfg)
-        dashes = '-'*max(0, (80 - len(contextString) - 4) / 2)
-        print '<%s %s %s>'%(dashes,contextString,dashes)
-    
-    def makeIdGrp(self, id, grp):
-        bpdb.grpInfos.setdefault(grp, {'__settings__':{},})
-        bpdb.grpInfos[grp].setdefault(id, {})
-
-    def parseBPPath(self, arg=None):
-        id = None
-        grp = None
-        if arg:
-            if not isinstance(arg, type('')):
-                print "error: argument must be string '[grp.]id'"
-                return None, None
-                
-            tokens = arg.split('.')
-            id = tokens[-1]
-            if len(tokens) > 1:
-                grp = tokens[-2]
-
-        id = id or bpdb.lastBp.id
-        grp = grp or bpdb.lastBp.grp       
-
-        return id, grp
-
-    def enable(self, enabled=True, id=None, grp=None):
-        id = id or bpdb.lastBp.id
-        grp = grp or bpdb.lastBp.grp
-        self.makeIdGrp(id,grp)  
-
-        bpdb.grpInfos[grp][id]['enabled'] = enabled
-        
-    def toggle(self, arg=None):
-        id, grp = self.parseBPPath(arg)
-        self.makeIdGrp(id,grp)  
-
-        newEnabled = not bpdb.grpInfos[grp][id].get('enabled', True)
-        bpdb.grpInfos[grp][id]['enabled'] = newEnabled
-        print '%s is now %s.'%(self.prettyName(id,grp,q=1),choice(newEnabled,'enabled','disabled'),)
-
-    def toggleGroup(self, grp=None):
-        if grp and not isinstance(grp, type('')):
-            print "error: argument must be string 'grp'"
-            return
-            
-        grp = grp or bpdb.lastBp.grp
-        bpdb.grpInfos.setdefault(grp, {'__settings__':{},})
-
-        newEnabled = not bpdb.grpInfos[grp]['__settings__'].get('enabled', True)
-        bpdb.grpInfos[grp]['__settings__']['enabled'] = newEnabled
-        print 'group %s is now %s.'%(self.prettyName(grp=grp,q=1),choice(newEnabled,'enabled','disabled'),)
-
-    def ignore(self, ignoreCount=0, arg=None):
-        if not isinstance(ignoreCount, int):
-            print 'error: first argument should be integer ignoreCount'
-            return
-            
-        id, grp = self.parseBPPath(arg)
-        self.makeIdGrp(id,grp)  
-
-        bpdb.grpInfos[grp][id]['ignoreCount'] = ignoreCount
-        print '%s will ignored %s times.'%(self.prettyName(id,grp,q=1),ignoreCount,)
-
-    def reset(self, arg=None):
-        id, grp = self.parseBPPath(arg)
-        self.makeIdGrp(id,grp)  
-
-        bpdb.grpInfos[grp][id] = {}
-        print '%s has been reset.'%(self.prettyName(id,grp,q=1),)
-
-    def maybeBreak(self, iff=True, test=None, frameCount=1, displayPrefix=''):
-        if self.shouldBreak(iff=iff, test=test):
-            self.doBreak(frameCount=frameCount+1,displayPrefix=displayPrefix)
-    
-    def shouldBreak(self,iff=True, test=None):
-        #check easy early out
-        if self.disabled:
-            return False
-        if not self.iff or not iff:
-            return False
-        if test:
-            if not isinstance(test, (list, tuple)):
-                test = (test,)
-            for atest in test:
-                if not atest():
-                    return False
-
-        #make sure we exist
-        self.makeIdGrp(self.id,self.grp)  
-
-        #check disabled conditions
-        if not bpdb.grpInfos[self.grp]['__settings__'].get('enabled', True):
-            return False
-        if not bpdb.grpInfos[self.grp][self.id].get('enabled', True):
-            return False
-        if self.cfg:
-            dConfigParamList = []
-            dConfigParams = choice(isinstance(self.cfg, (list,tuple)), self.cfg, (self.cfg,))
-            dConfigParamList = [param for param in dConfigParams \
-                                if ConfigVariableBool('want-bp-%s' % (param,), 0).getValue()]
-            if not dConfigParamList:
-                return False      
-
-        #check skip conditions
-        if bpdb.grpInfos[self.grp][self.id].get('ignoreCount', 0) > 0:
-            bpdb.grpInfos[self.grp][self.id]['ignoreCount'] -= 1
-            return False
-        if bpdb.grpInfos[self.grp][self.id].get('lifetime', -1) == 0:
-            return False
-
-        #all conditions go
-        return True
-        
-    def doBreak(self, frameCount=1,displayPrefix=''):
-        #make sure we exist
-        self.makeIdGrp(self.id,self.grp)  
-
-        #accumulate hit count
-        if 'lifetime' in bpdb.grpInfos[self.grp][self.id]:
-            bpdb.grpInfos[self.grp][self.id]['lifetime'] -= 1
-        bpdb.grpInfos[self.grp][self.id]['count'] = bpdb.grpInfos[self.grp][self.id].get('count', 0) + 1
-        
-        #setup debugger
-        self.displayContextHint(displayPrefix=displayPrefix)
-        bpdb.set_trace(frameCount=frameCount+1)
+    try:
+        enabled = __dev__
+        enabled = ConfigVariableBool('force-breakpoints', enabled).getValue()
+    finally:
+        return enabled
+bpdb.setEnabledCallback(bpdbGetEnabled)
+bpdb.setConfigCallback(lambda cfg: ConfigVariableBool('want-bp-%s' % (cfg,), 0).getValue())
 
-bpdb = BpDb()
 
 import __builtin__
 __builtin__.Functor = Functor