def test_vq_method__get_validations(self):
        # method must exist
        assert hasattr(ValidateQuantities, "validations")
        assert hasattr(ValidateQuantities, "_get_validations")

        # setup default validations
        default_validations = {
            **self.check_defaults.copy(),
            "units": [self.check_defaults["units"]],
        }

        # setup test cases
        # 'setup' = arguments for `_get_validations`
        # 'output' = expected return from `_get_validations`
        # 'raises' = if `_get_validations` raises an Exception
        # 'warns' = if `_get_validations` issues a warning
        #
        _cases = [
            {
                "descr": "typical call...using 'can_be_negative'",
                "setup": {
                    "function": self.foo,
                    "args": (5, ),
                    "kwargs": {},
                    "validations": {
                        "x": {
                            "units": u.cm,
                            "can_be_negative": False
                        }
                    },
                },
                "output": {
                    "x": {
                        "units": [u.cm],
                        "can_be_negative": False
                    }
                },
            },
            {
                "descr": "typical call...using 'none_shall_pass'",
                "setup": {
                    "function": self.foo,
                    "args": (5, ),
                    "kwargs": {},
                    "validations": {
                        "x": {
                            "units": u.cm,
                            "none_shall_pass": True
                        }
                    },
                },
                "output": {
                    "x": {
                        "units": [u.cm],
                        "none_shall_pass": True
                    }
                },
            },
            {
                "descr": "call w/o value validations",
                "setup": {
                    "function": self.foo,
                    "args": (5, ),
                    "kwargs": {},
                    "validations": {
                        "x": {
                            "units": u.cm
                        }
                    },
                },
                "output": {
                    "x": {
                        "units": [u.cm]
                    }
                },
            },
            {
                "descr": "call w/o unit validations",
                "setup": {
                    "function": self.foo,
                    "args": (5, ),
                    "kwargs": {},
                    "validations": {
                        "x": {
                            "can_be_inf": False
                        }
                    },
                },
                "raises": ValueError,
            },
            {
                "descr": "'none_shall_pass' defined w/ validations",
                "setup": {
                    "function": self.foo,
                    "args": (5, ),
                    "kwargs": {},
                    "validations": {
                        "x": {
                            "units": [u.cm, None]
                        }
                    },
                },
                "output": {
                    "x": {
                        "units": [u.cm],
                        "none_shall_pass": True
                    }
                },
            },
            {
                "descr": "units are defined via function annotations",
                "setup": {
                    "function": self.foo_anno,
                    "args": (5, ),
                    "kwargs": {},
                    "validations": {},
                },
                "output": {
                    "x": {
                        "units": [u.cm]
                    }
                },
            },
            {
                "descr": "define 'validations_on_return",
                "setup": {
                    "function": self.foo,
                    "args": (5, ),
                    "kwargs": {},
                    "validations": {
                        "validations_on_return": {
                            "units": [u.cm, None]
                        }
                    },
                },
                "output": {
                    "validations_on_return": {
                        "units": [u.cm],
                        "none_shall_pass": True
                    }
                },
            },
            {
                "descr": "'none_shall_pass' is inconsistently doubly defined'",
                "setup": {
                    "function": self.foo,
                    "args": (5, ),
                    "kwargs": {},
                    "validations": {
                        "x": {
                            "units": [u.cm, None],
                            "none_shall_pass": False
                        }
                    },
                },
                "raises": ValueError,
            },
            {
                "descr":
                "define both validations on args and validations_on_return",
                "setup": {
                    "function": self.foo,
                    "args": (5, ),
                    "kwargs": {},
                    "validations": {
                        "x": {
                            "units": [u.cm],
                            "none_shall_pass": False
                        },
                        "validations_on_return": {
                            "units": [u.cm],
                            "can_be_zero": False,
                        },
                    },
                },
                "output": {
                    "x": {
                        "units": [u.cm],
                        "none_shall_pass": False
                    },
                    "validations_on_return": {
                        "units": [u.cm],
                        "can_be_zero": False
                    },
                },
            },
        ]  # type: List[Dict[str, Any]]

        for case in _cases:
            sig = inspect.signature(case["setup"]["function"])
            args = case["setup"]["args"]
            kwargs = case["setup"]["kwargs"]
            bound_args = sig.bind(*args, **kwargs)

            vq = ValidateQuantities(**case["setup"]["validations"])
            vq.f = case["setup"]["function"]
            if "warns" in case:
                with pytest.warns(case["warns"]):
                    validations = vq._get_validations(bound_args)
            elif "raises" in case:
                with pytest.raises(case["raises"]):
                    vq._get_validations(bound_args)
    def test_vq_method__validate_quantity(self):

        # method must exist
        assert hasattr(ValidateQuantities, "_validate_quantity")

        # setup default validations
        default_validations = {
            **self.check_defaults.copy(),
            "units": [self.check_defaults["units"]],
        }

        # setup test cases
        # 'setup' = arguments for `_get_validations`
        # 'output' = expected return from `_get_validations`
        # 'raises' = if `_get_validations` raises an Exception
        # 'warns' = if `_get_validations` issues a warning
        #
        _cases = [
            # typical call
            {
                "input": {
                    "args": (5 * u.cm, "arg"),
                    "validations": {
                        **default_validations, "units": [u.cm]
                    },
                },
                "output": 5 * u.cm,
            },
            # argument does not have units, but only one is specified
            {
                "input": {
                    "args": (5, "arg"),
                    "validations": {
                        **default_validations, "units": [u.cm]
                    },
                },
                "output": 5 * u.cm,
                "warns": u.UnitsWarning,
            },
            # argument does not have units and multiple unit validations specified
            {
                "input": {
                    "args": (5, "arg"),
                    "validations": {
                        **default_validations, "units": [u.cm, u.km]
                    },
                },
                "raises": TypeError,
            },
            # units can NOT be applied to argument
            {
                "input": {
                    "args": ({}, "arg"),
                    "validations": {
                        **default_validations, "units": [u.cm]
                    },
                },
                "raises": TypeError,
            },
            # argument has a standard unit conversion
            {
                "input": {
                    "args": (5.0 * u.cm, "arg"),
                    "validations": {
                        **default_validations, "units": [u.km]
                    },
                },
                "output": (5.0 * u.cm).to(u.km),
            },
            # return value is None and not allowed
            {
                "input": {
                    "args": (None, "validations_on_return"),
                    "validations": {
                        **default_validations,
                        "units": [u.cm],
                        "none_shall_pass": False,
                    },
                },
                "raises": ValueError,
            },
            # 'pass_equivalent_units' is True and unit conversion is not performed
            {
                "input": {
                    "args": (5 * u.cm, "arg"),
                    "validations": {
                        **default_validations,
                        "units": [u.km],
                        "pass_equivalent_units": True,
                    },
                },
                "output": 5 * u.cm,
            },
        ]

        # setup wrapped function
        vq = ValidateQuantities()
        vq.f = self.foo

        # perform tests
        for ii, case in enumerate(_cases):
            arg, arg_name = case["input"]["args"]
            validations = case["input"]["validations"]

            if "warns" in case:
                with pytest.warns(case["warns"]):
                    _result = vq._validate_quantity(arg, arg_name, validations)
            elif "raises" in case:
                with pytest.raises(case["raises"]):
                    vq._validate_quantity(arg, arg_name, validations)
                for key in default_validations.keys():
                    if key in case["output"][arg_name]:
                        val = case["output"][arg_name][key]
                    else:
                        val = default_validations[key]

                    assert arg_validations[key] == val

        # method calls `_get_unit_checks` and `_get_value_checks`
        with mock.patch.object(
                CheckUnits, "_get_unit_checks",
                return_value={}) as mock_cu_get, mock.patch.object(
                    CheckValues, "_get_value_checks",
                    return_value={}) as mock_cv_get:
            vq = ValidateQuantities(x=u.cm)
            vq.f = self.foo
            sig = inspect.signature(self.foo)
            bound_args = sig.bind(5)

            assert vq._get_validations(bound_args) == {}
            assert mock_cu_get.called
            assert mock_cv_get.called

    def test_vq_method__validate_quantity(self):

        # method must exist
        assert hasattr(ValidateQuantities, "_validate_quantity")

        # setup default validations
        default_validations = {
Beispiel #4
0
    def test_vq_method__get_validations(self):
        # method must exist
        assert hasattr(ValidateQuantities, 'validations')
        assert hasattr(ValidateQuantities, '_get_validations')

        # setup default validations
        default_validations = {
            **self.check_defaults.copy(), 'units':
            [self.check_defaults['units']]
        }

        # setup test cases
        # 'setup' = arguments for `_get_validations`
        # 'output' = expected return from `_get_validations`
        # 'raises' = if `_get_validations` raises an Exception
        # 'warns' = if `_get_validations` issues a warning
        #
        _cases = [
            {
                'descr': "typical call...using 'can_be_negative'",
                'setup': {
                    'function': self.foo,
                    'args': (5, ),
                    'kwargs': {},
                    'validations': {
                        'x': {
                            'units': u.cm,
                            'can_be_negative': False
                        }
                    },
                },
                'output': {
                    'x': {
                        'units': [u.cm],
                        'can_be_negative': False
                    }
                },
            },
            {
                'descr': "typical call...using 'none_shall_pass'",
                'setup': {
                    'function': self.foo,
                    'args': (5, ),
                    'kwargs': {},
                    'validations': {
                        'x': {
                            'units': u.cm,
                            'none_shall_pass': True
                        }
                    },
                },
                'output': {
                    'x': {
                        'units': [u.cm],
                        'none_shall_pass': True
                    }
                },
            },
            {
                'descr': "call w/o value validations",
                'setup': {
                    'function': self.foo,
                    'args': (5, ),
                    'kwargs': {},
                    'validations': {
                        'x': {
                            'units': u.cm
                        }
                    },
                },
                'output': {
                    'x': {
                        'units': [u.cm]
                    }
                },
            },
            {
                'descr': "call w/o unit validations",
                'setup': {
                    'function': self.foo,
                    'args': (5, ),
                    'kwargs': {},
                    'validations': {
                        'x': {
                            'can_be_inf': False
                        }
                    },
                },
                'raises': ValueError,
            },
            {
                'descr': "'none_shall_pass' defined w/ validations",
                'setup': {
                    'function': self.foo,
                    'args': (5, ),
                    'kwargs': {},
                    'validations': {
                        'x': {
                            'units': [u.cm, None]
                        }
                    },
                },
                'output': {
                    'x': {
                        'units': [u.cm],
                        'none_shall_pass': True
                    }
                },
            },
            {
                'descr': "units are defined via function annotations",
                'setup': {
                    'function': self.foo_anno,
                    'args': (5, ),
                    'kwargs': {},
                    'validations': {},
                },
                'output': {
                    'x': {
                        'units': [u.cm]
                    }
                },
            },
            {
                'descr': "define 'validations_on_return",
                'setup': {
                    'function': self.foo,
                    'args': (5, ),
                    'kwargs': {},
                    'validations': {
                        'validations_on_return': {
                            'units': [u.cm, None]
                        }
                    },
                },
                'output': {
                    'validations_on_return': {
                        'units': [u.cm],
                        'none_shall_pass': True
                    }
                },
            },
            {
                'descr': "'none_shall_pass' is inconsistently doubly defined'",
                'setup': {
                    'function': self.foo,
                    'args': (5, ),
                    'kwargs': {},
                    'validations': {
                        'x': {
                            'units': [u.cm, None],
                            'none_shall_pass': False
                        }
                    },
                },
                'raises': ValueError,
            },
        ]  # type: List[Dict[str, Any]]

        for case in _cases:
            sig = inspect.signature(case['setup']['function'])
            args = case['setup']['args']
            kwargs = case['setup']['kwargs']
            bound_args = sig.bind(*args, **kwargs)

            vq = ValidateQuantities(**case['setup']['validations'])
            vq.f = case['setup']['function']
            if 'warns' in case:
                with pytest.warns(case['warns']):
                    validations = vq._get_validations(bound_args)
            elif 'raises' in case:
                with pytest.raises(case['raises']):
                    vq._get_validations(bound_args)
Beispiel #5
0
    def test_vq_called_as_decorator(self):
        """
        Test behavior of `ValidateQuantities.__call__` (i.e. used as a decorator).
        """
        # setup test cases
        # 'setup' = arguments for `CheckUnits` and wrapped function
        # 'output' = expected return from wrapped function
        # 'raises' = if an Exception is expected to be raised
        # 'warns' = if a warning is expected to be issued
        #
        _cases = [
            {
                'descr': 'clean execution',
                'setup': {
                    'function': self.foo,
                    'args': (2 * u.cm, ),
                    'kwargs': {},
                    'validations': {
                        'x': u.cm,
                        'validations_on_return': u.cm
                    }
                },
                'output': 2 * u.cm,
            },
            {
                'descr': 'call with unit conversion',
                'setup': {
                    'function': self.foo,
                    'args': (2 * u.cm, ),
                    'kwargs': {},
                    'validations': {
                        'x': u.cm,
                        'validations_on_return': u.um
                    }
                },
                'output': (2 * u.cm).to(u.um),
            },
            {
                'descr': 'argument fails checks',
                'setup': {
                    'function': self.foo,
                    'args': (2 * u.cm, ),
                    'kwargs': {},
                    'validations': {
                        'x': u.g,
                        'validations_on_return': u.cm
                    }
                },
                'raises': u.UnitTypeError,
            },
            {
                'descr': 'return fails checks',
                'setup': {
                    'function': self.foo,
                    'args': (2 * u.cm, ),
                    'kwargs': {},
                    'validations': {
                        'x': u.cm,
                        'validations_on_return': u.kg
                    }
                },
                'raises': u.UnitTypeError,
            },
            {
                'descr': 'decomposed units are still covnerted',
                'setup': {
                    'function': self.foo,
                    'args': (2 * u.kg * u.m / u.s**2, ),
                    'kwargs': {},
                    'validations': {
                        'x': u.N
                    }
                },
                'output': (2 * u.kg * u.m / u.s**2).to(u.N),
                'extra assert':
                lambda x: x.unit.to_string() == u.N.to_string(),
            },
        ]

        # perform tests
        for case in _cases:
            validations = case['setup']['validations']
            func = case['setup']['function']
            wfoo = ValidateQuantities(**validations)(func)

            args = case['setup']['args']
            kwargs = case['setup']['kwargs']

            if 'raises' in case:
                with pytest.raises(case['raises']):
                    wfoo(*args, **kwargs)
                continue

            _result = wfoo(*args, **kwargs)
            assert _result == case['output']
            if 'extra assert' in case:
                assert case['extra assert'](_result)
Beispiel #6
0
    def test_vq_method__validate_quantity(self):

        # method must exist
        assert hasattr(ValidateQuantities, '_validate_quantity')

        # setup default validations
        default_validations = {
            **self.check_defaults.copy(), 'units':
            [self.check_defaults['units']]
        }

        # setup test cases
        # 'setup' = arguments for `_get_validations`
        # 'output' = expected return from `_get_validations`
        # 'raises' = if `_get_validations` raises an Exception
        # 'warns' = if `_get_validations` issues a warning
        #
        _cases = [
            # typical call
            {
                'input': {
                    'args': (5 * u.cm, 'arg'),
                    'validations': {
                        **default_validations, 'units': [u.cm]
                    }
                },
                'output': 5 * u.cm
            },

            # argument does not have units, but only one is specified
            {
                'input': {
                    'args': (5, 'arg'),
                    'validations': {
                        **default_validations, 'units': [u.cm]
                    }
                },
                'output': 5 * u.cm,
                'warns': u.UnitsWarning
            },

            # argument does not have units and multiple unit validations specified
            {
                'input': {
                    'args': (5, 'arg'),
                    'validations': {
                        **default_validations, 'units': [u.cm, u.km]
                    }
                },
                'raises': TypeError
            },

            # units can NOT be applied to argument
            {
                'input': {
                    'args': ({}, 'arg'),
                    'validations': {
                        **default_validations, 'units': [u.cm]
                    }
                },
                'raises': TypeError
            },

            # argument has a non-standard unit conversion
            {
                'input': {
                    'args': (5. * u.K, 'arg'),
                    'validations': {
                        **default_validations, 'units': [u.eV],
                        'equivalencies': u.temperature_energy()
                    }
                },
                'output': (5. * u.K).to(u.eV,
                                        equivalencies=u.temperature_energy()),
                'warns': ImplicitUnitConversionWarning
            },

            # argument has a standard unit conversion
            {
                'input': {
                    'args': (5. * u.cm, 'arg'),
                    'validations': {
                        **default_validations, 'units': [u.km]
                    }
                },
                'output': (5. * u.cm).to(u.km)
            },

            # return value is None and not allowed
            {
                'input': {
                    'args': (None, 'validations_on_return'),
                    'validations': {
                        **default_validations, 'units': [u.cm],
                        'none_shall_pass': False
                    }
                },
                'raises': ValueError
            },

            # 'pass_equivalent_units' is True and unit conversion is not performed
            {
                'input': {
                    'args': (5 * u.cm, 'arg'),
                    'validations': {
                        **default_validations, 'units': [u.km],
                        'pass_equivalent_units': True
                    }
                },
                'output': 5 * u.cm
            },
        ]

        # setup wrapped function
        vq = ValidateQuantities()
        vq.f = self.foo

        # perform tests
        for ii, case in enumerate(_cases):
            arg, arg_name = case['input']['args']
            validations = case['input']['validations']

            if 'warns' in case:
                with pytest.warns(case['warns']):
                    warnings.simplefilter(
                        "always", category=ImplicitUnitConversionWarning)
                    _result = vq._validate_quantity(arg, arg_name, validations)
            elif 'raises' in case:
                with pytest.raises(case['raises']):
                    vq._validate_quantity(arg, arg_name, validations)