Skip to content

Commit

Permalink
New validators (#37)
Browse files Browse the repository at this point in the history
* Add RangeValidator

* Add NotEmptyValidator

* Add tests for new validators

* Fix formatting

* Update docs

* Bump version

* Ignore more directories when linting

* Fix new linting errors
  • Loading branch information
di committed Aug 11, 2017
1 parent 73ccfd7 commit efb9da6
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 7 deletions.
14 changes: 14 additions & 0 deletions README.rst
Expand Up @@ -244,12 +244,26 @@ Vladiate comes with a few common validators built-in:
:``pattern=r'di^'``:
The regex pattern. Fails for all fields by default.

*class* ``RangeValidator``

Validates whether a field falls within a given range (inclusive). Can handle
integers or floats.

:``low``:
The low value of the range.
:``high``:
The high value of the range.

*class* ``EmptyValidator``

Ensure that a field is always empty. Essentially the same as an empty
``SetValidator``. This is used by default when a field has no
validators.

*class* ``NotEmptyValidator``

The opposite of an ``EmptyValidator``. Ensure that a field is never empty.

*class* ``Ignore``

Always passes validation. Used to explicity ignore a given column.
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
@@ -1,4 +1,4 @@
[flake8]
max-line-length = 80
exclude = *.egg,.state,build
exclude = *.egg,.state,build,.tox
select = E,W,F,N
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -4,7 +4,7 @@
from setuptools import setup, find_packages
from setuptools.command.test import test as TestCommand

__version__ = '0.0.14'
__version__ = '0.0.15'


class PyTest(TestCommand):
Expand Down
4 changes: 2 additions & 2 deletions vladiate/main.py
Expand Up @@ -76,8 +76,8 @@ def _is_package(path):
Is the given path a Python package?
"""
return (
os.path.isdir(path)
and os.path.exists(os.path.join(path, '__init__.py'))
os.path.isdir(path) and
os.path.exists(os.path.join(path, '__init__.py'))
)


Expand Down
27 changes: 26 additions & 1 deletion vladiate/test/test_validators.py
Expand Up @@ -3,7 +3,8 @@

from ..validators import (
CastValidator, EmptyValidator, FloatValidator, Ignore, IntValidator,
RegexValidator, SetValidator, UniqueValidator, Validator
NotEmptyValidator, RangeValidator, RegexValidator, SetValidator,
UniqueValidator, Validator
)
from ..exceptions import BadValidatorException, ValidationException

Expand Down Expand Up @@ -154,6 +155,18 @@ def test_regex_validator_fails(pattern, field):
assert validator.bad == {field}


def test_range_validator_works():
RangeValidator(0, 100).validate("42")


def test_range_validator_fails():
validator = RangeValidator(0, 100)
with pytest.raises(ValidationException):
validator.validate("-42")

assert validator.bad == {'-42'}


def test_empty_validator_works():
EmptyValidator().validate("")

Expand All @@ -166,6 +179,18 @@ def test_empty_validator_fails():
assert validator.bad == {'foo'}


def test_non_empty_validator_works():
NotEmptyValidator().validate("foo")


def test_non_empty_validator_fails():
validator = NotEmptyValidator()
with pytest.raises(ValidationException):
validator.validate("")

assert validator.bad == set()


def test_ignore_validator():
validator = Ignore()
validator.validate("foo")
Expand Down
41 changes: 40 additions & 1 deletion vladiate/validators.py
Expand Up @@ -136,6 +136,27 @@ def bad(self):
return self.failures


class RangeValidator(Validator):
def __init__(self, low, high):
self.fail_count = 0
self.low = low
self.high = high
self.outside = set()

def validate(self, field, row={}):
if not self.low <= float(field) <= self.high:
self.outside.add(field)
raise ValidationException(
"'{}' is not in range {} to {}".format(
field, self.low, self.high
)
)

@property
def bad(self):
return self.outside


class EmptyValidator(Validator):
''' Validates that a field is always empty '''

Expand All @@ -147,13 +168,31 @@ def validate(self, field, row={}):
if field != '':
self.nonempty.add(field)
raise ValidationException(
"'{}' is not an empty string".format(field))
"'{}' is not an empty string".format(field)
)

@property
def bad(self):
return self.nonempty


class NotEmptyValidator(Validator):
''' Validates that a field is not empty '''

def __init__(self):
self.fail_count = 0

def validate(self, field, row={}):
if field == '':
raise ValidationException("Row has emtpy field in column")

@property
def bad(self):
# Return an empty set to conform to the protocol. The 'bad' fields
# would all be empty strings anyways
return set()


class Ignore(Validator):
''' Ignore a given field. Never fails '''

Expand Down
2 changes: 1 addition & 1 deletion vladiate/vlad.py
Expand Up @@ -40,7 +40,7 @@ def _log_validator_failures(self):
self.logger.error(
" {} failed {} time(s) ({:.1%}) on field: '{}'".format(
validator.__class__.__name__, validator.fail_count,
validator.fail_count/self.line_count, field_name))
validator.fail_count / self.line_count, field_name))
invalid = list(validator.bad)
shown = ["'{}'".format(field) for field in invalid[:99]]
hidden = ["'{}'".format(field) for field in invalid[99:]]
Expand Down

0 comments on commit efb9da6

Please sign in to comment.