I have been in a similar situation where I can't change the signature or the body of a function I call internally but I want to use the defaults or pass arguments only when they exists(which can get tricky if you plan to pop those keys manually)
This was the cleanest and the most reusable solution I could write.
Simplest solution:
def f(name='Hello Guest'):
print(name)
def A(**kwargs):
# If kwargs has any keys which are None in value, will be removed.
# Keys which don't exists in kwargs won't propagate, hence using default value.
f(**{k:v for k, v in kwargs.items() if v})
A()
# This returns "Hello Guest"
A(**{"name": None})
# This returns "Hello Guest"
A(**{"name": "Someone!"})
# This returns "Someone!"
Inspect solution:
Inspect is a great module if you plan to do something complex with function signature, parameters, etc.
from inspect import signature
# This function is untouched
def f(name='Hello Guest'):
print(name)
# changed the signature so that params can propagate further
def A(**kwargs):
t = signature(f, follow_wrapped=True)
# If kwargs has any key which is None in value,
# it will be replaced with default values for the function.
# Keys which don't exists in kwargs won't propagate.
f(**{k: (v or t.parameters[k].default) for k, v in kwargs.items()})
A()
# This returns "Hello Guest"
A(**{"name": None})
# This returns "Hello Guest"
A(**{"name": "Someone!"})
# This returns "Someone!"