Source code for globalsub

"""Global substitution for Python unit tests

Provides substitution in all dictionaries throughout the interpreter,
this includes references held by objects and modules.  Restores those 
references which were replaced (only), i.e. you can re-use the objects
you used as substitutions after restoring.


Sample usage for a test case:

    import globalsub as gs
    class Test(TestCase):
        def setUp( self ):
            gs.subs( somefunction, replacement )
        def tearDown( self ):
            gs.restore( somefunction )

"""
__version__ = '1.0.1'
import gc,logging 
log = logging.getLogger(__name__)

__all__ = ['subs','restore','List']

[docs]class List( list ): """Use to wrap your lists so that they can be subs'd"""
SUBS_TYPES = (str,unicode,int,long,float,tuple,complex,set,bool,)
[docs]def subsable( x ): """For builtin types that do not allow arbitrary attributes, subclass and copy""" if x.__class__ in SUBS_TYPES: # Note the explicit check for *precisely* these classes, *not* isinstance cls = type( 'Subs', (x.__class__,), {} ) return cls( x ) elif isinstance( x, list ): raise TypeError( """Use globalsub.PropList( mylist ) to make your list subs-compatible""" ) return x
[docs]def subs( function, replacement=None ): """Replaces function in all namespaces with replacement function -- function to replace replacement -- if provided, is used as the replacement object, otherwise a function returning None is constructed Used to stub out functionality globally, this function uses global_replace to find all references to function and replace them with replacement. Use restore( replacement ) to restore function references. Note: Only references in namespaces will be replaced, references in anything other than dictionaries will not be replaced. returns replacement """ _mocker_replace_ = False if replacement is None: def replacement( *args, **named ): return None replacement.__name__ = function.__name__ else: replacement = subsable( replacement ) if function is not replacement: replacement.__is_subs__ = True replacement.original = [function,replace_filter(replacement)] global_replace( function, replacement ) return replacement return function
[docs]def restore( function ): """Restore previously subs'd function in all namespaces function -- the replacement function which was substituted returns resolved function """ _mocker_replace_ = False new,filters = resolve( function ) if new is not function: global_replace( function, new, filters ) return new
def resolve( function ): """Find original function from the function or subs of the function""" _mocker_replace_ = False seen = {} all_filters = {} while hasattr( function, 'original' ): seen[id(function)] = True function,filters = function.original all_filters.update( filters ) if id(function) in seen: log.warn( 'Seem to have created a substituation loop on %s', function) break return function,all_filters
[docs]def global_replace(remove, install, filter=None): """Replace object 'remove' with object 'install' on all dictionaries. """ _mocker_replace_ = False for referrer in gc.get_referrers(remove): if ( type(referrer) is dict and referrer.get("_mocker_replace_", True) ): for key, value in list(referrer.iteritems()): if value is remove: if filter and id(referrer) in filter: if key in filter[id(referrer)]: continue # next key referrer[key] = install
[docs]def replace_filter( install ): """Calculate which instances should *not* be replaced Looks for the replacement (install) in namespaces, marking the key values in those namespaces as not-to-be-replaced. """ _mocker_replace_ = False blocks = {} # id(referrer): keys_to_skip for referrer in gc.get_referrers(install): if ( type(referrer) is dict and referrer.get("_mocker_replace_", True) ): for key, value in list(referrer.iteritems()): if value is install: blocks.setdefault(id(referrer),[]).append( key ) return blocks