diff --git a/owrx/property/__init__.py b/owrx/property/__init__.py index fed36a5..6acb68e 100644 --- a/owrx/property/__init__.py +++ b/owrx/property/__init__.py @@ -24,7 +24,6 @@ class Subscription(object): class PropertyManager(ABC): def __init__(self): self.subscribers = [] - self.validators = {} @abstractmethod def __getitem__(self, item): @@ -83,9 +82,6 @@ class PropertyManager(ABC): except Exception as e: logger.exception(e) - def setValidator(self, name, validator): - self.validators[name] = Validator.of(validator) - class PropertyLayer(PropertyManager): def __init__(self): @@ -153,6 +149,49 @@ class PropertyFilter(PropertyManager): return [k for k in self.pm.keys() if k in self.props] +class PropertyValidationError(Exception): + def __init__(self, key, value): + super().__init__('Invalid value for property "{key}": "{value}"'.format(key=key, value=str(value))) + + +class PropertyValidator(PropertyManager): + def __init__(self, pm: PropertyManager, validators=None): + self.pm = pm + if validators is None: + self.validators = {} + else: + self.validators = {k: Validator.of(v) for k, v in validators.items()} + super().__init__() + + def validate(self, key, value): + if key not in self.validators: + return + if not self.validators[key].isValid(value): + raise PropertyValidationError(key, value) + + def setValidator(self, key, validator): + self.validators[key] = Validator.of(validator) + + def __getitem__(self, item): + return self.pm.__getitem__(item) + + def __setitem__(self, key, value): + self.validate(key, value) + return self.pm.__setitem__(key, value) + + def __contains__(self, item): + return self.pm.__contains__(item) + + def __dict__(self): + return self.pm.__dict__() + + def __delitem__(self, key): + return self.pm.__delitem__(key) + + def keys(self): + return self.pm.keys() + + class PropertyStack(PropertyManager): def __init__(self): super().__init__() diff --git a/test/property/test_property_validator.py b/test/property/test_property_validator.py new file mode 100644 index 0000000..a246031 --- /dev/null +++ b/test/property/test_property_validator.py @@ -0,0 +1,37 @@ +from unittest import TestCase +from owrx.property import PropertyLayer, PropertyValidator, PropertyValidationError +from owrx.property.validators import NumberValidator, StringValidator + + +class PropertyValidatorTest(TestCase): + def testPassesUnvalidated(self): + pm = PropertyLayer() + pv = PropertyValidator(pm) + pv["testkey"] = "testvalue" + self.assertEqual(pv["testkey"], "testvalue") + self.assertEqual(pm["testkey"], "testvalue") + + def testPassesValidValue(self): + pv = PropertyValidator(PropertyLayer(), {"testkey": NumberValidator()}) + pv["testkey"] = 42 + self.assertEqual(pv["testkey"], 42) + + def testThrowsErrorOnInvalidValue(self): + pv = PropertyValidator(PropertyLayer(), {"testkey": NumberValidator()}) + with self.assertRaises(PropertyValidationError): + pv["testkey"] = "text" + + def testSetValidator(self): + pv = PropertyValidator(PropertyLayer()) + pv.setValidator("testkey", NumberValidator()) + with self.assertRaises(PropertyValidationError): + pv["testkey"] = "text" + + def testUpdateValidator(self): + pv = PropertyValidator(PropertyLayer(), {"testkey": StringValidator()}) + # this should pass + pv["testkey"] = "text" + pv.setValidator("testkey", NumberValidator()) + # this should raise + with self.assertRaises(PropertyValidationError): + pv["testkey"] = "text"