diff --git a/docs/configobj.rst b/docs/configobj.rst index 17dcddf..391fd26 100644 --- a/docs/configobj.rst +++ b/docs/configobj.rst @@ -522,7 +522,7 @@ validate .. code-block:: python - validate(validator, preserve_errors=False, copy=False) + validate(validator, preserve_errors=False, copy=False, strict_spec=False) .. code-block:: python diff --git a/src/configobj/__init__.py b/src/configobj/__init__.py index 26239c6..d0430a9 100644 --- a/src/configobj/__init__.py +++ b/src/configobj/__init__.py @@ -2098,7 +2098,7 @@ def write(self, outfile=None, section=None): h.write(output_bytes) def validate(self, validator, preserve_errors=False, copy=False, - section=None): + section=None, strict_spec=False): """ Test the ConfigObj against a configspec. @@ -2129,6 +2129,13 @@ def validate(self, validator, preserve_errors=False, copy=False, You must have the validate module to use ``preserve_errors=True``. + If ``strict_spec`` is ``True`` (``False`` is default) then instead of + ignoring entries undefined by the configspec, unrecognized entries will + fail validation and be inserted into the return dictionary with an + informational ``ValidateError``. + + You must have the validate module to use ``strict_spec=True``. + You can then use the ``flatten_errors`` function to turn your nested results dictionary into a flattened list of failures - useful for displaying meaningful error messages. @@ -2139,7 +2146,8 @@ def validate(self, validator, preserve_errors=False, copy=False, if preserve_errors: # We do this once to remove a top level dependency on the validate module # Which makes importing configobj faster - from configobj.validate import VdtMissingValue + from configobj.validate import VdtMissingDefinition, VdtMissingValue + self._vdtMissingDefinition = VdtMissingDefinition self._vdtMissingValue = VdtMissingValue section = self @@ -2263,6 +2271,15 @@ def validate_entry(entry, spec, val, missing, ret_true, ret_false): ret_false = False msg = 'Section %r was provided as a single value' % entry out[entry] = validator.baseErrorClass(msg) + if strict_spec: + for entry in unvalidated: + if not preserve_errors: + out[entry] = False + else: + ret_false = False + msg = 'Value %r was unrecognized' % entry + out[entry] = self._vdtMissingDefinition(msg) + # Missing sections will have been created as empty ones when the # configspec was read. diff --git a/src/configobj/validate.py b/src/configobj/validate.py index b4873fb..25cb4fc 100644 --- a/src/configobj/validate.py +++ b/src/configobj/validate.py @@ -147,6 +147,7 @@ 'VdtValueTooBigError', 'VdtValueTooShortError', 'VdtValueTooLongError', + 'VdtMissingDefinition', 'VdtMissingValue', 'Validator', 'is_integer', @@ -368,6 +369,10 @@ class ValidateError(Exception): """ +class VdtMissingDefinition(ValidateError): + """A check was requested on an undefined entry""" + + class VdtMissingValue(ValidateError): """No value was supplied to a check that needed one.""" diff --git a/src/tests/test_validate_errors.py b/src/tests/test_validate_errors.py index b256efa..8fed86b 100644 --- a/src/tests/test_validate_errors.py +++ b/src/tests/test_validate_errors.py @@ -48,6 +48,11 @@ def test_validate_preserve_errors(conf): assert not section['missing-subsection'] +def test_validate_strict_spec(conf): + conf['undefined-entry'] = True + assert not conf.validate(Validator(), strict_spec=True) + + def test_validate_extra_values(conf): conf.validate(Validator(), preserve_errors=True)