From 6f5d80401513dd0f8a7718eda10f75a4b7f71404 Mon Sep 17 00:00:00 2001 From: Peter Vizi Date: Mon, 18 Feb 2013 22:15:05 +0100 Subject: [PATCH] Object validation in separate class Moved validation logic into eeml.validator.Validator so one can turn off these checks using eeml.invalidator.Invalidator. Fixes #24 --- eeml/__init__.py | 50 +++++++++++++--------------------- eeml/invalidator.py | 23 ++++++++++++++++ eeml/validator.py | 66 +++++++++++++++++++++++++++++++++++++++++++++ tests/test_eeml.py | 8 ++++++ 4 files changed, 115 insertions(+), 32 deletions(-) create mode 100644 eeml/invalidator.py create mode 100644 eeml/validator.py diff --git a/eeml/__init__.py b/eeml/__init__.py index b5110d6..9c9149e 100644 --- a/eeml/__init__.py +++ b/eeml/__init__.py @@ -17,12 +17,26 @@ from eeml.namespace import EEML_SCHEMA_VERSION, SCHEMA_LOCATION from eeml.unit import Unit from eeml.util import _elem, _addE, _addA, _assertPosInt +from eeml.validator import Validator + +validator = Validator() + +def validate(validatorMethodName): + def fn(realf): + def wrapper(*args, **kwargs): + realf(*args, **kwargs) + vtor = getattr(validator, validatorMethodName) + vtor(args[0]) + + return wrapper + return fn class Environment(object): """ The Environment element of the document. """ + @validate('environment') def __init__(self, title=None, feed=None, status=None, description=None, icon=None, website=None, email=None, updated=None, creator=None, id_=None, private=None): @@ -54,9 +68,6 @@ def __init__(self, title=None, feed=None, status=None, description=None, """ self._title = title self._feed = feed - if status and status not in ['frozen', 'live']: - raise ValueError("status must be either 'frozen' or 'live', " - "got {}".format(status)) self._status = status self._description = description self._icon = icon @@ -64,16 +75,10 @@ def __init__(self, title=None, feed=None, status=None, description=None, self._email = email self._updated = updated self._creator = creator - _assertPosInt(id_, 'id', False) self._id = id_ self._location = None self._data = dict() - self._private = None - if isinstance(private, bool): - self._private = private - elif private is not None: - raise ValueError("private is expected to be bool, got {}" - .format(type(private))) + self._private = private def setLocation(self, location): """ @@ -200,6 +205,7 @@ class Location(object): """ A class representing the location tag of the document. """ + @validate('location') def __init__(self, domain, name=None, lat=None, lon=None, ele=None, exposure=None, disposition=None): """ @@ -225,20 +231,8 @@ def __init__(self, domain, name=None, lat=None, lon=None, ele=None, self._lat = lat self._lon = lon self._ele = ele - - if exposure is not None and exposure not in ['indoor', 'outdoor']: - raise ValueError("exposure must be 'indoor' or 'outdoor', got '{}'" - .format(exposure)) self._exposure = exposure - - if domain not in ['physical', 'virtual']: - raise ValueError("domain is required, must be 'physical' or 'virtual', got '{}'" - .format(domain)) self._domain = domain - - if disposition is not None and disposition not in ['fixed', 'mobile']: - raise ValueError("disposition must be 'fixed' or 'mobile', got '{}'" - .format(disposition)) self._disposition = disposition def toeeml(self): @@ -266,7 +260,7 @@ class Data(object): """ The Data element of the document """ - + @validate('data') def __init__(self, id_, value, tags=list(), minValue=None, maxValue=None, unit=None, at=None, datapoints=None): """ @@ -287,20 +281,13 @@ def __init__(self, id_, value, tags=list(), minValue=None, maxValue=None, :param datapoints: additional datapoints beyond current_value :type datapoints: `DataPoint` """ - _assertPosInt(id_, 'id', True) self._id = id_ self._value = value self._tags = tags self._minValue = minValue self._maxValue = maxValue - if unit is not None and not isinstance(unit, Unit): - raise ValueError("unit must be an instance of Unit, got {}" - .format(type(unit))) self._unit = unit - if at is not None and not isinstance(at, datetime): - raise ValueError("at must be an instance of datetime.datetime, " - "got {}".format(type(at))) self._at = at self._datapoints = datapoints @@ -339,7 +326,7 @@ class DataPoints(object): """ The DataPoints element of the document """ - + @validate('datapoints') def __init__(self, id_, values=list()): """ Create a new DataPoints. We want to be able to simply add a DataPoints @@ -351,7 +338,6 @@ def __init__(self, id_, values=list()): :param values: the value of the data points, pairs of (value, date), where date is optional :type values: `float` """ - _assertPosInt(id_, 'id', True) self._id = id_ self._values = values diff --git a/eeml/invalidator.py b/eeml/invalidator.py new file mode 100644 index 0000000..14d8fc5 --- /dev/null +++ b/eeml/invalidator.py @@ -0,0 +1,23 @@ +""" +Don't validate any input +""" + + +class Invalidator(object): + """ + Doesn't do much + """ + + def environment(self, env): + pass + + def location(self, loc): + pass + + def data(self, data): + pass + + def datapoints(self, datapoints): + pass + +Validator = Invalidator diff --git a/eeml/validator.py b/eeml/validator.py new file mode 100644 index 0000000..035f3d9 --- /dev/null +++ b/eeml/validator.py @@ -0,0 +1,66 @@ +""" +Here are the validators +""" + +from datetime import datetime + +from eeml.unit import Unit +from eeml.util import _assertPosInt + +import logging + +class Version051(object): + """ + Validate constructors by version 0.5.1 specification + """ + + def environment(self, env): + status = env._status + id_ = env._id + private = env._private + + if status is not None and status not in ['frozen', 'live']: + raise ValueError("status must be either 'frozen' or 'live', " + "got {}".format(status)) + _assertPosInt(id_, 'id', False) + if private is not None and not isinstance(private, bool): + raise ValueError("private is expected to be bool, got {}" + .format(type(private))) + + def location(self, loc): + exposure = loc._exposure + domain = loc._domain + disposition = loc._disposition + # TODO validate lat and lon + + if exposure is not None and exposure not in ['indoor', 'outdoor']: + raise ValueError("exposure must be 'indoor' or 'outdoor', got '{}'" + .format(exposure)) + + if domain not in ['physical', 'virtual']: + raise ValueError("domain is required, must be 'physical' or 'virtual', got '{}'" + .format(domain)) + + if disposition is not None and disposition not in ['fixed', 'mobile']: + raise ValueError("disposition must be 'fixed' or 'mobile', got '{}'" + .format(disposition)) + + def data(self, data): + unit = data._unit + at = data._at + id_ = data._id + + _assertPosInt(id_, 'id', True) + if unit is not None and not isinstance(unit, Unit): + raise ValueError("unit must be an instance of Unit, got {}" + .format(type(unit))) + if at is not None and not isinstance(at, datetime): + raise ValueError("at must be an instance of datetime.datetime, " + "got {}".format(type(at))) + + def datapoints(self, datapoints): + id_ = datapoints._id + + _assertPosInt(id_, 'id', True) + +Validator = Version051 diff --git a/tests/test_eeml.py b/tests/test_eeml.py index 12d8483..ab098a3 100644 --- a/tests/test_eeml.py +++ b/tests/test_eeml.py @@ -12,6 +12,7 @@ from eeml.unit import Celsius, Unit, RH from unittest import TestCase +from unittest import main as UnitTestMain class TestEEML(TestCase): @@ -340,3 +341,10 @@ def test_multiple_update(self): """), etree.fromstring(pac.geteeml()), reporter=self.fail)) + def test_invalidator(self): + import eeml.validator + oldvalidator = eeml.validator + from eeml.invalidator import Invalidator + eeml.validator = Invalidator() + env = Environment(status='foobar') + eeml.validator = oldvalidator