user611726
user611726

Reputation:

Decorating a module on import without affecting functions implemented in terms of other functions

I've got an interface that I'm working with to declares several verification methods. For simplicity's sake, we can consider three methods. verify_less_than(), verify_equal(), and verify_less_than_equal(). (See below for an implementation). When I import these, I would like to decorate them such that if a verification fails - verify_less_than( 10, 5 ) - an exception will be raised.

I've got this working for the first two, but the third function is giving me a much harder time. Because verify_less_than_equal is defined in terms of the previous two methods, if the first call fails - verify_less_than_equal( 5, 5 ) - an exception will be thrown before the second gets called.

Any help on this would be greatly appreciated

Example code:

module.py

class needs_decoration():
    def verify_less_than( self, x, y ):
        return( x < y )

    def verify_equal( self, x, y ):
        return( x == y )

    def verify_less_than_equal( self, x, y ):
        return( self.verify_less_than( x, y ) or self.verify_equal( x, y ) )

implementation.py

import types
import module

def decorate( fn ):
    def wrapped( self, x, y ):
        res = fn( self, x, y )
        if res == False:
            raise Exception( 'Verification failed!' )
        return res
    return wrapped

for k, v in vars( module.needs_decoration ).items():
    if isinstance( v, types.FunctionType ):
        if not '__init__' in str( vars( module.needs_decoration )[ k ] ):
            vars( module.needs_decoration )[ k ] = decorate( v )

verifier = module.needs_decoration()
verifier.verify_less_than_equal( 5, 5 ) # This will raise an exception, and I would like it not to

Upvotes: 3

Views: 86

Answers (1)

unutbu
unutbu

Reputation: 880577

You could define private (underscored) methods which will remain undecorated, so that _verify_less_than_equal can call the undecorated functions. The for-loop then can add the public API (methods without underscores) which are decorated versions of the private methods:

import types

def add_decorators(cls):
    def decorate(fn):
        def wrapped(self, x, y):
            res = fn(self, x, y)
            if not res:
                raise ValueError('Verification failed! {}({}, {}) is False'
                                .format(fn.__name__, x, y))
            return res
        return wrapped

    for k, v in vars(cls).items():
        if isinstance(v, types.FunctionType):
            if k.startswith('__'): continue
            if k.startswith('_'):
                setattr(cls, k[1:], decorate(v))
    return cls

@add_decorators
class NeedsDecoration():
    def _verify_less_than(self, x, y):
        return x < y

    def _verify_equal(self, x, y):
        return x == y

    def _verify_less_than_equal(self, x, y):
        return self._verify_less_than(x, y) or self._verify_equal(x, y)

    def __init__(self): pass
verifier = NeedsDecoration()
assert verifier.verify_less_than_equal(5, 5)

As desired, the last line does not raise an exception.

Upvotes: 1

Related Questions