I'm working on an API wrapper for a weather API. Here are two snippets of my code, which don't quite work:
def payload_helper(self, *args, **kwargs):
if args:
paylouad = args
else:
payload = {
"units": "metric",
}
payload.update(kwargs)
return payload
def weather(self, lat="", lon="", zip_code=""):
payload = self.payload_helper()
if lat:
payload = self.payload_helper(payload, lat=lat)
elif lon:
payload = self.payload_helper(payload, lon=lon)
elif zip_code:
payload = self.payload_helper(payload, zip_code=zip_code)
The goal of payload helper is to create a dict that adds a set to:
- if no payload is passed: a new payload dict
- if a payload is passed: update the payload and return the new (merged) one
I know I could solve this if I would just make weather(self, *args, **kwargs)
but I don't want that. I always find it nice if the user that uses the API wrapper also has intelli sense (see what variables the method expects), which is not possible with **kwargs
. I don't have to do this with the if elif else
chain. This was just my approach. There is probably a mew python way of doing this.
CodePudding user response:
The only thing the helper should do is filter out None
-valued arguments to weather
. The "base" payload should be created in weather
itself, then updated using the return value of the helper.
def weather(self, lat=None, lon=None, zip_code=None):
def helper(**kwargs):
return {k: v for k, v in kwargs.items() if v is not None}
payload = {'units': 'metric'}
payload.update(helper(lat=lat, lon=lon, zip_code=zip_code))
...
CodePudding user response:
Your main goal seems to be that you want to create a dictionary that contains all the keyword arguments that were included in the call to your original function.
A very straightforward way to achieve what you want:
def func(a="", b="", c=""):
payload = locals()
# do something else
print(payload)
func(a=1, b='2', c=3.)
Result:
{'a': 1, 'b': '2', 'c': 3.0}
But note that your idea to define the keyword parameters as empty strings means they are implicitly strings. This will lead to a decent IDE giving you all warnings about the types of the arguments being passed.
A better way would be to specify their type, but set them to be None
initially. Optional
helps to allow the None
value in spite of their type:
from typing import Optional
def func(a: Optional[int] = None, b: Optional[str] = None, c: Optional[float] = None):
payload = {name: value for name, value in locals().items() if value is not None}
# do something else
print(payload)
func(b='2', c=3.) # no warnings
Result:
{'b': '2', 'c': 3.0}
Note that the solution above does have the (perhaps unwanted) side effect of including any positional arguments as well. For example:
def func(a: int, b: Optional[int] = None):
payload = locals()
# do something else
print(payload)
func(1, 2)
Result:
{'a': 1, 'b': 2}
It's relevant, because your functions appear to be methods, so they will at least have the positional parameter self
, which would need to be removed:
from typing import Optional
class MyClass:
def func(self, x: Optional[int] = None):
payload = {k: v for k, v in locals().items() if v is not None and k != 'self'}
# do something else
print(payload)
mc = MyClass()
mc.func()
mc.func(1)
Result:
{}
{'x': 1}
Also note that, if you need your keyword arguments to actually be able to have the value None
assigned, the above still need some extra work, and you might look into __defaults__
.
On a more general note: have you looked at dataclasses
at all? Because it seems that what you're ultimately after might be better achieved using those.