From a9a746759161845fcf2c3ce07fcb1ceced423f8c Mon Sep 17 00:00:00 2001 From: drosengarten Date: Mon, 6 Jan 2020 10:49:03 +0000 Subject: [PATCH 1/9] Update test_checks.py --- tests/test_checks.py | 146 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 137 insertions(+), 9 deletions(-) diff --git a/tests/test_checks.py b/tests/test_checks.py index b5d445f..3e148ba 100644 --- a/tests/test_checks.py +++ b/tests/test_checks.py @@ -77,6 +77,134 @@ def test_has_no_x(): tm.assert_frame_equal(result, df + 2) +class TestFlexibleDecorator: + + df_pass = pd.DataFrame( [1] ) + df_fail = pd.DataFrame( [0] ) + + def test_return_single_df_none(self): + + @dc.HasNoX( df=None, values=[0] ) + def _func( x ): + return x + + assert _func( self.df_pass ) is self.df_pass + with pytest.raises( AssertionError ): + _func( self.df_fail ) + + with pytest.raises( AttributeError ): + _func( pd.Series([1]) ) + + def test_return_single_df_default(self): + + @dc.HasNoX( values=[0] ) + def _func( x ): + return x + + assert _func( self.df_pass ) is self.df_pass + with pytest.raises( AssertionError ): + _func( self.df_fail ) + + with pytest.raises( AttributeError ): + _func( pd.Series([1]) ) + + def test_required_dataframe_arg(self): + + @dc.HasNoX( df='df0', values=[0] ) + def _func( df0 ): + return df0 + + assert _func( self.df_pass ) is self.df_pass + assert _func( df0=self.df_pass ) is self.df_pass + + with pytest.raises( AssertionError ): + _func( self.df_fail ) + with pytest.raises( AssertionError ): + _func( df0=self.df_fail ) + + with pytest.raises( TypeError ): + _func() + + assert _func( None ) is None + assert _func( df0=None) is None + + with pytest.raises( AttributeError ): + _func( df0=pd.Series([1]) ) + + def test_optional_dataframe_arg(self): + + @dc.HasNoX( df='df_opt', values=[0] ) + def _func( x, df_opt=None ): + return df_opt + + assert _func( 99 ) is None + assert _func( x=99 ) is None + assert _func( 99, None ) is None + assert _func( 99, df_opt=None ) is None + assert _func( df_opt=None, x=99 ) is None + + assert _func( 99, self.df_pass ) is self.df_pass + assert _func( 99, df_opt=self.df_pass ) is self.df_pass + assert _func( df_opt=self.df_pass, x=99 ) is self.df_pass + + with pytest.raises( AssertionError ): + _func( 99, self.df_fail ) + with pytest.raises( AssertionError ): + _func( 99, df_opt=self.df_fail ) + with pytest.raises( AssertionError ): + _func( df_opt=self.df_fail, x=99 ) + + with pytest.raises( AttributeError ): + _func( 99, df_opt='hello world' ) + + def test_optional_dataframe_wrong_name(self): + with pytest.raises( NameError ): + @dc.HasNoX( df='df0', values=[0] ) + def _func( x, df1=None ): + return df1 + + def test_optional_dataframe_wrong_type(self): + + @dc.HasNoX( df='df_opt', values=[0] ) + def _func( x, df_opt=1 ): + return df_opt + + with pytest.raises( AttributeError ): + _func( 99 ) + + assert _func( 99, None ) is None + assert _func( 99, self.df_pass ) is self.df_pass + + with pytest.raises( AssertionError ): + _func( 99, self.df_fail ) + + def test_func_returns_tuple(self): + + @dc.HasNoX(df=1, values=[0]) + def _func(x): + return x + + x = ( 99, self.df_pass ) + assert _func( x ) is x + with pytest.raises( AssertionError ): + _func( (99, self.df_fail) ) + + with pytest.raises( IndexError ): + _func( (self.df_pass, ) ) + + x = [ 99, self.df_pass ] + with pytest.raises( TypeError ): + _func( x ) + + x = pd.Series( [None, self.df_pass], index=[1,0] ) + with pytest.raises( TypeError ): + _func( x ) + + def test_df_is_not_int(self): + with pytest.raises( TypeError ): + dc.HasNoX( df=1.0, values=[0] ) + + def test_has_no_nans(): df = pd.DataFrame(np.random.randn(5, 3)) result = ck.has_no_nans(df) @@ -522,24 +650,24 @@ def f(df, length): return df df = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]}) - df2 = pd.DataFrame({"a": [7, np.nan, 9], "b": [10, 11, 12]}) - tm.assert_frame_equal(df, ck.custom_check(df, f, length=2)) - tm.assert_frame_equal(df, ck.custom_check(df, f, 2)) + + tm.assert_frame_equal(df, ck.custom_check(f, df=df, length=2)) + tm.assert_frame_equal(df, ck.custom_check(f, df, 2)) # order is verify_func, verif_kwargs, decorated_func tm.assert_frame_equal(df, dc.CustomCheck(f, length=2)(_noop)(df)) tm.assert_frame_equal(df, dc.CustomCheck(f, 2)(_noop)(df)) - @dc.CustomCheck(f, 10, enabled=False) - def append_a_df(df, df2): - return df.append(df2, ignore_index=True) - result = append_a_df(df, df2) - tm.assert_frame_equal(df.append(df2, ignore_index=True), result) + + + + + with pytest.raises(AssertionError): - ck.custom_check(df, f, length=4) + ck.custom_check(f, df=df, length=4) with pytest.raises(AssertionError): dc.CustomCheck(f, 4)(_noop)(df) From 64a2aa0f6c2f68aaea3d990b0e191f6dbd78c313 Mon Sep 17 00:00:00 2001 From: drosengarten Date: Mon, 6 Jan 2020 12:01:51 +0000 Subject: [PATCH 2/9] added examples for location of dataframe checks --- README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.md b/README.md index 10c4984..2be664b 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,33 @@ on the functions you're already writing: return result_df ``` +The checks can also be performed on an item in a returned tuple with a dataframe as +one of the values: +```python + import bulwark.decorators as dc + + @dc.IsShape((-1, 10)) + @dc.IsMonotonic(df=0,strict=True) + @dc.HasNoNans(df=0) # the number specifies the tuple of the returned list/tuple + @dc.HasNoNans(df=1) + def compute(df): + # complex operations to determine result + ... + return (result_df1, result_df2) +``` +The checks can also be applied to input parameters as well: +```python + import bulwark.decorators as dc + + @dc.IsShape(df='df_input',shape=(-1, 10)) + @dc.IsMonotonic(df='df_input',strict=True) + @dc.HasNoNans(df='df_input') # the name specifies the name of the parameter + def compute(df_input): + # complex operations to determine result + ... + return result_df +``` + Still want to have more robust test files? Bulwark's got you covered there, too, with importable functions. From 49284e3a6719dd22cae8bc6b9e016de004e16735 Mon Sep 17 00:00:00 2001 From: drosengarten Date: Mon, 6 Jan 2020 12:08:15 +0000 Subject: [PATCH 3/9] can now specify location of dataframe to check str: arg name of the input argument to the decorated function to check int: the entry in a tuple returned by the decorated function to check None: check the single dataframe return by the decorated function (default functionality for backward compatibility) --- bulwark/decorators.py | 93 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 83 insertions(+), 10 deletions(-) diff --git a/bulwark/decorators.py b/bulwark/decorators.py index 4718a05..6321858 100644 --- a/bulwark/decorators.py +++ b/bulwark/decorators.py @@ -1,30 +1,103 @@ """Generates decorators for each check in `checks.py`.""" import functools import sys -from inspect import getfullargspec, getmembers, isfunction +from inspect import getfullargspec, getmembers, isfunction, signature, \ + Parameter import bulwark.checks as ck from bulwark.generic import snake_to_camel class BaseDecorator(object): + + df_allowed_types = ( int, str, type(None) ) + def __init__(self, *args, **kwargs): self.enabled = kwargs.pop("enabled", True) # setter to enforce bool would be a lot safer # self.warn = False ? No - put at func level for all funcs and pass through - - self.check_func_params = dict( - zip(getfullargspec(self.check_func).args[1:], args)) - self.check_func_params.update(**kwargs) - + self.params = getfullargspec(self.check_func).args[1:] + + self.__dict__.update(dict(zip(self.params, args))) + self.__dict__.update(**kwargs) + + if type( self._df ) not in self.df_allowed_types: + msg = "'df' arg cannot by of type {}.\n" \ + "Only allowed types are:\n" \ + " str: arg name of the input argument to the decorated function to check\n" \ + " int: the entry in a tuple returned by the decorated function to check\n" \ + " None: check the single dataframe return by the decorated function".format(type(self._df)) + raise TypeError(msg) + + @property + def _df(self): + return self.__dict__.get( 'df', None ) + + + + def __call__(self, f): + + if type(self._df) is str: + if not self._has_arg_name( self._df, f ): + raise NameError( "'{}' is not an arg to function '{}'".format(self._df, f.__name__) ) + @functools.wraps(f) def decorated(*args, **kwargs): - df = f(*args, **kwargs) - if self.enabled: - self.check_func(df, **self.check_func_params) - return df + + if not self.enabled: + return f(*args, **kwargs) + + check_kwargs = {k: v for k, v in self.__dict__.items() + if k not in ["check_func", "enabled", "params"]} + + if self._df is None: + res = f(*args, **kwargs) + check_kwargs['df'] = res + self.check_func( **check_kwargs ) + return res + + if type( self._df ) is int: + res = f(*args, **kwargs) + if type(res) is not tuple: + raise TypeError('Your function needs to return a tuple to use df= in this decorator') + check_kwargs['df'] = res[ self._df ] + self.check_func( **check_kwargs ) + return res + + if type( self._df ) is str: + df_default = self._get_arg_default( f, self._df ) + df_value = self._get_arg_value( f, self._df, *args, **kwargs ) + if isinstance(df_default, Parameter.empty) or (df_value is not None): + check_kwargs['df'] = df_value + self.check_func(**check_kwargs) + res = f(*args, **kwargs) + return res + + + + return decorated + @staticmethod + def _has_arg_name( arg_name, func ): + sig = signature( func ) + res = arg_name in sig.parameters + return res + + @staticmethod + def _get_arg_default( func, arg_name ): + func_sig = signature( func ) + res = func_sig.parameters[arg_name].default + return res + + @staticmethod + def _get_arg_value( func, arg_name, *args, **kwargs): + func_sig = signature( func ) + bound_args = func_sig.bind(*args, **kwargs) + bound_args.apply_defaults() + res = bound_args.arguments[ arg_name ] + return res + def decorator_factory(decorator_name, func): """Takes in a function and outputs a class that can be used as a decorator.""" From 650586bfd67b5c5805f75383850718715cb5a337 Mon Sep 17 00:00:00 2001 From: drosengarten Date: Mon, 6 Jan 2020 12:13:31 +0000 Subject: [PATCH 4/9] added tests for specifying location of dataframe --- tests/test_checks.py | 273 +++++++++++++++++++++---------------------- 1 file changed, 136 insertions(+), 137 deletions(-) diff --git a/tests/test_checks.py b/tests/test_checks.py index 3e148ba..b0223bc 100644 --- a/tests/test_checks.py +++ b/tests/test_checks.py @@ -77,134 +77,6 @@ def test_has_no_x(): tm.assert_frame_equal(result, df + 2) -class TestFlexibleDecorator: - - df_pass = pd.DataFrame( [1] ) - df_fail = pd.DataFrame( [0] ) - - def test_return_single_df_none(self): - - @dc.HasNoX( df=None, values=[0] ) - def _func( x ): - return x - - assert _func( self.df_pass ) is self.df_pass - with pytest.raises( AssertionError ): - _func( self.df_fail ) - - with pytest.raises( AttributeError ): - _func( pd.Series([1]) ) - - def test_return_single_df_default(self): - - @dc.HasNoX( values=[0] ) - def _func( x ): - return x - - assert _func( self.df_pass ) is self.df_pass - with pytest.raises( AssertionError ): - _func( self.df_fail ) - - with pytest.raises( AttributeError ): - _func( pd.Series([1]) ) - - def test_required_dataframe_arg(self): - - @dc.HasNoX( df='df0', values=[0] ) - def _func( df0 ): - return df0 - - assert _func( self.df_pass ) is self.df_pass - assert _func( df0=self.df_pass ) is self.df_pass - - with pytest.raises( AssertionError ): - _func( self.df_fail ) - with pytest.raises( AssertionError ): - _func( df0=self.df_fail ) - - with pytest.raises( TypeError ): - _func() - - assert _func( None ) is None - assert _func( df0=None) is None - - with pytest.raises( AttributeError ): - _func( df0=pd.Series([1]) ) - - def test_optional_dataframe_arg(self): - - @dc.HasNoX( df='df_opt', values=[0] ) - def _func( x, df_opt=None ): - return df_opt - - assert _func( 99 ) is None - assert _func( x=99 ) is None - assert _func( 99, None ) is None - assert _func( 99, df_opt=None ) is None - assert _func( df_opt=None, x=99 ) is None - - assert _func( 99, self.df_pass ) is self.df_pass - assert _func( 99, df_opt=self.df_pass ) is self.df_pass - assert _func( df_opt=self.df_pass, x=99 ) is self.df_pass - - with pytest.raises( AssertionError ): - _func( 99, self.df_fail ) - with pytest.raises( AssertionError ): - _func( 99, df_opt=self.df_fail ) - with pytest.raises( AssertionError ): - _func( df_opt=self.df_fail, x=99 ) - - with pytest.raises( AttributeError ): - _func( 99, df_opt='hello world' ) - - def test_optional_dataframe_wrong_name(self): - with pytest.raises( NameError ): - @dc.HasNoX( df='df0', values=[0] ) - def _func( x, df1=None ): - return df1 - - def test_optional_dataframe_wrong_type(self): - - @dc.HasNoX( df='df_opt', values=[0] ) - def _func( x, df_opt=1 ): - return df_opt - - with pytest.raises( AttributeError ): - _func( 99 ) - - assert _func( 99, None ) is None - assert _func( 99, self.df_pass ) is self.df_pass - - with pytest.raises( AssertionError ): - _func( 99, self.df_fail ) - - def test_func_returns_tuple(self): - - @dc.HasNoX(df=1, values=[0]) - def _func(x): - return x - - x = ( 99, self.df_pass ) - assert _func( x ) is x - with pytest.raises( AssertionError ): - _func( (99, self.df_fail) ) - - with pytest.raises( IndexError ): - _func( (self.df_pass, ) ) - - x = [ 99, self.df_pass ] - with pytest.raises( TypeError ): - _func( x ) - - x = pd.Series( [None, self.df_pass], index=[1,0] ) - with pytest.raises( TypeError ): - _func( x ) - - def test_df_is_not_int(self): - with pytest.raises( TypeError ): - dc.HasNoX( df=1.0, values=[0] ) - - def test_has_no_nans(): df = pd.DataFrame(np.random.randn(5, 3)) result = ck.has_no_nans(df) @@ -650,24 +522,151 @@ def f(df, length): return df df = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]}) + df2 = pd.DataFrame({"a": [7, np.nan, 9], "b": [10, 11, 12]}) - - tm.assert_frame_equal(df, ck.custom_check(f, df=df, length=2)) - tm.assert_frame_equal(df, ck.custom_check(f, df, 2)) + tm.assert_frame_equal(df, ck.custom_check(df, f, length=2)) + tm.assert_frame_equal(df, ck.custom_check(df, f, 2)) # order is verify_func, verif_kwargs, decorated_func tm.assert_frame_equal(df, dc.CustomCheck(f, length=2)(_noop)(df)) tm.assert_frame_equal(df, dc.CustomCheck(f, 2)(_noop)(df)) + @dc.CustomCheck(f, 10, enabled=False) + def append_a_df(df, df2): + return df.append(df2, ignore_index=True) - - - - - + result = append_a_df(df, df2) + tm.assert_frame_equal(df.append(df2, ignore_index=True), result) with pytest.raises(AssertionError): - ck.custom_check(f, df=df, length=4) + ck.custom_check(df, f, length=4) with pytest.raises(AssertionError): dc.CustomCheck(f, 4)(_noop)(df) + +class TestFlexibleDecorator: + + df_pass = pd.DataFrame( [1] ) + df_fail = pd.DataFrame( [0] ) + + def test_return_single_df_none(self): + + @dc.HasNoX( df=None, values=[0] ) + def _func( x ): + return x + + assert _func( self.df_pass ) is self.df_pass + with pytest.raises( AssertionError ): + _func( self.df_fail ) + + with pytest.raises( AttributeError ): + _func( pd.Series([1]) ) + + def test_return_single_df_default(self): + + @dc.HasNoX( values=[0] ) + def _func( x ): + return x + + assert _func( self.df_pass ) is self.df_pass + with pytest.raises( AssertionError ): + _func( self.df_fail ) + + with pytest.raises( AttributeError ): + _func( pd.Series([1]) ) + + def test_required_dataframe_arg(self): + + @dc.HasNoX( df='df0', values=[0] ) + def _func( df0 ): + return df0 + + assert _func( self.df_pass ) is self.df_pass + assert _func( df0=self.df_pass ) is self.df_pass + + with pytest.raises( AssertionError ): + _func( self.df_fail ) + with pytest.raises( AssertionError ): + _func( df0=self.df_fail ) + + with pytest.raises( TypeError ): + _func() + + assert _func( None ) is None + assert _func( df0=None) is None + + with pytest.raises( AttributeError ): + _func( df0=pd.Series([1]) ) + + def test_optional_dataframe_arg(self): + + @dc.HasNoX( df='df_opt', values=[0] ) + def _func( x, df_opt=None ): + return df_opt + + assert _func( 99 ) is None + assert _func( x=99 ) is None + assert _func( 99, None ) is None + assert _func( 99, df_opt=None ) is None + assert _func( df_opt=None, x=99 ) is None + + assert _func( 99, self.df_pass ) is self.df_pass + assert _func( 99, df_opt=self.df_pass ) is self.df_pass + assert _func( df_opt=self.df_pass, x=99 ) is self.df_pass + + with pytest.raises( AssertionError ): + _func( 99, self.df_fail ) + with pytest.raises( AssertionError ): + _func( 99, df_opt=self.df_fail ) + with pytest.raises( AssertionError ): + _func( df_opt=self.df_fail, x=99 ) + + with pytest.raises( AttributeError ): + _func( 99, df_opt='hello world' ) + + def test_optional_dataframe_wrong_name(self): + with pytest.raises( NameError ): + @dc.HasNoX( df='df0', values=[0] ) + def _func( x, df1=None ): + return df1 + + def test_optional_dataframe_wrong_type(self): + + @dc.HasNoX( df='df_opt', values=[0] ) + def _func( x, df_opt=1 ): + return df_opt + + with pytest.raises( AttributeError ): + _func( 99 ) + + assert _func( 99, None ) is None + assert _func( 99, self.df_pass ) is self.df_pass + + with pytest.raises( AssertionError ): + _func( 99, self.df_fail ) + + def test_func_returns_tuple(self): + + @dc.HasNoX(df=1, values=[0]) + def _func(x): + return x + + x = ( 99, self.df_pass ) + assert _func( x ) is x + with pytest.raises( AssertionError ): + _func( (99, self.df_fail) ) + + with pytest.raises( IndexError ): + _func( (self.df_pass, ) ) + + x = [ 99, self.df_pass ] + with pytest.raises( TypeError ): + _func( x ) + + x = pd.Series( [None, self.df_pass], index=[1,0] ) + with pytest.raises( TypeError ): + _func( x ) + + def test_df_is_not_int(self): + with pytest.raises( TypeError ): + dc.HasNoX( df=1.0, values=[0] ) From 4068740ce74caf3cd6eb2327560c4e09d0fafba3 Mon Sep 17 00:00:00 2001 From: drosengarten Date: Wed, 8 Jan 2020 09:44:50 +0000 Subject: [PATCH 5/9] pep8 white space/line continuation changes --- bulwark/decorators.py | 86 ++++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 47 deletions(-) diff --git a/bulwark/decorators.py b/bulwark/decorators.py index 6321858..1eb6953 100644 --- a/bulwark/decorators.py +++ b/bulwark/decorators.py @@ -1,17 +1,15 @@ """Generates decorators for each check in `checks.py`.""" import functools import sys -from inspect import getfullargspec, getmembers, isfunction, signature, \ - Parameter +from inspect import Parameter, getfullargspec, getmembers, isfunction, signature import bulwark.checks as ck from bulwark.generic import snake_to_camel class BaseDecorator(object): - - df_allowed_types = ( int, str, type(None) ) - + df_allowed_types = (int, str, type(None)) + def __init__(self, *args, **kwargs): self.enabled = kwargs.pop("enabled", True) # setter to enforce bool would be a lot safer # self.warn = False ? No - put at func level for all funcs and pass through @@ -19,83 +17,77 @@ def __init__(self, *args, **kwargs): self.__dict__.update(dict(zip(self.params, args))) self.__dict__.update(**kwargs) - - if type( self._df ) not in self.df_allowed_types: - msg = "'df' arg cannot by of type {}.\n" \ - "Only allowed types are:\n" \ - " str: arg name of the input argument to the decorated function to check\n" \ - " int: the entry in a tuple returned by the decorated function to check\n" \ - " None: check the single dataframe return by the decorated function".format(type(self._df)) + + if type(self._df) not in self.df_allowed_types: + msg = ("'df' arg cannot by of type {}.\n" + "Only allowed types are:\n" + " str: arg name of the input argument to the decorated function to check\n" + " int: the entry in a tuple returned by the decorated function to check\n" + " None: check the single dataframe return by the decorated function".format(type(self._df)) + ) raise TypeError(msg) - + @property def _df(self): - return self.__dict__.get( 'df', None ) - - + return self.__dict__.get('df', None) - def __call__(self, f): - + if type(self._df) is str: - if not self._has_arg_name( self._df, f ): - raise NameError( "'{}' is not an arg to function '{}'".format(self._df, f.__name__) ) - + if not self._has_arg_name(self._df, f): + raise NameError("'{}' is not an arg to function '{}'".format(self._df, f.__name__)) + @functools.wraps(f) def decorated(*args, **kwargs): - + if not self.enabled: return f(*args, **kwargs) - + check_kwargs = {k: v for k, v in self.__dict__.items() if k not in ["check_func", "enabled", "params"]} - + if self._df is None: res = f(*args, **kwargs) check_kwargs['df'] = res - self.check_func( **check_kwargs ) - return res - - if type( self._df ) is int: + self.check_func(**check_kwargs) + return res + + if type(self._df) is int: res = f(*args, **kwargs) if type(res) is not tuple: raise TypeError('Your function needs to return a tuple to use df= in this decorator') - check_kwargs['df'] = res[ self._df ] - self.check_func( **check_kwargs ) + check_kwargs['df'] = res[self._df] + self.check_func(**check_kwargs) return res - - if type( self._df ) is str: - df_default = self._get_arg_default( f, self._df ) - df_value = self._get_arg_value( f, self._df, *args, **kwargs ) + + if type(self._df) is str: + df_default = self._get_arg_default(f, self._df) + df_value = self._get_arg_value(f, self._df, *args, **kwargs) if isinstance(df_default, Parameter.empty) or (df_value is not None): check_kwargs['df'] = df_value self.check_func(**check_kwargs) res = f(*args, **kwargs) return res - - - - return decorated @staticmethod - def _has_arg_name( arg_name, func ): - sig = signature( func ) + def _has_arg_name(arg_name, func): + sig = signature(func) res = arg_name in sig.parameters return res - + @staticmethod - def _get_arg_default( func, arg_name ): - func_sig = signature( func ) + def _get_arg_default(func, arg_name): + func_sig = signature(func) res = func_sig.parameters[arg_name].default return res - + @staticmethod - def _get_arg_value( func, arg_name, *args, **kwargs): - func_sig = signature( func ) + def _get_arg_value(func, arg_name, *args, **kwargs): + func_sig = signature(func) bound_args = func_sig.bind(*args, **kwargs) bound_args.apply_defaults() - res = bound_args.arguments[ arg_name ] + res = bound_args.arguments[arg_name] return res From a9cd6a5640851c1a82ba9e438caccd41465735a1 Mon Sep 17 00:00:00 2001 From: drosengarten Date: Wed, 8 Jan 2020 10:58:11 +0000 Subject: [PATCH 6/9] reduced line lengths to less than 100 --- bulwark/decorators.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bulwark/decorators.py b/bulwark/decorators.py index 1eb6953..bda176e 100644 --- a/bulwark/decorators.py +++ b/bulwark/decorators.py @@ -23,7 +23,8 @@ def __init__(self, *args, **kwargs): "Only allowed types are:\n" " str: arg name of the input argument to the decorated function to check\n" " int: the entry in a tuple returned by the decorated function to check\n" - " None: check the single dataframe return by the decorated function".format(type(self._df)) + " None: check the single dataframe return by" + " the decorated function".format(type(self._df)) ) raise TypeError(msg) @@ -55,7 +56,8 @@ def decorated(*args, **kwargs): if type(self._df) is int: res = f(*args, **kwargs) if type(res) is not tuple: - raise TypeError('Your function needs to return a tuple to use df= in this decorator') + raise TypeError('Your function needs to return' + 'a tuple to use df= in this decorator') check_kwargs['df'] = res[self._df] self.check_func(**check_kwargs) return res From f9568a496e9a5c1305e48b50b9d780adeffdf45c Mon Sep 17 00:00:00 2001 From: drosengarten Date: Wed, 8 Jan 2020 13:46:15 +0000 Subject: [PATCH 7/9] pep8 conformity changes --- tests/test_checks.py | 216 +++++++++++++++++++++---------------------- 1 file changed, 108 insertions(+), 108 deletions(-) diff --git a/tests/test_checks.py b/tests/test_checks.py index b0223bc..f04df83 100644 --- a/tests/test_checks.py +++ b/tests/test_checks.py @@ -487,8 +487,8 @@ def test_multi_check(): tm.assert_frame_equal(df, result) result = dc.MultiCheck(checks={ck.has_no_nans: {"columns": None}, - ck.is_shape: {"shape": (3, 2)}}, - warn=False)(_noop)(df) + ck.is_shape: {"shape": (3, 2)} + }, warn=False)(_noop)(df) tm.assert_frame_equal(df, result) def total_sum_not_equal(df, amt): @@ -509,7 +509,7 @@ def total_sum_not_equal(df, amt): with pytest.raises(AssertionError): result = dc.MultiCheck(checks={ck.has_no_nans: {"columns": None}, - total_sum_not_equal: {"amt": 51}}, + total_sum_not_equal: {"amt": 51}}, warn=False)(_noop)(df) # with pytest.raises(AssertionError): @@ -544,129 +544,129 @@ def append_a_df(df, df2): with pytest.raises(AssertionError): dc.CustomCheck(f, 4)(_noop)(df) + class TestFlexibleDecorator: - - df_pass = pd.DataFrame( [1] ) - df_fail = pd.DataFrame( [0] ) - + + df_pass = pd.DataFrame([1]) + df_fail = pd.DataFrame([0]) + def test_return_single_df_none(self): - - @dc.HasNoX( df=None, values=[0] ) - def _func( x ): + + @dc.HasNoX(df=None, values=[0]) + def _func(x): return x - - assert _func( self.df_pass ) is self.df_pass - with pytest.raises( AssertionError ): - _func( self.df_fail ) - - with pytest.raises( AttributeError ): - _func( pd.Series([1]) ) - + + assert _func(self.df_pass) is self.df_pass + with pytest.raises(AssertionError): + _func(self.df_fail) + + with pytest.raises(AttributeError): + _func(pd.Series([1])) + def test_return_single_df_default(self): - - @dc.HasNoX( values=[0] ) - def _func( x ): + + @dc.HasNoX(values=[0]) + def _func(x): return x - - assert _func( self.df_pass ) is self.df_pass - with pytest.raises( AssertionError ): - _func( self.df_fail ) - - with pytest.raises( AttributeError ): - _func( pd.Series([1]) ) - + + assert _func(self.df_pass) is self.df_pass + with pytest.raises(AssertionError): + _func(self.df_fail) + + with pytest.raises(AttributeError): + _func(pd.Series([1])) + def test_required_dataframe_arg(self): - - @dc.HasNoX( df='df0', values=[0] ) - def _func( df0 ): + + @dc.HasNoX(df='df0', values=[0]) + def _func(df0): return df0 - - assert _func( self.df_pass ) is self.df_pass - assert _func( df0=self.df_pass ) is self.df_pass - - with pytest.raises( AssertionError ): - _func( self.df_fail ) - with pytest.raises( AssertionError ): - _func( df0=self.df_fail ) - - with pytest.raises( TypeError ): + + assert _func(self.df_pass) is self.df_pass + assert _func(df0=self.df_pass) is self.df_pass + + with pytest.raises(AssertionError): + _func(self.df_fail) + with pytest.raises(AssertionError): + _func(df0=self.df_fail) + + with pytest.raises(TypeError): _func() - - assert _func( None ) is None - assert _func( df0=None) is None - - with pytest.raises( AttributeError ): - _func( df0=pd.Series([1]) ) - + + assert _func(None) is None + assert _func(df0=None) is None + + with pytest.raises(AttributeError): + _func(df0=pd.Series([1])) + def test_optional_dataframe_arg(self): - - @dc.HasNoX( df='df_opt', values=[0] ) - def _func( x, df_opt=None ): + + @dc.HasNoX(df='df_opt', values=[0]) + def _func(x, df_opt=None): return df_opt - - assert _func( 99 ) is None - assert _func( x=99 ) is None - assert _func( 99, None ) is None - assert _func( 99, df_opt=None ) is None - assert _func( df_opt=None, x=99 ) is None - - assert _func( 99, self.df_pass ) is self.df_pass - assert _func( 99, df_opt=self.df_pass ) is self.df_pass - assert _func( df_opt=self.df_pass, x=99 ) is self.df_pass - - with pytest.raises( AssertionError ): - _func( 99, self.df_fail ) - with pytest.raises( AssertionError ): - _func( 99, df_opt=self.df_fail ) - with pytest.raises( AssertionError ): - _func( df_opt=self.df_fail, x=99 ) - - with pytest.raises( AttributeError ): - _func( 99, df_opt='hello world' ) - + + assert _func(99) is None + assert _func(x=99) is None + assert _func(99, None) is None + assert _func(99, df_opt=None) is None + assert _func(df_opt=None, x=99) is None + + assert _func(99, self.df_pass) is self.df_pass + assert _func(99, df_opt=self.df_pass) is self.df_pass + assert _func(df_opt=self.df_pass, x=99) is self.df_pass + + with pytest.raises(AssertionError): + _func(99, self.df_fail) + with pytest.raises(AssertionError): + _func(99, df_opt=self.df_fail) + with pytest.raises(AssertionError): + _func(df_opt=self.df_fail, x=99) + + with pytest.raises(AttributeError): + _func(99, df_opt='hello world') + def test_optional_dataframe_wrong_name(self): - with pytest.raises( NameError ): - @dc.HasNoX( df='df0', values=[0] ) - def _func( x, df1=None ): + with pytest.raises(NameError): + @dc.HasNoX(df='df0', values=[0]) + def _func(x, df1=None): return df1 - + def test_optional_dataframe_wrong_type(self): - @dc.HasNoX( df='df_opt', values=[0] ) - def _func( x, df_opt=1 ): + @dc.HasNoX(df='df_opt', values=[0]) + def _func(x, df_opt=1): return df_opt - - with pytest.raises( AttributeError ): - _func( 99 ) - - assert _func( 99, None ) is None - assert _func( 99, self.df_pass ) is self.df_pass - - with pytest.raises( AssertionError ): - _func( 99, self.df_fail ) + + with pytest.raises(AttributeError): + _func(99) + + assert _func(99, None) is None + assert _func(99, self.df_pass) is self.df_pass + + with pytest.raises(AssertionError): + _func(99, self.df_fail) def test_func_returns_tuple(self): - @dc.HasNoX(df=1, values=[0]) def _func(x): return x - - x = ( 99, self.df_pass ) - assert _func( x ) is x - with pytest.raises( AssertionError ): - _func( (99, self.df_fail) ) - - with pytest.raises( IndexError ): - _func( (self.df_pass, ) ) - - x = [ 99, self.df_pass ] - with pytest.raises( TypeError ): - _func( x ) - - x = pd.Series( [None, self.df_pass], index=[1,0] ) - with pytest.raises( TypeError ): - _func( x ) - + + x = (99, self.df_pass) + assert _func(x) is x + with pytest.raises(AssertionError): + _func((99, self.df_fail)) + + with pytest.raises(IndexError): + _func((self.df_pass, )) + + x = [99, self.df_pass] + with pytest.raises(TypeError): + _func(x) + + x = pd.Series([None, self.df_pass], index=[1, 0]) + with pytest.raises(TypeError): + _func(x) + def test_df_is_not_int(self): - with pytest.raises( TypeError ): - dc.HasNoX( df=1.0, values=[0] ) + with pytest.raises(TypeError): + dc.HasNoX(df=1.0, values=[0]) From 9bbbf267de174c28981238814ed42996d659eb04 Mon Sep 17 00:00:00 2001 From: drosengarten Date: Fri, 10 Jan 2020 00:06:13 +0000 Subject: [PATCH 8/9] fix pre-commit hook failures removed trailing spaces and ordered imports --- README.md | 14 +++++++------- bulwark/decorators.py | 3 ++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 2be664b..161ae28 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ Bulwark's Documentation ======================================== -downloads +downloads latest release supported python versions @@ -63,11 +63,11 @@ on the functions you're already writing: @dc.HasNoNans() def compute(df): # complex operations to determine result - ... + return result_df ``` -The checks can also be performed on an item in a returned tuple with a dataframe as +The checks can also be performed on an item in a returned tuple with a dataframe as one of the values: ```python import bulwark.decorators as dc @@ -78,7 +78,7 @@ one of the values: @dc.HasNoNans(df=1) def compute(df): # complex operations to determine result - ... + return (result_df1, result_df2) ``` The checks can also be applied to input parameters as well: @@ -90,7 +90,7 @@ The checks can also be applied to input parameters as well: @dc.HasNoNans(df='df_input') # the name specifies the name of the parameter def compute(df_input): # complex operations to determine result - ... + return result_df ``` @@ -110,7 +110,7 @@ Nope - just toggle the built-in "enabled" flag available for every decorator. @dc.IsShape((3, 2), enabled=False) def compute(df): # complex operations to determine result - ... + return result_df ``` @@ -174,5 +174,5 @@ We work hard to make contributing as easy as possible, and previous open source experience is not required! Please see [contributing.md](docs/contributing.md) for how to get started. -Thank you to all our past contributors, especially these folks: +Thank you to all our past contributors, especially these folks: [![](https://sourcerer.io/fame/ZaxR/ZaxR/bulwark/images/0)](https://sourcerer.io/fame/ZaxR/ZaxR/bulwark/links/0)[![](https://sourcerer.io/fame/ZaxR/ZaxR/bulwark/images/1)](https://sourcerer.io/fame/ZaxR/ZaxR/bulwark/links/1)[![](https://sourcerer.io/fame/ZaxR/ZaxR/bulwark/images/2)](https://sourcerer.io/fame/ZaxR/ZaxR/bulwark/links/2)[![](https://sourcerer.io/fame/ZaxR/ZaxR/bulwark/images/3)](https://sourcerer.io/fame/ZaxR/ZaxR/bulwark/links/3)[![](https://sourcerer.io/fame/ZaxR/ZaxR/bulwark/images/4)](https://sourcerer.io/fame/ZaxR/ZaxR/bulwark/links/4)[![](https://sourcerer.io/fame/ZaxR/ZaxR/bulwark/images/5)](https://sourcerer.io/fame/ZaxR/ZaxR/bulwark/links/5)[![](https://sourcerer.io/fame/ZaxR/ZaxR/bulwark/images/6)](https://sourcerer.io/fame/ZaxR/ZaxR/bulwark/links/6)[![](https://sourcerer.io/fame/ZaxR/ZaxR/bulwark/images/7)](https://sourcerer.io/fame/ZaxR/ZaxR/bulwark/links/7) diff --git a/bulwark/decorators.py b/bulwark/decorators.py index bda176e..18609c5 100644 --- a/bulwark/decorators.py +++ b/bulwark/decorators.py @@ -1,7 +1,8 @@ """Generates decorators for each check in `checks.py`.""" import functools import sys -from inspect import Parameter, getfullargspec, getmembers, isfunction, signature +from inspect import (Parameter, getfullargspec, getmembers, isfunction, + signature) import bulwark.checks as ck from bulwark.generic import snake_to_camel From 1d46ba3a8fb1c7b23af145cc6aa00a27d239a835 Mon Sep 17 00:00:00 2001 From: drosengarten Date: Fri, 10 Jan 2020 00:33:47 +0000 Subject: [PATCH 9/9] fix autopep8 precommit failure added indent on test_multi_check parameter --- tests/test_checks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_checks.py b/tests/test_checks.py index f04df83..55fb147 100644 --- a/tests/test_checks.py +++ b/tests/test_checks.py @@ -509,7 +509,7 @@ def total_sum_not_equal(df, amt): with pytest.raises(AssertionError): result = dc.MultiCheck(checks={ck.has_no_nans: {"columns": None}, - total_sum_not_equal: {"amt": 51}}, + total_sum_not_equal: {"amt": 51}}, warn=False)(_noop)(df) # with pytest.raises(AssertionError):