I'm writing an api and was wondering what's the most pythonic way to do the following.
I'm writing a bunch of methods to do various web calls, the arguments mostly translate into post data keys and values.
The way I've been writing it so far is mostly like this;
def doSomething(self,param1,param2,param3):
payload={"param1":param1,
"param2":param2,
"param3":param3}
return self.request("do/something",payload)
This already has the draw back of having to repeat the parameter names which are subject to change, but this pattern isn't too bad.
The following case is what got me trying to think of a better way. In this case there are 4 optional arguments for the call
def doSomethingElse(self,param1,param2=None,param3=None,param4=None,param5=None):
payload= {"param1":param1}
if param2:
payload["param2"]= param2
if param3:
payload["param3"]= param3
# ... etc ...
self.request("do/something/else",payload)
My first thought was to do this:
def doSomethingElse(self,param1,**params):
payload = {"param1":param1}
payload.update(params)
self.request("do/something/else",payload)
or even:
def doSomethingElse(self,**payload):
self.request("do/something/else",payload)
Although the second one is nice and simple, the method can be called without the non-default argument. But in both cases I lose the method signature when using the api and the user won't know what the parameters are (I know I could write the expected signature in a docstring but I'd rather prevent misspelt keywords getting sent).
I'm thinking there must be a nice pythonic solution to do this, any ideas?
EDIT
I think a key point which I didn't make clear enough is that the arguments are getting sent in post data in a call, and I want to make sure only those keys can get sent, like in the first example of doSomethingElse, you can't send anything other than those 5 named parameters.
The Pythonic way is to name the parameters when you call the function, not in the function signature:
def doSomething(self, **kwargs):
self.request("do/something/else", kwargs)
doSomething(param1=3, param2='one', param3=4)
How about simply
def get_payload(ldict):
return {k:v for k,v in ldict.iteritems() if k != 'self' and v is not None}
class fred(object):
some_class_var = 17
def method(self, a, b=2):
payload = get_payload(locals())
print payload
which gives
>>> f = fred()
>>> f.method()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: method() takes at least 2 arguments (1 given)
>>> f.method(2)
{'a': 2, 'b': 2}
>>> f.method(2, b=3)
{'a': 2, 'b': 3}
>>> f.method(5, b=None)
{'a': 5}
>>> f.method(2, b=3, c=19)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: method() got an unexpected keyword argument 'c'
>>> help(f.method)
Help on method method in module __main__:
method(self, a, b=2) method of __main__.fred instance
which I think matches your criteria. The next step would be to use a decorator (probably with either wraps or the decorator module to preserve the signature) so that payload was computed and then passed, but I don't know if #payload would be all that much better than payload = get_payload(locals()). Note that using locals() this way, it needs to be done at the start.
I second the feeling that this isn't exactly the best way to prevent unwanted nuclear attacks, though.
Something like this, perhaps:
def doSomethingElse(self, param1, **params):
payload = {"param1": param1}
for name, value in params.items():
if value is not None:
payload[name] = value
self.request("do/something/else", payload)
If you have several such functions, you can do as following:
class Requester(object):
def __init__(self, tobecalled, *allowed):
self.tobecalled = tobecalled
self.allowed = set(allowed)
def __call__(self, otherobj, **k):
for kw in k.iterkeys():
if kw not in self.allowed:
raise ValueError("unknown argument(s) given: %s" % kw)
otherobj.request(self.tobecalled, **k)
def __get__(self, outside, outsideclass):
return lambda **k: self(outside, **k)
class Outside(object):
def request(self, method, **k):
print method, k
do_one_thing = Requester("do/one/thing", 'param1', 'param2')
do_nonsense = Requester("do/nonsense", 'param3')
simple = Requester("simple")
o = Outside()
o.do_one_thing(param1=1, param2=2)
o.do_nonsense(param3=12)
o.simple()
try: o.do_one_thing(rparam1=1, param2=2)
except ValueError, e: print e
try: o.do_nonsense(gparam3=12)
except ValueError, e: print e
try: o.simple(whatever=12)
except ValueError, e: print e
What happens here? We create a Requester object which plays the role of a method: if we put it in another class (here: Outside), it can be called in a way that it also gets a reference of an object which it is called on. What I call outside here is "the outer self", as I call it now. And then, it returns a lambda which calls the object itself, just like a function does. And there, the arguments are checked for validity, and if that passes, we do the call on the "outside"'s request() method.
Related
I just wrote a small function that returns its own arguments as a dict:
from inspect import signature
class MyClass:
def MyFunc(self, thing1=0, thing2=0, thing3=0, thing4="", thing5=""):
P = {}
for p in list(signature(self.MyFunc).parameters):
P[p] = eval(p)
return P
Setting aside why anyone would want to do that (and accepting that I've distilled a very simple example out of a broader context to explore a very specific question), there's an explicit reference self.MyFunc there.
I've seen complicated ways of avoiding that like:
globals()[inspect.getframeinfo(inspect.currentframe()).function]
and
globals()[sys._getframe().f_code.co_name]
but I wonder if there's something like the anonymous super() construct Python offers to reference the method of the same name in a parent class, that works for elegantly permitting a function to refer to itself, anonymously, i.e. without having to name itself.
I suspect not, that there is no way to do this as of Python 3.8. But thought this a worthwhile question to table and explore and invite correction of my suspicion on.
No such construct exists. Code in a function has no special way to refer to that function.
Execution of a function doesn't actually involve the function itself, after initial startup. After startup, all that's needed from the function is the code object, and that's the only part the stack frame keeps a reference to. You can't recover the function from just the code object - many functions can share the same code object.
You can do it with a decorator that adds the parameter list to those passed to the method.
The same approach could be extended into a class decorator that did it to some or all of the methods of the class.
Here's an example implementation of the single-method decorator:
from inspect import signature
def add_paramlist(func):
paramlist = list(signature(func).parameters)
try:
paramlist.remove('paramlist')
except ValueError as exc:
raise RuntimeError(f'"paramlist" argument not declareed in signature of '
f'{func.__name__}() method') from exc
def wrapped(*args, **kwargs):
return func(paramlist, *args, **kwargs)
return wrapped
class MyClass:
#add_paramlist
def MyFunc(paramlist, self, thing1=0, thing2=0, thing3=0, thing4="", thing5=""):
P = {}
for p in paramlist:
P[p] = eval(p)
return P
from pprint import pprint
inst = MyClass()
res = inst.MyFunc(thing1=2, thing2=2, thing3=2, thing4="2", thing5="2")
pprint(res)
Output:
{'self': <__main__.MyClass object at 0x00566B38>,
'thing1': 2,
'thing2': 2,
'thing3': 2,
'thing4': '2',
'thing5': '2'}
As user2357112 says,you can't have any hack-less way to get a name of a function from within that function,but if you just want a function to return its arguments as a dict, you can use this:
class MyClass:
def MyFunc(self,**kwargs):
return kwargs
or if you want to use the *args:
class MyClass:
def MyFunc(self,*args,**kwargs):
names=["thing%d"%i for i in range(1,6)]
for v,k in zip(args,names):
if k in kwargs:
raise ValueError
else:
kwargs[k]=v
return kwargs
Using a hack including locals:
class MyClass:
def MyFunc(self, thing1=0, thing2=0, thing3=0, thing4="", thing5=""):
d=locals().copy()
del d["self"]
return d
I am trying to test some code that makes an external call. I want to mock that call out. The call takes keyword args, so I wrote this little helper function in my test:
def mock_function(*args, **kwargs)
io_obj = StringIO()
for k,v in kwargs.iteritems():
io_obj.write("{}: {}\n".format(k, v)
print "\n{}".format(io_obj.getvalue()) # for testing purposes
return io_obj
in my setUp function for the test class, I have this:
#patch('function_to_test')
def setUp(self, mock_dude):
self.mock_client = mock_dude.return_value
self.mock_client.function_to_test.side_effect = mock_function
self.client = ClientClass()
in my test function, I am calling the function that calls the external function.
I get the printout from mock_function, so I know that I am mocking the function correctly. My question is this:
How can I get at the io_obj that is created in mock_function? My external function doesn't return anything.
The Mock object actually captures the arguments it's called with, so you don't need to write your own function to do that. You can access the arguments directly using Mock.call_args, or assert that the mock was called with certain arguments using assert_called_with.
Example:
>>> m = mock.Mock()
>>> m(1,2,3)
<Mock name='mock()' id='139905514719504'>
>>> m.call_args
call(1, 2, 3)
>>> m.assert_called_with(1,2,4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib64/python2.6/site-packages/mock.py", line 835, in assert_called_with
raise AssertionError(msg)
AssertionError: Expected call: mock(1, 2, 4)
Actual call: mock(1, 2, 3)
I want to give user API for my library with easier way to distinguish different types of parameters which I pass to function. All groups of arguments are defined earlier (for now I have 3 groups), but attributes of them need to be constructed on run. I can do this in Django ORM style, where double underscore separates 2 parts of parameter. But it is very unreadable. Example:
def api_function(**kwargs):
""" Separate passed arguments """
api_function(post__arg1='foo', api__arg1='bar', post_arg2='foo2')
Better way do this SQLAlchemy, but only to compare attributes and all args are defined earlier. Example:
class API(object):
arg1 = Arg()
arg2 = Arg()
class Post(object): #...
def api_function(*args):
""" Separate passed arguments """
api_function(POST.arg1=='foo', API.arg1=='bar', POST.arg2=='foo2')
What I would like to achive is behaviour like this:
class API(object): # Magic
class POST(object): # Magic
def api_function(*args):
""" Separate passed arguments """
api_function(POST.arg1='foo', API.arg1='bar', POST.arg2='foo2')
What have I tried:
declare metamodel with defined __setattr__, but it rise on evaluation SyntaxError: keyword can't be an expression
declare __set__, but it is designed for known attributes
My questions are:
Is it even possible in Python to work like in third snippet?
If not, is there any really close solution to look like in third snippet? The best way should use assignment operator API.arg1='foo', the worst API(arg1='foo')
Requirements -- should work at least at Python 2.7. Good to work on Python 3.2.
EDIT1
My first test, which is using equality operator (but it NEVER should be use in this way):
class APIMeta(type):
def __getattr__(cls, item):
return ApiData(item, None)
class API(object):
__metaclass__ = APIMeta
def __init__(self, key, value):
self.key = key
self.value = value
def __str__(self):
return "{0}={1}".format(self.key, self.value)
def __eq__(self, other):
self.value = other
return self
def print_api(*api_data):
for a in api_data:
print(str(a))
print_api(API.page=='3', API=='bar')
It is working right, but using == is suggesting that I want to compare something and I want to assign value.
NOTE: I don't know how much I like this schema you want. But I know one annoying thing will be all the imports to call api_function. E.G. from api import POST, API, api_function
As I said in the comments, the first way is not possible. This is because assignment (=) is a statement not an expression, so it can't return a value. Source
But the other way you asked for certainly is:
class POST(object):
def __init__(self, **kwargs):
self.args = kwargs
# You'll also probably want to make this function a little safer.
def __getattr__(self, name):
return self.args[name]
def api_function(*args):
# Update this to how complicated the handling needs to be
# but you get the general idea...
post_data = None
for a in args:
if isinstance(a, POST):
post_data = a.args
if post_data is None:
raise Exception('This function needs a POST object passed.')
print post_data
Using it:
>>> api_function('foo')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in api_function
Exception: This function needs a POST object passed.
>>> api_function(POST(arg1='foo'))
{'arg1': 'foo'}
>>> api_function(POST(arg1='foo',
... arg2='bar'
... )
... )
{'arg1': 'foo', 'arg2': 'bar'}
Here's my solution. It's not the best in design, as the structure of the argument grouper is nested quite deep, so I'd appreciate feedback on it:
class ArgumentGrouper(object):
"""Transforms a function so that you can apply arguments in named groups.
This system isn't tested as thoroughly as something with so many moving
parts should be. Use at own risk.
Usage:
#ArgumentGrouper("foo", "bar")
def method(regular_arg, foo__arg1, bar__arg2):
print(regular_arg + foo__arg1 + bar__arg2)
method.foo(", ").bar("world!")("Hello")() # Prints "Hello, world!"
"""
def __call__(self, func):
"""Decorate the function."""
return self.Wrapper(func, self.argument_values)
def __init__(self, *argument_groups):
"""Constructor.
argument_groups -- The names of argument groups in the function.
"""
self.argument_values = {i: {} for i in argument_groups}
class Wrapper(object):
"""This is the result of decorating the function. You can call group
names as function to supply their keyword arguments.
"""
def __call__(self, *args):
"""Execute the decorated function by passing any given arguments
and predefined group arguments.
"""
kwargs = {}
for group, values in self.argument_values.items():
for name, value in values.items():
# Add a new argument in the form foo__arg1 to kwargs, as
# per the supplied arguments.
new_name = "{}__{}".format(
group,
name
)
kwargs[new_name] = value
# Invoke the function with the determined arguments.
return self.func(*args, **kwargs)
def __init__(self, func, argument_values):
"""Constructor.
func -- The decorated function.
argument_values -- A dict with the current values for group
arguments. Must be a reference to the actual dict, since each
WrappedMethod uses it.
"""
self.func = func
self.argument_values = argument_values
def __getattr__(self, name):
"""When trying to call `func.foo(arg1="bar")`, provide `foo`. TODO:
This would be better handled at initialization time.
"""
if name in self.argument_values:
return self.WrappedMethod(name, self, self.argument_values)
else:
return self.__dict__[name]
class WrappedMethod(object):
"""For `func.foo(arg1="bar")`, this is `foo`. Pretends to be a
function that takes the keyword arguments to be supplied to the
decorated function.
"""
def __call__(self, **kwargs):
"""`foo` has been called, record the arguments passed."""
for k, v in kwargs.items():
self.argument_values[self.name][k] = v
return self.wrapper
def __init__(self, name, wrapper, argument_values):
"""Constructor.
name -- The name of the argument group. (This is the string
"foo".)
wrapper -- The decorator. We need this so that we can return it
to chain calls.
argument_values -- A dict with the current values for group
arguments. Must be a reference to the actual dict, since
each WrappedMethod uses it.
"""
self.name = name
self.wrapper = wrapper
self.argument_values = argument_values
# Usage:
#ArgumentGrouper("post", "api")
def api_function(regular_arg, post__arg1, post__arg2, api__arg3):
print("Got regular args {}".format(regular_arg))
print("Got API args {}, {}, {}".format(post__arg1, post__arg2, api__arg3))
api_function.post(
arg1="foo", arg2="bar"
).api(
arg3="baz"
)
api_function("foo")
Then, usage:
#ArgumentGrouper("post", "api")
def api_function(regular_arg, post__arg1, post__arg2, api__arg3):
print("Got regular args {}".format(regular_arg))
print("Got API args {}, {}, {}".format(post__arg1, post__arg2, api__arg3))
api_function.post(
arg1="foo", arg2="bar"
).api(
arg3="baz"
)
api_function("foo")
Output:
Got regular args foo
Got API args foo, bar, baz
It should be simple to scrape argument group names by introspection.
You'll notice the argument naming convention is hardcoded into the WrappedMethod, so you'll have to make sure you're okay with that.
You can also invoke it in one statement:
api_function.post(
arg1="foo", arg2="bar"
).api(
arg3="baz"
)("foo")
Or you could add a dedicated run method which would invoke it, which would just take the place of Wrapper.__call__.
Python don't allow to use assignment operator inside any other code, so:
(a=1)
func((a=1))
will rise SyntaxError. This means that it is not possible to use it in this way. Moreover:
func(API.arg1=3)
Will be treated that left side of assignment is argument API.arg1 which is not valid name in Python for variables. Only solution is to make this in SQLAlchemy style:
func({
API.arg1: 'foo',
API.arg2: 'bar',
DATA.arg1: 'foo1',
})
or
func(**{
API.arg1: 'foo',
API.arg2: 'bar',
DATA.arg1: 'foo1',
})
or just only:
func( API(arg1='foo', arg2='bar'), POST(arg1='foo1'), POST(arg2='bar1'))
Thank you for your interest and answers.
I'm dreaming of a Python method with explicit keyword args:
def func(a=None, b=None, c=None):
for arg, val in magic_arg_dict.items(): # Where do I get the magic?
print '%s: %s' % (arg, val)
I want to get a dictionary of only those arguments the caller actually passed into the method, just like **kwargs, but I don't want the caller to be able to pass any old random args, unlike **kwargs.
>>> func(b=2)
b: 2
>>> func(a=3, c=5)
a: 3
c: 5
So: is there such an incantation? In my case, I happen to be able to compare each argument against its default to find the ones that are different, but this is kind of inelegant and gets tedious when you have nine arguments. For bonus points, provide an incantation that can tell me even when the caller passes in a keyword argument assigned its default value:
>>> func(a=None)
a: None
Tricksy!
Edit: The (lexical) function signature has to remain intact. It's part of a public API, and the primary worth of the explicit keyword args lies in their documentary value. Just to make things interesting. :)
I was inspired by lost-theory's decorator goodness, and after playing about with it for a bit came up with this:
def actual_kwargs():
"""
Decorator that provides the wrapped function with an attribute 'actual_kwargs'
containing just those keyword arguments actually passed in to the function.
"""
def decorator(function):
def inner(*args, **kwargs):
inner.actual_kwargs = kwargs
return function(*args, **kwargs)
return inner
return decorator
if __name__ == "__main__":
#actual_kwargs()
def func(msg, a=None, b=False, c='', d=0):
print msg
for arg, val in sorted(func.actual_kwargs.iteritems()):
print ' %s: %s' % (arg, val)
func("I'm only passing a", a='a')
func("Here's b and c", b=True, c='c')
func("All defaults", a=None, b=False, c='', d=0)
func("Nothin'")
try:
func("Invalid kwarg", e="bogon")
except TypeError, err:
print 'Invalid kwarg\n %s' % err
Which prints this:
I'm only passing a
a: a
Here's b and c
b: True
c: c
All defaults
a: None
b: False
c:
d: 0
Nothin'
Invalid kwarg
func() got an unexpected keyword argument 'e'
I'm happy with this. A more flexible approach is to pass the name of the attribute you want to use to the decorator, instead of hard-coding it to 'actual_kwargs', but this is the simplest approach that illustrates the solution.
Mmm, Python is tasty.
Here is the easiest and simplest way:
def func(a=None, b=None, c=None):
args = locals().copy()
print args
func(2, "egg")
This give the output: {'a': 2, 'c': None, 'b': 'egg'}.
The reason args should be a copy of the locals dictionary is that dictionaries are mutable, so if you created any local variables in this function args would contain all of the local variables and their values, not just the arguments.
More documentation on the built-in locals function here.
One possibility:
def f(**kw):
acceptable_names = set('a', 'b', 'c')
if not (set(kw) <= acceptable_names):
raise WhateverYouWantException(whatever)
...proceed...
IOW, it's very easy to check that the passed-in names are within the acceptable set and otherwise raise whatever you'd want Python to raise (TypeError, I guess;-). Pretty easy to turn into a decorator, btw.
Another possibility:
_sentinel = object():
def f(a=_sentinel, b=_sentinel, c=_sentinel):
...proceed with checks `is _sentinel`...
by making a unique object _sentinel you remove the risk that the caller might be accidentally passing None (or other non-unique default values the caller could possibly pass). This is all object() is good for, btw: an extremely-lightweight, unique sentinel that cannot possibly be accidentally confused with any other object (when you check with the is operator).
Either solution is preferable for slightly different problems.
How about using a decorator to validate the incoming kwargs?
def validate_kwargs(*keys):
def entangle(f):
def inner(*args, **kwargs):
for key in kwargs:
if not key in keys:
raise ValueError("Received bad kwarg: '%s', expected: %s" % (key, keys))
return f(*args, **kwargs)
return inner
return entangle
###
#validate_kwargs('a', 'b', 'c')
def func(**kwargs):
for arg,val in kwargs.items():
print arg, "->", val
func(b=2)
print '----'
func(a=3, c=5)
print '----'
func(d='not gonna work')
Gives this output:
b -> 2
----
a -> 3
c -> 5
----
Traceback (most recent call last):
File "kwargs.py", line 20, in <module>
func(d='not gonna work')
File "kwargs.py", line 6, in inner
raise ValueError("Received bad kwarg: '%s', expected: %s" % (key, keys))
ValueError: Received bad kwarg: 'd', expected: ('a', 'b', 'c')
This is easiest accomplished with a single instance of a sentry object:
# Top of module, does not need to be exposed in __all__
missing = {}
# Function prototype
def myFunc(a = missing, b = missing, c = missing):
if a is not missing:
# User specified argument a
if b is missing:
# User did not specify argument b
The nice thing about this approach is that, since we're using the "is" operator, the caller can pass an empty dict as the argument value, and we'll still pick up that they did not mean to pass it. We also avoid nasty decorators this way, and keep our code a little cleaner.
There's probably better ways to do this, but here's my take:
def CompareArgs(argdict, **kwargs):
if not set(argdict.keys()) <= set(kwargs.keys()):
# not <= may seem weird, but comparing sets sometimes gives weird results.
# set1 <= set2 means that all items in set 1 are present in set 2
raise ValueError("invalid args")
def foo(**kwargs):
# we declare foo's "standard" args to be a, b, c
CompareArgs(kwargs, a=None, b=None, c=None)
print "Inside foo"
if __name__ == "__main__":
foo(a=1)
foo(a=1, b=3)
foo(a=1, b=3, c=5)
foo(c=10)
foo(bar=6)
and its output:
Inside foo
Inside foo
Inside foo
Inside foo
Traceback (most recent call last):
File "a.py", line 18, in
foo(bar=6)
File "a.py", line 9, in foo
CompareArgs(kwargs, a=None, b=None, c=None)
File "a.py", line 5, in CompareArgs
raise ValueError("invalid args")
ValueError: invalid args
This could probably be converted to a decorator, but my decorators need work. Left as an exercise to the reader :P
Perhaps raise an error if they pass any *args?
def func(*args, **kwargs):
if args:
raise TypeError("no positional args allowed")
arg1 = kwargs.pop("arg1", "default")
if kwargs:
raise TypeError("unknown args " + str(kwargs.keys()))
It'd be simple to factor it into taking a list of varnames or a generic parsing function to use. It wouldn't be too hard to make this into a decorator (python 3.1), too:
def OnlyKwargs(func):
allowed = func.__code__.co_varnames
def wrap(*args, **kwargs):
assert not args
# or whatever logic you need wrt required args
assert sorted(allowed) == sorted(kwargs)
return func(**kwargs)
Note: i'm not sure how well this would work around already wrapped functions or functions that have *args or **kwargs already.
Magic is not the answer:
def funky(a=None, b=None, c=None):
for name, value in [('a', a), ('b', b), ('c', c)]:
print name, value
This works now for those new to this question:
class ensureparams(object):
"""
Used as a decorator with an iterable passed in, this will look for each item
in the iterable given as a key in the params argument of the function being
decorated. It was built for a series of PayPal methods that require
different params, and AOP was the best way to handle it while staying DRY.
>>> #ensureparams(['name', 'pass', 'code'])
... def complex_function(params):
... print(params['name'])
... print(params['pass'])
... print(params['code'])
>>>
>>> params = {
... 'name': 'John Doe',
... 'pass': 'OpenSesame',
... #'code': '1134',
... }
>>>
>>> complex_function(params=params)
Traceback (most recent call last):
...
ValueError: Missing from "params" dictionary in "complex_function": code
"""
def __init__(self, required):
self.required = set(required)
def __call__(self, func):
def wrapper(*args, **kwargs):
if not kwargs.get('params', None):
raise KeyError('"params" kwarg required for {0}'.format(func.__name__))
missing = self.required.difference(kwargs['params'])
if missing:
raise ValueError('Missing from "params" dictionary in "{0}": {1}'.format(func.__name__, ', '.join(sorted(missing))))
return func(*args, **kwargs)
return wrapper
if __name__ == "__main__":
import doctest
doctest.testmod()
def wrapper(params): means you're only going to accept one argument -- and so of course calls with (self, params) just won't work. You need to be able to accept either one or two arguments, e.g., at the very least (if you don't need to support calls w/named args):
def wrapper(one, two=None):
if two is None: params = one
else: params = two
# and the rest as above
You can get much more complex / sophisticated in order to also accept named arguments, but this is much simpler and still "mostly works";-).
Decorators normally look like this:
def wrapper(*args, **kargs):
# Pull what you need out of the argument lists and do stuff with it
func(*args, **kargs)
Then they work with any function passed to them, not just functions with a specific number of arguments or with specific keyword arguments. In this specific case, you may want to do some introspection on the func passed to __call__ to find out if it's a one or two argument function and to make sure the last argument is called 'params'. Then just write wrapper like this:
def wrapper(*args):
params = args[-1]
missing = self.required.difference(params)
if missing:
raise ValueError('Missing from "params" argument: %s' % ', '.join(sorted(missing)))
func(params)
What I did was add *args, **kwargs, and just check for the keys that are required within the 'params' argument via kwargs['params'] after checking that kwargs params exists.
Here's the new version (which works perfectly):
class requiresparams(object):
"""
Used as a decorator with an iterable passed in, this will look for each item
in the iterable given as a key in the params argument of the function being
decorated. It was built for a series of PayPal methods that require
different params, and AOP was the best way to handle it while staying DRY.
>>> #requiresparams(['name', 'pass', 'code'])
... def complex_function(params):
... print(params['name'])
... print(params['pass'])
... print(params['code'])
>>>
>>> params = {
... 'name': 'John Doe',
... 'pass': 'OpenSesame',
... #'code': '1134',
... }
>>>
>>> complex_function(params=params)
Traceback (most recent call last):
...
ValueError: Missing from "params" dictionary: code
"""
def __init__(self, required):
self.required = set(required)
def __call__(self, func):
def wrapper(*args, **kwargs):
if not kwargs.get('params', None):
raise KeyError('"params" kwarg required for {0}'.format(func.__name__))
missing = self.required.difference(kwargs['params'])
if missing:
raise ValueError('Missing from "params" dictionary: %s' % ', '.join(sorted(missing)))
return func(*args, **kwargs)
return wrapper
if __name__ == "__main__":
import doctest
doctest.testmod()