import inspect
from functools import wraps
def dictparams(func):
aspe = None
if func.func_closure is not None:
aspe = inspect.getargspec(func.func_closure[0].cell_contents)
else:
aspe = inspect.getargspec(func)
argnames = aspe.args
needed_argnames = argnames
if aspe.defaults is not None:
needed_argnames = needed_argnames[:-len(aspe.defaults)]
@wraps(func)
def wrapper(*args, **kwargs):
d = {}
if len(args) > 0:
if not isinstance(args[0], dict):
raise TypeError("the only non-keyword arg accepted by %s is (at most) one dict (due to @dictparams decorator)" % func.func_name)
d = dict(args[0])
if len(args) > 1:
raise TypeError("%s can only be used with one dictionary arg and optionally keyword args (due to @dictparams decorator)" % func.func_name)
d.update(kwargs)
for k in d.keys():
if not k in argnames:
if k in kwargs.keys(): # this was explicitly passed, but is not an argument the function takes
raise TypeError("%s got an unexpected keyword argument '%s'" % (func.func_name, k))
del d[k]
# find out which arguments are missing
missing = []
for a in needed_argnames:
if not a in d.keys():
missing.append(a)
if len(missing) > 0:
raise TypeError("%s is missing arguments: %s" % (func.func_name, ",".join(["'"+m+"'" for m in missing])))
return func(**d)
if wrapper.__doc__ is not None:
# this is a rather sad hack cause the function signature of a decorated function is not preserved for the help
doclines = wrapper.__doc__.split('\n')
newdocline = "\nThis function takes the keyword arguments: %s\nA dict containing default parameter values may be passed as the only non-keyword argument." % ", ".join(argnames)
wrapper.__doc__ = '\n'.join([doclines[0], newdocline] + doclines[1:])
return wrapper