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

StackOverflow https://stackoverflow.com/questions/22463574

  •  16-06-2023
  •  | 
  •  

Question

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
Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top