From 8c496df312c4d73bc8515db5ff004b84b34b801f Mon Sep 17 00:00:00 2001 From: Grokzen Date: Wed, 31 Oct 2018 09:59:47 +0100 Subject: [PATCH 1/8] Restructure how include argument works to now be explicit and not lazy loaded. This might solve most of the include issues where rules was not merged properly from same and parent rules --- pykwalify/core.py | 50 ++++++++++---------- pykwalify/rule.py | 19 ++++++-- tests/files/partial_schemas/1s-partials.yaml | 11 ----- tests/files/partial_schemas/1s-schema.yaml | 12 +++++ tests/files/partial_schemas/2s-partials.yaml | 16 ------- tests/files/partial_schemas/2s-schema.yaml | 17 +++++++ tests/files/partial_schemas/7s-schema.yaml | 1 + tests/test_core.py | 32 +++++++++---- 8 files changed, 95 insertions(+), 63 deletions(-) delete mode 100644 tests/files/partial_schemas/1s-partials.yaml delete mode 100644 tests/files/partial_schemas/2s-partials.yaml diff --git a/pykwalify/core.py b/pykwalify/core.py index 25be852..1e4a6ee 100644 --- a/pykwalify/core.py +++ b/pykwalify/core.py @@ -256,9 +256,9 @@ def _validate(self, value, rule, path, done): return log.debug(u" ? ValidateRule: %s", rule) - if rule.include_name is not None: - self._validate_include(value, rule, path, done=None) - elif rule.sequence is not None: + # if rule.include_name is not None: + # self._validate_include(value, rule, path, done=None) + if rule.sequence is not None: self._validate_sequence(value, rule, path, done=None) elif rule.mapping is not None or rule.allowempty_map: self._validate_mapping(value, rule, path, done=None) @@ -303,28 +303,28 @@ def _handle_func(self, value, rule, path, done=None): if not found_method: raise CoreError(u"Did not find method '{0}' in any loaded extension file".format(func)) - def _validate_include(self, value, rule, path, done=None): - """ - """ - # TODO: It is difficult to get a good test case to trigger this if case - if rule.include_name is None: - self.errors.append(SchemaError.SchemaErrorEntry( - msg=u'Include name not valid', - path=path, - value=value.encode('unicode_escape'))) - return - include_name = rule.include_name - partial_schema_rule = pykwalify.partial_schemas.get(include_name) - if not partial_schema_rule: - self.errors.append(SchemaError.SchemaErrorEntry( - msg=u"Cannot find partial schema with name '{include_name}'. Existing partial schemas: '{existing_schemas}'. Path: '{path}'", - path=path, - value=value, - include_name=include_name, - existing_schemas=", ".join(sorted(pykwalify.partial_schemas.keys())))) - return - - self._validate(value, partial_schema_rule, path, done) +# def _validate_include(self, value, rule, path, done=None): +# """ +# """ +# # TODO: It is difficult to get a good test case to trigger this if case +# if rule.include_name is None: +# self.errors.append(SchemaError.SchemaErrorEntry( +# msg=u'Include name not valid', +# path=path, +# value=value.encode('unicode_escape'))) +# return +# include_name = rule.include_name +# partial_schema_rule = pykwalify.partial_schemas.get(include_name) +# if not partial_schema_rule: +# self.errors.append(SchemaError.SchemaErrorEntry( +# msg=u"Cannot find partial schema with name '{include_name}'. Existing partial schemas: '{existing_schemas}'. Path: '{path}'", +# path=path, +# value=value, +# include_name=include_name, +# existing_schemas=", ".join(sorted(pykwalify.partial_schemas.keys())))) +# return +# +# self._validate(value, partial_schema_rule, path, done) def _validate_sequence(self, value, rule, path, done=None): """ diff --git a/pykwalify/rule.py b/pykwalify/rule.py index 7ac2c9e..11a6c9b 100644 --- a/pykwalify/rule.py +++ b/pykwalify/rule.py @@ -372,9 +372,18 @@ def init(self, schema, path): # Check if this item is a include, overwrite schema with include schema and continue to parse if include: - log.debug(u"Found include tag...") - self.include_name = include - return + import pykwalify + + partial_schema_rule = pykwalify.partial_schemas.get(include) + + # schema = {key: value for (key, value) in (schema.items() + partial_schema_rule.schema.items())} + # schema = dict(schema.items() | partial_schema_rule.schema.items()) + for k, v in partial_schema_rule.schema.items(): + schema[k] = v + + # log.debug(u"Found include tag...") + # self.include_name = include + # return t = None rule = self @@ -419,6 +428,7 @@ def init(self, schema, path): "format": self.init_format_value, "func": self.init_func, "ident": self.init_ident_value, + "include": self.init_include, "length": self.init_length_value, "map": self.init_mapping_value, "mapping": self.init_mapping_value, @@ -460,6 +470,9 @@ def init(self, schema, path): self.check_type_keywords(schema, rule, path) + def init_include(self, v, rule, path): + pass + def init_format_value(self, v, rule, path): log.debug(u"Init format value : %s", path) diff --git a/tests/files/partial_schemas/1s-partials.yaml b/tests/files/partial_schemas/1s-partials.yaml deleted file mode 100644 index ef84657..0000000 --- a/tests/files/partial_schemas/1s-partials.yaml +++ /dev/null @@ -1,11 +0,0 @@ -schema;fooone: - type: map - mapping: - foo: - type: str - -schema;footwo: - type: map - mapping: - foo: - type: bool \ No newline at end of file diff --git a/tests/files/partial_schemas/1s-schema.yaml b/tests/files/partial_schemas/1s-schema.yaml index ea433b7..7a8cf30 100644 --- a/tests/files/partial_schemas/1s-schema.yaml +++ b/tests/files/partial_schemas/1s-schema.yaml @@ -1,3 +1,15 @@ type: seq sequence: - include: fooone + +schema;fooone: + type: map + mapping: + foo: + type: str + +schema;footwo: + type: map + mapping: + foo: + type: bool \ No newline at end of file diff --git a/tests/files/partial_schemas/2s-partials.yaml b/tests/files/partial_schemas/2s-partials.yaml deleted file mode 100644 index 238abb9..0000000 --- a/tests/files/partial_schemas/2s-partials.yaml +++ /dev/null @@ -1,16 +0,0 @@ -schema;footwo: - type: map - mapping: - bar: - include: foothree - -schema;fooone: - type: map - mapping: - foo: - include: footwo - -schema;foothree: - type: seq - sequence: - - type: bool diff --git a/tests/files/partial_schemas/2s-schema.yaml b/tests/files/partial_schemas/2s-schema.yaml index ea433b7..d9fc80a 100644 --- a/tests/files/partial_schemas/2s-schema.yaml +++ b/tests/files/partial_schemas/2s-schema.yaml @@ -1,3 +1,20 @@ type: seq sequence: - include: fooone + +schema;foothree: + type: seq + sequence: + - type: bool + +schema;footwo: + type: map + mapping: + bar: + include: foothree + +schema;fooone: + type: map + mapping: + foo: + include: footwo diff --git a/tests/files/partial_schemas/7s-schema.yaml b/tests/files/partial_schemas/7s-schema.yaml index eaf0613..87275d3 100644 --- a/tests/files/partial_schemas/7s-schema.yaml +++ b/tests/files/partial_schemas/7s-schema.yaml @@ -5,6 +5,7 @@ mapping: required: True bar: include: bar + schema;bar: type: seq required: True diff --git a/tests/test_core.py b/tests/test_core.py index 7092d3d..327ab70 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -227,11 +227,10 @@ def test_multi_file_support(self): ( [ self.f("partial_schemas", "1s-schema.yaml"), - self.f("partial_schemas", "1s-partials.yaml"), ], self.f("partial_schemas", "1s-data.yaml"), { - 'sequence': [{'include': 'fooone'}], + 'sequence': [{'include': 'fooone', 'mapping': {'foo': {'type': 'str'}}, 'type': 'map'}], 'type': 'seq', } ), @@ -241,13 +240,25 @@ def test_multi_file_support(self): ( [ self.f("partial_schemas", "2s-schema.yaml"), - self.f("partial_schemas", "2s-partials.yaml"), ], self.f("partial_schemas", "2s-data.yaml"), + # { + # 'sequence': [{'include': 'fooone'}], + # 'type': 'seq', + # } + { - 'sequence': [{'include': 'fooone'}], - 'type': 'seq', - } + "type": "seq", + "sequence": + [{'include': 'fooone', + 'mapping': {'foo': {'include': 'footwo', + 'mapping': {'bar': {'include': 'foothree', + 'sequence': [{'type': 'bool'}], + 'type': 'seq'}}, + 'type': 'map'}}, + 'type': 'map'}] + }, + ), # This tests that you can include a partial schema alongside other rules in a map ( @@ -263,7 +274,10 @@ def test_multi_file_support(self): 'required': True }, 'bar': { - 'include': 'bar' + 'include': 'bar', + "required": True, + "sequence": [{'type': 'str'}], + "type": "seq", } } } @@ -324,6 +338,8 @@ def test_multi_file_support(self): ] for passing_test in pass_tests: + print("Running testfile: {0}".format(passing_test)) + try: c = Core(source_file=passing_test[1], schema_files=passing_test[0]) c.validate() @@ -333,7 +349,7 @@ def test_multi_file_support(self): raise e # This serve as an extra schema validation that tests more complex structures then testrule.py do - compare(c.root_rule.schema_str, passing_test[2], prefix="Parsed rules is not correct, something have changed...") + compare(c.root_rule.schema_str, passing_test[2], prefix="Parsed rules is not correct, something have changed... {0}".format(passing_test)) for failing_test in failing_tests: print("Test files: {0} : {1}".format(", ".join(failing_test[0]), failing_test[1])) From 6a6b4cbb152a19a9eaa8b27750175caa14afe3f2 Mon Sep 17 00:00:00 2001 From: Grokzen Date: Sat, 10 Nov 2018 19:34:09 +0100 Subject: [PATCH 2/8] Fixed broken tests. Partial schema order still buggy. --- pykwalify/rule.py | 9 ++++++- tests/files/partial_schemas/4f-schema.yaml | 12 ++++----- tests/files/partial_schemas/5f-schema.yaml | 8 +++--- tests/files/partial_schemas/6f-schema.yaml | 12 ++++----- tests/test_core.py | 30 +++++++++++----------- 5 files changed, 39 insertions(+), 32 deletions(-) diff --git a/pykwalify/rule.py b/pykwalify/rule.py index 11a6c9b..6e48525 100644 --- a/pykwalify/rule.py +++ b/pykwalify/rule.py @@ -8,7 +8,7 @@ # pykwalify imports from pykwalify.compat import basestring -from pykwalify.errors import SchemaConflict, RuleError +from pykwalify.errors import SchemaConflict, RuleError, SchemaError from pykwalify.types import ( DEFAULT_TYPE, is_bool, @@ -376,6 +376,13 @@ def init(self, schema, path): partial_schema_rule = pykwalify.partial_schemas.get(include) + if not partial_schema_rule: + raise RuleError( + msg=u"Include key: {0} not defined in schema".format(include), + error_key=u"include.key.unknown", + path=path, + ) + # schema = {key: value for (key, value) in (schema.items() + partial_schema_rule.schema.items())} # schema = dict(schema.items() | partial_schema_rule.schema.items()) for k, v in partial_schema_rule.schema.items(): diff --git a/tests/files/partial_schemas/4f-schema.yaml b/tests/files/partial_schemas/4f-schema.yaml index d109e40..a6ac3d5 100644 --- a/tests/files/partial_schemas/4f-schema.yaml +++ b/tests/files/partial_schemas/4f-schema.yaml @@ -2,19 +2,19 @@ type: seq sequence: - include: fooone -schema;fooone: +schema;foothree: type: map mapping: - foo: - include: footwo + bar: + type: str schema;footwo: type: seq sequence: - include: foothree -schema;foothree: +schema;fooone: type: map mapping: - bar: - type: str + foo: + include: footwo diff --git a/tests/files/partial_schemas/5f-schema.yaml b/tests/files/partial_schemas/5f-schema.yaml index 788d906..da3f1ec 100644 --- a/tests/files/partial_schemas/5f-schema.yaml +++ b/tests/files/partial_schemas/5f-schema.yaml @@ -2,17 +2,17 @@ type: seq sequence: - include: fooone -schema;fooone: +schema;foothree: type: seq sequence: - - include: footwo + - type: str schema;footwo: type: seq sequence: - include: foothree -schema;foothree: +schema;fooone: type: seq sequence: - - type: str + - include: footwo diff --git a/tests/files/partial_schemas/6f-schema.yaml b/tests/files/partial_schemas/6f-schema.yaml index 00c0a91..475ccbd 100644 --- a/tests/files/partial_schemas/6f-schema.yaml +++ b/tests/files/partial_schemas/6f-schema.yaml @@ -3,11 +3,11 @@ mapping: foo: include: fooone -schema;fooone: +schema;foothree: type: map mapping: - bar: - include: footwo + ewq: + type: str schema;footwo: type: map @@ -15,8 +15,8 @@ schema;footwo: qwe: include: foothree -schema;foothree: +schema;fooone: type: map mapping: - ewq: - type: str + bar: + include: footwo diff --git a/tests/test_core.py b/tests/test_core.py index 327ab70..bd8b3b0 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -8,7 +8,7 @@ # pykwalify imports import pykwalify from pykwalify.core import Core -from pykwalify.errors import SchemaError, CoreError +from pykwalify.errors import SchemaError, CoreError, RuleError # 3rd party imports import pytest @@ -292,8 +292,8 @@ def test_multi_file_support(self): self.f("partial_schemas", "1f-partials.yaml") ], self.f("partial_schemas", "1f-data.yaml"), - SchemaError, - ["Cannot find partial schema with name 'fooonez'. Existing partial schemas: 'bar, fooone, foothree, footwo'. Path: '/0'"] + RuleError, + ["Include key: None not defined in schema: Path: '/sequence/0'"], ), ( [ @@ -352,23 +352,23 @@ def test_multi_file_support(self): compare(c.root_rule.schema_str, passing_test[2], prefix="Parsed rules is not correct, something have changed... {0}".format(passing_test)) for failing_test in failing_tests: - print("Test files: {0} : {1}".format(", ".join(failing_test[0]), failing_test[1])) + print("Running failing tests: {0}".format(failing_test)) with pytest.raises(failing_test[2]): c = Core(schema_files=failing_test[0], source_file=failing_test[1]) c.validate() - if not c.validation_errors: - raise AssertionError("No validation_errors was raised...") - - compare( - sorted(c.validation_errors), - sorted(failing_test[3]), - prefix="Wrong validation errors when parsing files : {0} : {1}".format( - failing_test[0], - failing_test[1], - ), - ) + if not c.validation_errors: + raise AssertionError("No validation_errors was raised...") + + compare( + sorted(c.validation_errors), + sorted(failing_test[3]), + prefix="Wrong validation errors when parsing files : {0} : {1}".format( + failing_test[0], + failing_test[1], + ), + ) def test_python_obj_loading(self, tmp_path): schema = """ From b245a7aa19e45f76d9451deae3a96a5987b158ee Mon Sep 17 00:00:00 2001 From: Grokzen Date: Sat, 10 Nov 2018 20:50:24 +0100 Subject: [PATCH 3/8] Refactor failing section of test_multi_file_support to reduce number of files. Added support for sending in multiple schemas as a list into schema_data variable and merge them into one. Simulates merging multiple data sources into one schema. --- pykwalify/core.py | 10 ++++++++- tests/files/partial_schemas/10f.yaml | 24 +++++++++++++++++++++ tests/test_core.py | 32 ++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 tests/files/partial_schemas/10f.yaml diff --git a/pykwalify/core.py b/pykwalify/core.py index 1e4a6ee..6bf22c6 100644 --- a/pykwalify/core.py +++ b/pykwalify/core.py @@ -133,9 +133,17 @@ def __init__(self, source_file=None, schema_files=None, source_data=None, schema if self.source is None: log.debug(u"No source file loaded, trying source data variable") self.source = source_data + if self.schema is None: log.debug(u"No schema file loaded, trying schema data variable") - self.schema = schema_data + + if isinstance(schema_data, list): + merged_schema = {} + for schema in schema_data: + merged_schema.update(schema) + self.schema = merged_schema + else: + self.schema = schema_data # Test if anything was loaded if self.source is None: diff --git a/tests/files/partial_schemas/10f.yaml b/tests/files/partial_schemas/10f.yaml new file mode 100644 index 0000000..af8f78d --- /dev/null +++ b/tests/files/partial_schemas/10f.yaml @@ -0,0 +1,24 @@ +--- +data: + point: + x: 3 + +schema: + type: map + mapping: + point: + mapping: + x: + required: true + include: coordinate_value + y: + required: true + include: coordinate_value + +partial-files: + - schema;coordinate_value: + type: number + +fail-exception-class: "SchemaError" +fail-validation-errors: + - "Cannot find required key 'y'. Path: '/point'.: Path: '/'" diff --git a/tests/test_core.py b/tests/test_core.py index bd8b3b0..d1aafc3 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -370,6 +370,38 @@ def test_multi_file_support(self): ), ) + fail_tests = [ + self.f('partial_schemas', '10f.yaml') + ] + + for fail_test in fail_tests: + with open(fail_test, 'r') as stream: + fail_test_raw_data = yaml.safe_load_all(stream) + + for document_index, document in enumerate(fail_test_raw_data): + fail_test_data = document + + data = fail_test_data['data'] + schema = fail_test_data['schema'] + partial_files = fail_test_data['partial-files'] + fail_exception_class = fail_test_data['fail-exception-class'] + fail_validation_errors = fail_test_data['fail-validation-errors'] + fail_except_class_instance = None + + if fail_exception_class == "SchemaError": + fail_except_class_instance = SchemaError + elif fail_exception_class == "RuleError": + fail_except_class_instance = RuleError + + msg = "FAILED TEST FILE: {0}".format(fail_test) + + with pytest.raises(fail_except_class_instance, message=msg): + c = Core( + source_data=data, + schema_data=[schema] + partial_files, + ) + c.validate() + def test_python_obj_loading(self, tmp_path): schema = """ allowempty: True From 4d216f6a146a062f53044dd17833d3603bc92f97 Mon Sep 17 00:00:00 2001 From: Grokzen Date: Sat, 10 Nov 2018 20:58:24 +0100 Subject: [PATCH 4/8] Fix linting issues --- pykwalify/core.py | 23 ----------------------- pykwalify/rule.py | 2 +- tests/files/partial_schemas/10f.yaml | 2 +- tests/test_core.py | 8 ++++++++ 4 files changed, 10 insertions(+), 25 deletions(-) diff --git a/pykwalify/core.py b/pykwalify/core.py index 6bf22c6..13dfed6 100644 --- a/pykwalify/core.py +++ b/pykwalify/core.py @@ -311,29 +311,6 @@ def _handle_func(self, value, rule, path, done=None): if not found_method: raise CoreError(u"Did not find method '{0}' in any loaded extension file".format(func)) -# def _validate_include(self, value, rule, path, done=None): -# """ -# """ -# # TODO: It is difficult to get a good test case to trigger this if case -# if rule.include_name is None: -# self.errors.append(SchemaError.SchemaErrorEntry( -# msg=u'Include name not valid', -# path=path, -# value=value.encode('unicode_escape'))) -# return -# include_name = rule.include_name -# partial_schema_rule = pykwalify.partial_schemas.get(include_name) -# if not partial_schema_rule: -# self.errors.append(SchemaError.SchemaErrorEntry( -# msg=u"Cannot find partial schema with name '{include_name}'. Existing partial schemas: '{existing_schemas}'. Path: '{path}'", -# path=path, -# value=value, -# include_name=include_name, -# existing_schemas=", ".join(sorted(pykwalify.partial_schemas.keys())))) -# return -# -# self._validate(value, partial_schema_rule, path, done) - def _validate_sequence(self, value, rule, path, done=None): """ """ diff --git a/pykwalify/rule.py b/pykwalify/rule.py index 6e48525..a0a1975 100644 --- a/pykwalify/rule.py +++ b/pykwalify/rule.py @@ -8,7 +8,7 @@ # pykwalify imports from pykwalify.compat import basestring -from pykwalify.errors import SchemaConflict, RuleError, SchemaError +from pykwalify.errors import SchemaConflict, RuleError from pykwalify.types import ( DEFAULT_TYPE, is_bool, diff --git a/tests/files/partial_schemas/10f.yaml b/tests/files/partial_schemas/10f.yaml index af8f78d..9074228 100644 --- a/tests/files/partial_schemas/10f.yaml +++ b/tests/files/partial_schemas/10f.yaml @@ -21,4 +21,4 @@ partial-files: fail-exception-class: "SchemaError" fail-validation-errors: - - "Cannot find required key 'y'. Path: '/point'.: Path: '/'" + - "Cannot find required key 'y'. Path: '/point'" diff --git a/tests/test_core.py b/tests/test_core.py index d1aafc3..8246c97 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -402,6 +402,14 @@ def test_multi_file_support(self): ) c.validate() + compare( + sorted(c.validation_errors), + sorted(fail_validation_errors), + prefix="Wrong validation errors when parsing file : {0}".format( + fail_test, + ) + ) + def test_python_obj_loading(self, tmp_path): schema = """ allowempty: True From 8a11f5fd918590f6e0fb5c9daddc58272aa518d4 Mon Sep 17 00:00:00 2001 From: Grokzen Date: Sat, 19 Oct 2019 23:34:50 +0200 Subject: [PATCH 5/8] Fix broken pytest.raises context manager --- tests/test_core.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index 8246c97..e0565a4 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -393,9 +393,8 @@ def test_multi_file_support(self): elif fail_exception_class == "RuleError": fail_except_class_instance = RuleError - msg = "FAILED TEST FILE: {0}".format(fail_test) - - with pytest.raises(fail_except_class_instance, message=msg): + with pytest.raises(fail_except_class_instance): + print("Running test file {0}".format(fail_test)) c = Core( source_data=data, schema_data=[schema] + partial_files, From ae44cc9cf9fc415ba677f9d6004bddf67c5c6846 Mon Sep 17 00:00:00 2001 From: Grokzen Date: Sat, 19 Oct 2019 23:38:17 +0200 Subject: [PATCH 6/8] Add python_requires block to setup.py to do more hard limit of what supported python version is allowed --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index acdba95..2766f60 100644 --- a/setup.py +++ b/setup.py @@ -27,9 +27,10 @@ }, install_requires=[ 'docopt>=0.6.2', - "ruamel.yaml>=0.16.0" + 'ruamel.yaml>=0.16.0', 'python-dateutil>=2.8.0', ], + python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", classifiers=[ # 'Development Status :: 1 - Planning', # 'Development Status :: 2 - Pre-Alpha', From 187dc6573c9b41e9f61cd24e8bbe030e9c012da0 Mon Sep 17 00:00:00 2001 From: Grokzen Date: Sat, 19 Oct 2019 23:40:52 +0200 Subject: [PATCH 7/8] Fix bug where tmp_path should be explicitly casted to a string --- tests/test_core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index e0565a4..aa5957c 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -424,10 +424,10 @@ def test_python_obj_loading(self, tmp_path): - default - goodbye """ - schema_path = os.path.join(tmp_path, 'schema.yaml') + schema_path = os.path.join(str(tmp_path), 'schema.yaml') with open(schema_path, 'w') as stream: stream.write(schema) - data_path = os.path.join(tmp_path, 'data.yaml') + data_path = os.path.join(str(tmp_path), 'data.yaml') with open(data_path, 'w') as stream: stream.write(data) From b684de5024af001b333f2195cfeb1869c09cc676 Mon Sep 17 00:00:00 2001 From: Grokzen Date: Sun, 20 Oct 2019 10:59:51 +0200 Subject: [PATCH 8/8] Some random work and testing of include rules and schema tags --- pykwalify/rule.py | 23 +++++++++++++++-------- tests/test_rule.py | 18 +++++++++++++++++- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/pykwalify/rule.py b/pykwalify/rule.py index a0a1975..059abb6 100644 --- a/pykwalify/rule.py +++ b/pykwalify/rule.py @@ -455,17 +455,24 @@ def init(self, schema, path): "version": self.init_version, } + # Do a initial pass of all keys to look for the schema tagg. + # If we are on the root rule, this check will not be done. + # If we are in any child rules, check all keys for any schema and + # raise error if we find any as schemas can only be defined at root level + if self.parent: + for k, v in schema.items(): + if k.startswith("schema;"): + # Schema tag is only allowed on top level of data + log.debug(u"Found schema tag...") + raise RuleError( + msg=u"Schema is only allowed on top level of schema file", + error_key=u"schema.not.toplevel", + path=path, + ) + for k, v in schema.items(): if k in func_mapping: func_mapping[k](v, rule, path) - elif k.startswith("schema;"): - # Schema tag is only allowed on top level of data - log.debug(u"Found schema tag...") - raise RuleError( - msg=u"Schema is only allowed on top level of schema file", - error_key=u"schema.not.toplevel", - path=path, - ) else: raise RuleError( msg=u"Unknown key: {0} found".format(k), diff --git a/tests/test_rule.py b/tests/test_rule.py index 4b2b7c9..af5f5c2 100644 --- a/tests/test_rule.py +++ b/tests/test_rule.py @@ -23,7 +23,23 @@ def setUp(self): def test_schema(self): # Test that when using both schema; and include tag that it throw an error because schema; tags should be parsed via Core() with pytest.raises(RuleError) as r: - Rule(schema={"schema;str": {"type": "map", "mapping": {"foo": {"type": "str"}}}, "type": "map", "mapping": {"foo": {"include": "str"}}}) + Rule( + schema={ + "schema;str": { + "type": "map", "mapping": { + "foo": { + "type": "str" + }, + }, + }, + "type": "map", + "mapping": { + "foo": { + "include": "str" + }, + }, + } + ) assert str(r.value) == "" assert r.value.error_key == 'schema.not.toplevel'