From 3ad6fb24707cc2846c2742542d2400c38264bba1 Mon Sep 17 00:00:00 2001 From: blopker Date: Wed, 30 Apr 2014 19:19:55 -0700 Subject: [PATCH] Fix #5: add optional validators to map() --- README.md | 11 ++++-- yamale/__init__.py | 2 +- yamale/schema/schema.py | 17 ++++++--- yamale/tests/fixtures/keywords_bad.yaml | 2 +- yamale/tests/fixtures/map.yaml | 1 + yamale/tests/fixtures/map_bad.yaml | 3 ++ yamale/tests/fixtures/map_good.yaml | 3 ++ yamale/tests/fixtures/types.yaml | 1 + yamale/tests/fixtures/types_bad_data.yaml | 13 +++---- yamale/tests/fixtures/types_good_data.yaml | 3 ++ yamale/tests/functional_tests.py | 40 +++++++++++++++------- yamale/validators/validators.py | 4 +++ 12 files changed, 71 insertions(+), 29 deletions(-) create mode 100644 yamale/tests/fixtures/map.yaml create mode 100644 yamale/tests/fixtures/map_bad.yaml create mode 100644 yamale/tests/fixtures/map_good.yaml diff --git a/README.md b/README.md index 61b1cea..1f2a816 100644 --- a/README.md +++ b/README.md @@ -159,9 +159,6 @@ Validates integers and floats. ### Boolean - `bool()` Validates booleans. -### Map - `map()` -Validates maps. Use when you want a node to contain freeform data. - ### Enum - `enum([primitives])` Validates from a list of constants. - arguments: constants to test equality with @@ -177,6 +174,14 @@ Examples: - `list()`: Validates any list - `list(str(), int())`: Only validates lists that contain strings or integers. +### Map - `map([validators])` +Validates maps. Use when you want a node to contain freeform data. Similar to `List`, `Map` also takes a number of validators to +run against it's children nodes. A child validates if at least one validator passes. + +Examples: +- `map()`: Validates any map +- `map(str(), int())`: Only validates maps whose children are strings or integers. + ### Include - `include(include_name)` Validates included structures. Must supply the name of a valid include. - arguments: single name of a defined include, surrounded by quotes. diff --git a/yamale/__init__.py b/yamale/__init__.py index c63050c..68971f4 100644 --- a/yamale/__init__.py +++ b/yamale/__init__.py @@ -1,7 +1,7 @@ from .yamale import make_schema, make_data, validate -VERSION = (1, 0, 1, 'final', 0) +VERSION = (1, 0, 2, 'final', 0) # Dynamically calculate the version based on VERSION. diff --git a/yamale/schema/schema.py b/yamale/schema/schema.py index d69e82e..ea37cef 100644 --- a/yamale/schema/schema.py +++ b/yamale/schema/schema.py @@ -82,18 +82,24 @@ def _validate(self, validator, data, key, position=None, includes=None): if isinstance(validator, val.Include): errors += self._validate_include(validator, data_item, includes, position) - elif isinstance(validator, val.List): - errors += self._validate_list(validator, data_item, includes, position) + elif isinstance(validator, (val.Map, val.List)): + errors += self._validate_map_list(validator, data_item, includes, position) return errors - def _validate_list(self, validator, data, includes, pos): + + def _validate_map_list(self, validator, data, includes, pos): errors = [] if not validator.validators: - return errors # No validators, user just wanted a list. + return errors # No validators, user just wanted a map. + + if isinstance(validator, val.List): + keys = range(len(data)) + else: + keys = data.keys() - for key in range(len(data)): + for key in keys: sub_errors = [] for v in validator.validators: err = self._validate(v, data, key, pos, includes) @@ -107,6 +113,7 @@ def _validate_list(self, validator, data, includes, pos): return errors + def _validate_include(self, validator, data, includes, pos): errors = [] diff --git a/yamale/tests/fixtures/keywords_bad.yaml b/yamale/tests/fixtures/keywords_bad.yaml index 76b374a..448640b 100644 --- a/yamale/tests/fixtures/keywords_bad.yaml +++ b/yamale/tests/fixtures/keywords_bad.yaml @@ -2,4 +2,4 @@ optional: 'bad!?' optional_min: 9 min_example: 1.3 max_example: NOT AN INT -boolean: True +boolean: "True" diff --git a/yamale/tests/fixtures/map.yaml b/yamale/tests/fixtures/map.yaml new file mode 100644 index 0000000..a356cfd --- /dev/null +++ b/yamale/tests/fixtures/map.yaml @@ -0,0 +1 @@ +map: map(str(), int()) diff --git a/yamale/tests/fixtures/map_bad.yaml b/yamale/tests/fixtures/map_bad.yaml new file mode 100644 index 0000000..010831e --- /dev/null +++ b/yamale/tests/fixtures/map_bad.yaml @@ -0,0 +1,3 @@ +map: + bad: 12.5 + not: [] diff --git a/yamale/tests/fixtures/map_good.yaml b/yamale/tests/fixtures/map_good.yaml new file mode 100644 index 0000000..af9f897 --- /dev/null +++ b/yamale/tests/fixtures/map_good.yaml @@ -0,0 +1,3 @@ +map: + good: "hello" + yes: 5 diff --git a/yamale/tests/fixtures/types.yaml b/yamale/tests/fixtures/types.yaml index 6c0927c..a597b37 100644 --- a/yamale/tests/fixtures/types.yaml +++ b/yamale/tests/fixtures/types.yaml @@ -4,3 +4,4 @@ integer: int() boolean: bool() list: list() enum: enum('one', True, 1) +map: map() diff --git a/yamale/tests/fixtures/types_bad_data.yaml b/yamale/tests/fixtures/types_bad_data.yaml index 878b754..57d7a5d 100644 --- a/yamale/tests/fixtures/types_bad_data.yaml +++ b/yamale/tests/fixtures/types_bad_data.yaml @@ -1,6 +1,7 @@ -string: "hello" -number: 12.1 -integer: "NOT AN INT" -boolean: True -list: [] -enum: 2 +string: 1 +number: "nan" +integer: 1.4 +boolean: 0 +list: "list?" +enum: False +map: 'hello' diff --git a/yamale/tests/fixtures/types_good_data.yaml b/yamale/tests/fixtures/types_good_data.yaml index 115179c..0b7f0f6 100644 --- a/yamale/tests/fixtures/types_good_data.yaml +++ b/yamale/tests/fixtures/types_good_data.yaml @@ -4,3 +4,6 @@ integer: 2 boolean: True list: ['hi'] enum: 1 +map: + hello: 1 + another: "hi" diff --git a/yamale/tests/functional_tests.py b/yamale/tests/functional_tests.py index 05a6519..0ec349a 100644 --- a/yamale/tests/functional_tests.py +++ b/yamale/tests/functional_tests.py @@ -1,5 +1,3 @@ -from nose.tools import raises - from . import get_fixture import yamale as sch from .. import validators as val @@ -34,7 +32,13 @@ 'good': 'lists_good.yaml' } -test_data = [types, nested, custom, keywords, lists] +maps = { + 'schema': 'map.yaml', + 'bad': 'map_bad.yaml', + 'good': 'map_good.yaml' +} + +test_data = [types, nested, custom, keywords, lists, maps] for d in test_data: for key in d.keys(): @@ -69,26 +73,36 @@ def good_gen(data_map): sch.validate(data_map['schema'], data_map['good']) -@raises(ValueError) def test_bad_validate(): - sch.validate(types['schema'], types['bad']) + assert count_exception_lines(types['schema'], types['bad']) == 9 -@raises(ValueError) def test_bad_nested(): - sch.validate(nested['schema'], nested['bad']) + assert count_exception_lines(nested['schema'], nested['bad']) == 3 -@raises(ValueError) def test_bad_custom(): - assert sch.validate(custom['schema'], custom['bad']) + assert count_exception_lines(custom['schema'], custom['bad']) == 3 -@raises(ValueError) def test_bad_lists(): - assert sch.validate(lists['schema'], lists['bad']) + assert count_exception_lines(lists['schema'], lists['bad']) == 4 + + +def test_bad_maps(): + assert count_exception_lines(maps['schema'], maps['bad']) == 6 -@raises(ValueError) def test_bad_keywords(): - assert sch.validate(keywords['schema'], keywords['bad']) + assert count_exception_lines(keywords['schema'], keywords['bad']) == 6 + + +def count_exception_lines(schema, data): + try: + sch.validate(schema, data) + except ValueError as exp: + count = len(exp.message.split('\n')) + print(exp.message) + print(count) + return count + raise Exception("Data valid") diff --git a/yamale/validators/validators.py b/yamale/validators/validators.py index f522781..6ae686e 100644 --- a/yamale/validators/validators.py +++ b/yamale/validators/validators.py @@ -57,6 +57,10 @@ class Map(Validator): """Map and dict validator""" tag = 'map' + def __init__(self, *args, **kwargs): + super(Map, self).__init__(*args, **kwargs) + self.validators = [val for val in args if isinstance(val, Validator)] + def _is_valid(self, value): return isinstance(value, Mapping)