It occurred to me that a slightly different but perhaps cleaner idea is to decorate the low-level functions to check them for None values no matter where they get called from. Here's a prototype decorator:
def require_values(*parameter_names):
"""
Check the function call to make sure the specified arguments are not None.
"""
def factory(function):
@functools.wraps(function)
def wrapper(*args, **kwargs):
# match up the argument values with the function parameter names
signature = inspect.signature(function)
# `binding` is now a dict of parameter name -> argument value
binding = signature.bind(*args, **kwargs).arguments
# find which of the specified arguments are none
none_subset = [
param for param in parameter_names if binding[param] is None
]
if none_subset:
funcname = function.__name__
error_template = "Arguments to function `{}` cannot be None: "
error_message = error_template.format(funcname)
raise ValueError(error_message + ", ".join(none_subset))
return function(*args, **kwargs)
return wrapper
return factory
Sorry for the big block of non-highlighted code. It would be used to decorate functions and specify the parameters that cannot be None. Some functions (e.g. irradiance.haydavies) have parameters that are allowed to be None, so the decorator can't just check all arguments. More concretely: it would decorate the functions `irradiance.perez`,
`irradiance.haydavies` and such so that if a user doesn't specify all
the required arguments in `irradiance.get_total_irradiance`, they get a
nicer error message. Something like this:
@require_values('surface_tilt', 'surface_azimuth', 'dhi', 'dni', 'dni_extra')
def haydavies(surface_tilt, surface_azimuth, dhi, dni, dni_extra,
solar_zenith=None, solar_azimuth=None, projection_ratio=None):
...
get_total_irradiance(20, 180, 45, 45, 100, 300, 200, model='haydavies')
Traceback (most recent call last):
...
ValueError: Arguments to function `haydavies` cannot be None: dni_extra
Does this seem like a good idea? I can open a PR to show it in action if so.
Kevin