Add a decorator decorator that checks func args
This patch adds a decorator for decorators that allows specifying the arg names that the function being decorated must accept. If the function being decorated does not accept the specified args, an exception will be thrown during 'decoration', meaning import time usually. If however the function accepts variable number of arguments, the decorator will be a no-op as it is not possible to guess what will be passed at runtime. This is however good enough for Nova's compute manager decorators that inspired this work, as they decorate methods with well defined and even versioned arguments. Several decorators used in compute manager are now fortified with this "compile time" check. Partial-bug:#1296593 Change-Id: I00ebcc8a092fa0894ba8764699093f59a46b8647
This commit is contained in:
parent
793f517f5d
commit
7cf7d4a6db
|
@ -225,6 +225,7 @@ wrap_exception = functools.partial(exception.wrap_exception,
|
|||
get_notifier=get_notifier)
|
||||
|
||||
|
||||
@utils.expects_func_args('migration')
|
||||
def errors_out_migration(function):
|
||||
"""Decorator to error out migration on failure."""
|
||||
|
||||
|
@ -255,6 +256,7 @@ def errors_out_migration(function):
|
|||
return decorated_function
|
||||
|
||||
|
||||
@utils.expects_func_args('instance')
|
||||
def reverts_task_state(function):
|
||||
"""Decorator to revert task_state on failure."""
|
||||
|
||||
|
@ -308,6 +310,7 @@ def wrap_instance_fault(function):
|
|||
return decorated_function
|
||||
|
||||
|
||||
@utils.expects_func_args('instance')
|
||||
def wrap_instance_event(function):
|
||||
"""Wraps a method to log the event taken on the instance, and result.
|
||||
|
||||
|
@ -331,6 +334,7 @@ def wrap_instance_event(function):
|
|||
return decorated_function
|
||||
|
||||
|
||||
@utils.expects_func_args('image_id', 'instance')
|
||||
def delete_image_on_error(function):
|
||||
"""Used for snapshot related method to ensure the image created in
|
||||
compute.api is deleted when an error occurs.
|
||||
|
|
|
@ -709,6 +709,52 @@ class WrappedCodeTestCase(test.NoDBTestCase):
|
|||
self.assertIn('blue', func_code.co_varnames)
|
||||
|
||||
|
||||
class ExpectedArgsTestCase(test.NoDBTestCase):
|
||||
def test_passes(self):
|
||||
@utils.expects_func_args('foo', 'baz')
|
||||
def dec(f):
|
||||
return f
|
||||
|
||||
@dec
|
||||
def func(foo, bar, baz="lol"):
|
||||
pass
|
||||
|
||||
def test_raises(self):
|
||||
@utils.expects_func_args('foo', 'baz')
|
||||
def dec(f):
|
||||
return f
|
||||
|
||||
def func(bar, baz):
|
||||
pass
|
||||
|
||||
self.assertRaises(TypeError, dec, func)
|
||||
|
||||
def test_var_no_of_args(self):
|
||||
@utils.expects_func_args('foo')
|
||||
def dec(f):
|
||||
return f
|
||||
|
||||
@dec
|
||||
def func(bar, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def test_more_layers(self):
|
||||
@utils.expects_func_args('foo', 'baz')
|
||||
def dec(f):
|
||||
return f
|
||||
|
||||
def dec_2(f):
|
||||
def inner_f(*a, **k):
|
||||
return f()
|
||||
return inner_f
|
||||
|
||||
@dec_2
|
||||
def func(bar, baz):
|
||||
pass
|
||||
|
||||
self.assertRaises(TypeError, dec, func)
|
||||
|
||||
|
||||
class StringLengthTestCase(test.NoDBTestCase):
|
||||
def test_check_string_length(self):
|
||||
self.assertIsNone(utils.check_string_length(
|
||||
|
|
|
@ -914,6 +914,27 @@ def get_wrapped_function(function):
|
|||
return _get_wrapped_function(function)
|
||||
|
||||
|
||||
def expects_func_args(*args):
|
||||
def _decorator_checker(dec):
|
||||
@functools.wraps(dec)
|
||||
def _decorator(f):
|
||||
base_f = get_wrapped_function(f)
|
||||
arg_names, a, kw, _default = inspect.getargspec(base_f)
|
||||
if a or kw or set(args) <= set(arg_names):
|
||||
# NOTE (ndipanov): We can't really tell if correct stuff will
|
||||
# be passed if it's a function with *args or **kwargs so
|
||||
# we still carry on and hope for the best
|
||||
return dec(f)
|
||||
else:
|
||||
raise TypeError("Decorated function %(f_name)s does not "
|
||||
"have the arguments expected by the "
|
||||
"decorator %(d_name)s" %
|
||||
{'f_name': base_f.__name__,
|
||||
'd_name': dec.__name__})
|
||||
return _decorator
|
||||
return _decorator_checker
|
||||
|
||||
|
||||
class ExceptionHelper(object):
|
||||
"""Class to wrap another and translate the ClientExceptions raised by its
|
||||
function calls to the actual ones.
|
||||
|
|
Loading…
Reference in New Issue