NCAR / geocat-comp

GeoCAT-comp provides implementations of computational functions for operating on geosciences data. Many of these functions originated in NCL and were translated into Python.
https://geocat-comp.readthedocs.io
Apache License 2.0
122 stars 55 forks source link

Expand Testing for Input Validation #479

Open cyschneck opened 11 months ago

cyschneck commented 11 months ago

Describe the feature you'd like added to this project Expand input validation for existing geoCAT-comp functions with new associated testing

Describe the solution you'd like Expand existing input validation (i.e., float/int vs xarray) across geoCAT-comp functionality with some reasonable custom messages for common errors. New validation should be associated with new testing

Additional context Opened in favor of #254

cyschneck commented 7 months ago

Decorators are a common method to validate input

def validate_user_input(func):
    def validate_wrapper(x, y):
        if x < 0:
            raise ValueError(f"x should be postive {func}")
        return func(x, y)
    return validate_wrapper

@validate_user_input
def average(x, y):
    return (x+y)/2    

average(-1, 0)

Validation output:

  File "/home/user/Desktop/testing.py", line 12, in <module>
    average(-1, 0)
  File "/home/user/Desktop/testing.py", line 4, in validate_wrapper
    raise ValueError(f"x should be postive {func}")
ValueError: x should be postive <function average at 0x7f485d119f80>

Can be expanded for **kwargs and class inheritance

def validate_user_input(*types):
    def validate_acceptable_inputs(func):
        def validate_wrapper(*args, **kwargs):
            for (a, t) in zip(args, types):
                assert isinstance(a, t), f"arg {a} does not match expected type {t}"
            return func(*args, **kwargs)
        return validate_wrapper
    return validate_acceptable_inputs

@validate_user_input((int, float), (int, float))
def testing(x, y):
    return (x * y)

print(testing("3", 2))

Without validation, output=33, with validation that only accepts int and float types

  File "/home/user/Desktop/testing.py", line 14, in <module>
    print(testing("3", 2))
          ^^^^^^^^^^^^^^^
  File "/home/user/Desktop/testing.py", line 5, in validate_wrapper
    assert isinstance(a, t), f"arg {a} does not match expected type {t}"
AssertionError: arg 3 does not match expected type (<class 'int'>, <class 'float'>)

Good breakdown and example