Rolling your ownΒΆ

Creating your own validator is easy. Keep these rules in mind:

  • Subclass xcheck
  • Capture custom attributes inthe __call__ method
  • Append any custom attributes to the _object_atts list
  • write a checkContent method
  • write a normalizeContent method

Note

There is no mechanism for loading custom checkers with the load_chekcer() function, but the to_definition_node() method should generate something.

For example, xcheck does not have a built-in ComplexCheck class.

Lets assume that we want to allow None as well. We want to be able to return a string representation, a complex number, and a (real, imag) tuple when validating an object.

import xcheck


class ComplexError(xcheck.XCheckError): pass

class ComplexCheck(xcheck.XCheck):
    def __init__(self, name, **kwargs):
        self.allow_none = kwargs.pop('allow_none', True)
        if 'error' not in kwargs:
            kwargs['error'] = ComplexError
        xcheck.XCheck.__init__(self, name, **kwargs)

        # extend _object_atts for to_node() to work
        self._object_atts.extend(['allow_none'])

    def __call__(self, item, **kwargs):
        self._as_string = kwargs.pop('as_string', False)
        self._as_complex = kwargs.pop('as_complex', False)
        self._as_tuple = kwargs.pop('as_tuple', False)

        if any((self._as_string, self._as_complex, self._as_tuple)):
            kwargs['normalize'] = True
        else:
            kwargs['normalize'] = False

        return xcheck.XCheck.__call__(self, item, **kwargs)

    def check_content(self, item):
        ok = None
        complex_number = None
        is_none = False

        # handle any acceptable None items
        if item is None or str(item).lower().strip() == 'none':
            if self.allow_none:
                ok = True
                self._normalized_value = "None"
                is_none = True

            else:
                ok = False
                raise self.error("ComplexCheck cannot accept None")

        # handle everything else
        if ok is None:
            try:
                self._value = complex(item)
                self._normalized_value = self.normalize_content()
                ok = True
            except Exception as E:
                ok = False
                raise E

        return ok

    def normalize_content(self):
        if self._as_string:
            return str(self._value)
        elif self._as_complex:
            return self._value
        elif self._as_tuple:
            return (self._value.real, self._value.imag)
        else:
            return self._value # always returns the complex number

A simple set of unit tests can verify your new checker is working.

ET = xcheck.ET
import unittest

class ComplexCheckTC(unittest.TestCase):
    def setUp(self):
        self.c = ComplexCheck('origin')

    def tearDown(self):
        del self.c

    def test_default(self):
        self.assertTrue(self.c.allow_none)
        self.assertTrue(issubclass(self.c.error, ComplexError))

    def test_acceptable_values(self):
        self.assertTrue(self.c(None))
        self.assertTrue(self.c('none'))
        self.assertTrue(self.c(1))
        self.assertTrue(self.c((1+0j)))
        self.assertTrue(self.c(3j))
        text = '1+2j'
        node_text = '<origin>1+2j</origin>'
        node = ET.fromstring(node_text)
        self.assertTrue(self.c(text))
        self.assertTrue(self.c(node_text))
        self.assertTrue(self.c(node))

    def test_failures(self):
        self.assertRaises(ValueError, self.c, 'not a number')
        self.assertRaises(xcheck.MismatchedTagError, self.c, '<o>1+4j</o>')

        bad_text = '<origin>not a number</origin>'
        bad_node = ET.fromstring(bad_text)

        self.assertRaises(ValueError, self.c, bad_text)
        self.assertRaises(ValueError, self.c, bad_node)


    def test_normalizations(self):
        self.assertIsInstance(self.c(1+2j, as_string=True), basestring)
        self.assertIsInstance(self.c(1+2j, as_complex=True), complex)
        self.assertIsInstance(self.c(1+2j, as_tuple=True), tuple)
        self.assertIsInstance(self.c(1+2j, normalize=True), bool)




if __name__=='__main__':
    unittest.main(verbosity=1)

Previous topic

Errors

Next topic

Calling an Checker – Under the Hood

This Page