Compare commits

...

4 Commits

Author SHA1 Message Date
Harmen Stoppels
f7fc421283 fix 2025-01-08 18:23:40 +01:00
Harmen Stoppels
7e31e4a4a6 fix docs 2025-01-08 14:03:23 +01:00
Harmen Stoppels
98db2b9e76 jsonschema.exceptions 2025-01-08 13:51:40 +01:00
Harmen Stoppels
b68c331bac config: report file:line of deprecated config items 2025-01-08 13:17:18 +01:00
6 changed files with 35 additions and 22 deletions

View File

@ -206,6 +206,7 @@ def setup(sphinx):
("py:class", "TextIO"), ("py:class", "TextIO"),
("py:class", "hashlib._Hash"), ("py:class", "hashlib._Hash"),
("py:class", "concurrent.futures._base.Executor"), ("py:class", "concurrent.futures._base.Executor"),
("py:class", "jsonschema.exceptions.ValidationError"),
# Spack classes that are private and we don't want to expose # Spack classes that are private and we don't want to expose
("py:class", "spack.provider_index._IndexBase"), ("py:class", "spack.provider_index._IndexBase"),
("py:class", "spack.repo._PrependFileLoader"), ("py:class", "spack.repo._PrependFileLoader"),

View File

@ -34,8 +34,11 @@
import os import os
import re import re
import sys import sys
import warnings
from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Union from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Union
import jsonschema
from llnl.util import filesystem, lang, tty from llnl.util import filesystem, lang, tty
import spack.error import spack.error
@ -1048,7 +1051,6 @@ def validate(
This leverages the line information (start_mark, end_mark) stored This leverages the line information (start_mark, end_mark) stored
on Spack YAML structures. on Spack YAML structures.
""" """
import jsonschema
try: try:
spack.schema.Validator(schema).validate(data) spack.schema.Validator(schema).validate(data)
@ -1057,7 +1059,12 @@ def validate(
line_number = e.instance.lc.line + 1 line_number = e.instance.lc.line + 1
else: else:
line_number = None line_number = None
raise ConfigFormatError(e, data, filename, line_number) from e exception = ConfigFormatError(e, data, filename, line_number)
if isinstance(e, spack.schema.NonFatalValidationError):
warnings.warn(str(exception))
else:
raise exception from e
# return the validated data so that we can access the raw data # return the validated data so that we can access the raw data
# mostly relevant for environments # mostly relevant for environments
return data return data

View File

@ -6,6 +6,8 @@
""" """
import warnings import warnings
import jsonschema
import spack.environment as ev import spack.environment as ev
import spack.schema.env as env import spack.schema.env as env
import spack.util.spack_yaml as syaml import spack.util.spack_yaml as syaml
@ -30,7 +32,6 @@ def validate(configuration_file):
Returns: Returns:
A sanitized copy of the configuration stored in the input file A sanitized copy of the configuration stored in the input file
""" """
import jsonschema
with open(configuration_file, encoding="utf-8") as f: with open(configuration_file, encoding="utf-8") as f:
config = syaml.load(f) config = syaml.load(f)

View File

@ -4,7 +4,10 @@
"""This module contains jsonschema files for all of Spack's YAML formats.""" """This module contains jsonschema files for all of Spack's YAML formats."""
import copy import copy
import typing import typing
import warnings
import jsonschema
import jsonschema.exceptions
import jsonschema.validators
import llnl.util.lang import llnl.util.lang
@ -16,14 +19,14 @@ class DeprecationMessage(typing.NamedTuple):
error: bool error: bool
# jsonschema is imported lazily as it is heavy to import class NonFatalValidationError(jsonschema.exceptions.ValidationError):
# and increases the start-up time """A validation error that should only produce a warning."""
def _make_validator(): def _make_validator():
import jsonschema
def _validate_spec(validator, is_spec, instance, schema): def _validate_spec(validator, is_spec, instance, schema):
"""Check if the attributes on instance are valid specs.""" """Check if the attributes on instance are valid specs."""
import jsonschema
import spack.spec_parser import spack.spec_parser
@ -56,15 +59,18 @@ def _deprecated_properties(validator, deprecated, instance, schema):
# Process issues # Process issues
errors = [] errors = []
warnings = []
for name in issues: for name in issues:
msg = deprecations[name].message.format(name=name) msg = deprecations[name].message.format(name=name)
if deprecations[name].error: if deprecations[name].error:
errors.append(msg) errors.append(msg)
else: else:
warnings.warn(msg) warnings.append(msg)
if errors: if errors:
yield jsonschema.ValidationError("\n".join(errors)) yield jsonschema.ValidationError("\n".join(errors))
if warnings:
yield NonFatalValidationError("\n".join(warnings))
return jsonschema.validators.extend( return jsonschema.validators.extend(
jsonschema.Draft4Validator, jsonschema.Draft4Validator,

View File

@ -9,6 +9,8 @@
""" """
from typing import Any, Dict from typing import Any, Dict
import jsonschema
#: Common properties for connection specification #: Common properties for connection specification
connection = { connection = {
"url": {"type": "string"}, "url": {"type": "string"},
@ -102,7 +104,6 @@
def update(data): def update(data):
import jsonschema
errors = [] errors = []

View File

@ -105,25 +105,22 @@ def test_schema_validation(meta_schema, config_name):
def test_deprecated_properties(module_suffixes_schema): def test_deprecated_properties(module_suffixes_schema):
# Test that an error is reported when 'error: True' # Test that an error is reported when 'error: True'
msg_fmt = r"{name} is deprecated"
module_suffixes_schema["deprecatedProperties"] = [ module_suffixes_schema["deprecatedProperties"] = [
{"names": ["tcl"], "message": msg_fmt, "error": True} {"names": ["tcl"], "message": r"{name} is deprecated", "error": True}
] ]
v = spack.schema.Validator(module_suffixes_schema)
data = {"tcl": {"all": {"suffixes": {"^python": "py"}}}} data = {"tcl": {"all": {"suffixes": {"^python": "py"}}}}
expected_match = "tcl is deprecated" with pytest.raises(jsonschema.ValidationError, match="tcl is deprecated") as e:
with pytest.raises(jsonschema.ValidationError, match=expected_match): assert not isinstance(e, spack.schema.NonFatalValidationError)
v.validate(data) spack.schema.Validator(module_suffixes_schema).validate(data)
# Test that just a warning is reported when 'error: False' # Test that just a non fatal error is reported when 'error: False'
module_suffixes_schema["deprecatedProperties"] = [ module_suffixes_schema["deprecatedProperties"] = [
{"names": ["tcl"], "message": msg_fmt, "error": False} {"names": ["tcl"], "message": r"{name} is deprecated", "error": False}
] ]
v = spack.schema.Validator(module_suffixes_schema)
data = {"tcl": {"all": {"suffixes": {"^python": "py"}}}} with pytest.raises(spack.schema.NonFatalValidationError, match="tcl is deprecated"):
# The next validation doesn't raise anymore spack.schema.Validator(module_suffixes_schema).validate(data)
v.validate(data)
def test_ordereddict_merge_order(): def test_ordereddict_merge_order():