From 40e531c0daa6bb9ac00eef01cbe4130265d80ed4 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sun, 24 Jan 2021 20:53:51 +0100 Subject: [PATCH] start implementing a validation layer, refs #215 --- owrx/property/__init__.py | 5 +++ owrx/property/validators.py | 42 +++++++++++++++++++ test/property/validators/__init__.py | 0 .../validators/test_integer_validator.py | 15 +++++++ .../validators/test_lambda_validator.py | 21 ++++++++++ .../validators/test_number_validator.py | 18 ++++++++ .../validators/test_string_validator.py | 14 +++++++ test/property/validators/test_validator.py | 16 +++++++ 8 files changed, 131 insertions(+) create mode 100644 owrx/property/validators.py create mode 100644 test/property/validators/__init__.py create mode 100644 test/property/validators/test_integer_validator.py create mode 100644 test/property/validators/test_lambda_validator.py create mode 100644 test/property/validators/test_number_validator.py create mode 100644 test/property/validators/test_string_validator.py create mode 100644 test/property/validators/test_validator.py diff --git a/owrx/property/__init__.py b/owrx/property/__init__.py index f3560fa..fed36a5 100644 --- a/owrx/property/__init__.py +++ b/owrx/property/__init__.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod +from owrx.property.validators import Validator import logging logger = logging.getLogger(__name__) @@ -23,6 +24,7 @@ class Subscription(object): class PropertyManager(ABC): def __init__(self): self.subscribers = [] + self.validators = {} @abstractmethod def __getitem__(self, item): @@ -81,6 +83,9 @@ 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): diff --git a/owrx/property/validators.py b/owrx/property/validators.py new file mode 100644 index 0000000..fce2955 --- /dev/null +++ b/owrx/property/validators.py @@ -0,0 +1,42 @@ +from abc import ABC, abstractmethod + + +class ValidatorException(Exception): + pass + + +class Validator(ABC): + @staticmethod + def of(x): + if isinstance(x, Validator): + return x + if callable(x): + return LambdaValidator(x) + raise ValidatorException("Cannot create validator") + + @abstractmethod + def isValid(self, value): + pass + + +class LambdaValidator(Validator): + def __init__(self, c): + self.callable = c + + def isValid(self, value): + return self.callable(value) + + +class NumberValidator(Validator): + def isValid(self, value): + return isinstance(value, int) or isinstance(value, float) + + +class IntegerValidator(Validator): + def isValid(self, value): + return isinstance(value, int) + + +class StringValidator(Validator): + def isValid(self, value): + return isinstance(value, str) diff --git a/test/property/validators/__init__.py b/test/property/validators/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/property/validators/test_integer_validator.py b/test/property/validators/test_integer_validator.py new file mode 100644 index 0000000..454918a --- /dev/null +++ b/test/property/validators/test_integer_validator.py @@ -0,0 +1,15 @@ +from unittest import TestCase +from owrx.property.validators import IntegerValidator + + +class IntegerValidatorTest(TestCase): + def testPassesIntegers(self): + validator = IntegerValidator() + self.assertTrue(validator.isValid(123)) + self.assertTrue(validator.isValid(-2)) + + def testDoesntPassOthers(self): + validator = IntegerValidator() + self.assertFalse(validator.isValid(.5)) + self.assertFalse(validator.isValid("text")) + self.assertFalse(validator.isValid(object())) diff --git a/test/property/validators/test_lambda_validator.py b/test/property/validators/test_lambda_validator.py new file mode 100644 index 0000000..969dce6 --- /dev/null +++ b/test/property/validators/test_lambda_validator.py @@ -0,0 +1,21 @@ +from unittest import TestCase +from unittest.mock import Mock +from owrx.property.validators import LambdaValidator + + +class LambdaValidatorTest(TestCase): + def testPassesValue(self): + mock = Mock() + validator = LambdaValidator(mock.method) + validator.isValid("test") + mock.method.assert_called_once_with("test") + + def testReturnsTrue(self): + validator = LambdaValidator(lambda x: True) + self.assertTrue(validator.isValid("any value")) + self.assertTrue(validator.isValid(3.1415926)) + + def testReturnsFalse(self): + validator = LambdaValidator(lambda x: False) + self.assertFalse(validator.isValid("any value")) + self.assertFalse(validator.isValid(42)) diff --git a/test/property/validators/test_number_validator.py b/test/property/validators/test_number_validator.py new file mode 100644 index 0000000..3eff11b --- /dev/null +++ b/test/property/validators/test_number_validator.py @@ -0,0 +1,18 @@ +from unittest import TestCase +from owrx.property.validators import NumberValidator + + +class NumberValidatorTest(TestCase): + def testPassesNumbers(self): + validator = NumberValidator() + self.assertTrue(validator.isValid(123)) + self.assertTrue(validator.isValid(-2)) + self.assertTrue(validator.isValid(.5)) + + def testDoesntPassOthers(self): + validator = NumberValidator() + # bool is a subclass of int, so it passes this test. + # not sure if we need to be more specific or if this is alright. + # self.assertFalse(validator.isValid(True)) + self.assertFalse(validator.isValid("text")) + self.assertFalse(validator.isValid(object())) diff --git a/test/property/validators/test_string_validator.py b/test/property/validators/test_string_validator.py new file mode 100644 index 0000000..d285f1a --- /dev/null +++ b/test/property/validators/test_string_validator.py @@ -0,0 +1,14 @@ +from unittest import TestCase +from owrx.property.validators import StringValidator + +class StringValidatorTest(TestCase): + def testPassesStrings(self): + validator = StringValidator() + self.assertTrue(validator.isValid("text")) + + def testDoesntPassOthers(self): + validator = StringValidator() + self.assertFalse(validator.isValid(123)) + self.assertFalse(validator.isValid(-2)) + self.assertFalse(validator.isValid(.5)) + self.assertFalse(validator.isValid(object())) diff --git a/test/property/validators/test_validator.py b/test/property/validators/test_validator.py new file mode 100644 index 0000000..a083889 --- /dev/null +++ b/test/property/validators/test_validator.py @@ -0,0 +1,16 @@ +from unittest import TestCase +from owrx.property.validators import Validator, NumberValidator, LambdaValidator + + +class ValidatorTest(TestCase): + + def testReturnsValidator(self): + validator = NumberValidator() + self.assertIs(validator, Validator.of(validator)) + + def testTransformsLambda(self): + def my_callable(v): + return True + validator = Validator.of(my_callable) + self.assertIsInstance(validator, LambdaValidator) + self.assertTrue(validator.isValid("test"))