From f7ad595147f44e2c6b31f30c2ceb4a5f2aca6f21 Mon Sep 17 00:00:00 2001 From: Martin Hoyer Date: Fri, 1 Nov 2024 19:07:11 +0100 Subject: [PATCH 1/3] Update .gitignore with tmt's contents --- .gitignore | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8e8280a5..84aa7d65 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,77 @@ dist docs/_build docs/spec docs/stories -__pycache__ + +# Python + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST +tmt.1 + +# Testing +.mypy_cache +.pytest_cache +*.tgz +# Created by pytest-html reporting plugin +/assets/style.css +/report.html + +# Virtual environment +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Jetbrains +.idea/ + +# Vim + +# Swap +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +# Temporary +.netrwhist +*~ + +# Auto-generated tag files +tags + +# Persistent undo +[._]*.un~ + +# Visual Studio Code +.vscode From 89cd184afbd23a89bd97fa6178f0f00c4b0b6405 Mon Sep 17 00:00:00 2001 From: Martin Hoyer Date: Fri, 1 Nov 2024 19:29:33 +0100 Subject: [PATCH 2/3] Opportunistically use referencing library --- .pre-commit-config.yaml | 1 - fmf/_compat/__init__.py | 0 fmf/_compat/jsonschema.py | 51 +++++++++++++++++++++++++++++++++++++++ fmf/utils.py | 40 ++++++++++++------------------ 4 files changed, 66 insertions(+), 26 deletions(-) create mode 100644 fmf/_compat/__init__.py create mode 100644 fmf/_compat/jsonschema.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3398a73e..4d66739c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,6 @@ repos: - --recursive - --in-place - --aggressive - - --aggressive - --hang-closing - --max-line-length=99 diff --git a/fmf/_compat/__init__.py b/fmf/_compat/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fmf/_compat/jsonschema.py b/fmf/_compat/jsonschema.py new file mode 100644 index 00000000..8eb5cf02 --- /dev/null +++ b/fmf/_compat/jsonschema.py @@ -0,0 +1,51 @@ +"""Compatibility layer for jsonschema validation.""" + +from typing import Any, Optional + +import jsonschema +from jsonschema.validators import Draft4Validator + + +def get_validator( + schema: Any, + schema_store: Optional[dict[str, Any]] = None + ) -> Draft4Validator: + """Create a validator instance based on available jsonschema version.""" + # Validate schema is a dict + if not isinstance(schema, dict): # TODO remove once mypy/pyright is added + from fmf.utils import JsonSchemaError + raise JsonSchemaError(f'Invalid schema type: {type(schema)}. Schema must be a dictionary.') + + schema_store = schema_store or {} + + try: + from jsonschema import validators + from referencing import Registry, Resource + from referencing.jsonschema import DRAFT4 + + # Modern approach with referencing + resources = [] + for uri, contents in schema_store.items(): + # Try to create resource from contents (will use $schema if present) + try: + resource = Resource.from_contents(contents) + except Exception: + # If that fails, explicitly create as Draft4 + resource = DRAFT4.create_resource(contents) + resources.append((uri, resource)) + + registry = Registry().with_resources(resources) + + # Create validator using Draft4 meta-schema + validator_cls = validators.validator_for(schema, default=Draft4Validator) + return validator_cls(schema=schema, registry=registry) + + except ImportError: + # Legacy approach with RefResolver + try: + resolver = jsonschema.RefResolver.from_schema( + schema, store=schema_store) + except AttributeError as error: + from fmf.utils import JsonSchemaError + raise JsonSchemaError(f'Provided schema cannot be loaded: {error}') + return Draft4Validator(schema, resolver=resolver) diff --git a/fmf/utils.py b/fmf/utils.py index 4e70fb61..30b0897f 100644 --- a/fmf/utils.py +++ b/fmf/utils.py @@ -10,7 +10,7 @@ import time import warnings from io import StringIO -from typing import Any, List, NamedTuple +from typing import Any, NamedTuple, Optional import jsonschema from filelock import FileLock, Timeout @@ -18,6 +18,7 @@ from ruamel.yaml.comments import CommentedMap import fmf.base +from fmf._compat.jsonschema import get_validator # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Constants @@ -419,7 +420,7 @@ class Logging: _level = LOG_WARN # Already initialized loggers by their name - _loggers = dict() + _loggers: dict[str, logging.Logger] = dict() def __init__(self, name='fmf'): # Use existing logger if already initialized @@ -912,34 +913,22 @@ def dict_to_yaml(data, width=None, sort=False): # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ class JsonSchemaValidationResult(NamedTuple): - """ Represents JSON Schema validation result """ - + """Represents JSON Schema validation result.""" result: bool - errors: List[Any] - - -def validate_data(data, schema, schema_store=None): - """ - Validate data with given JSON Schema and schema references. + errors: list[Any] - schema_store is a dict of schema references and their content. - Return a named tuple utils.JsonSchemaValidationResult - with the following two items: +def validate_data( + data: Any, + schema: Any, + schema_store: Optional[dict[str, Any]] = None + ) -> JsonSchemaValidationResult: + """Validate data against a JSON Schema. - result ... boolean representing the validation result - errors ... A list of validation errors - - Raises utils.JsonSchemaError if the supplied schema was invalid. + Validates the given data using the specified JSON Schema and optional + schema references. """ - schema_store = schema_store or {} - try: - resolver = jsonschema.RefResolver.from_schema( - schema, store=schema_store) - except AttributeError as error: - raise JsonSchemaError(f'Provided schema cannot be loaded: {error}') - - validator = jsonschema.Draft4Validator(schema, resolver=resolver) + validator = get_validator(schema, schema_store) try: validator.validate(data) @@ -955,6 +944,7 @@ def validate_data(data, schema, schema_store=None): jsonschema.exceptions.RefResolutionError, jsonschema.exceptions.UnknownType ) as error: + from fmf.utils import JsonSchemaError raise JsonSchemaError(f'Errors found in provided schema: {error}') From 3d50f985967f9d2f3e66e87474747049d06dfd59 Mon Sep 17 00:00:00 2001 From: Martin Hoyer Date: Mon, 2 Dec 2024 16:58:36 +0100 Subject: [PATCH 3/3] fixup! Opportunistically use referencing library --- .gitignore | 2 +- .pre-commit-config.yaml | 1 + fmf/utils.py | 3 +-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 84aa7d65..ca0a16ee 100644 --- a/.gitignore +++ b/.gitignore @@ -36,7 +36,7 @@ share/python-wheels/ .installed.cfg *.egg MANIFEST -tmt.1 +fmf.1 # Testing .mypy_cache diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4d66739c..3398a73e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,6 +9,7 @@ repos: - --recursive - --in-place - --aggressive + - --aggressive - --hang-closing - --max-line-length=99 diff --git a/fmf/utils.py b/fmf/utils.py index 30b0897f..0c921487 100644 --- a/fmf/utils.py +++ b/fmf/utils.py @@ -913,7 +913,7 @@ def dict_to_yaml(data, width=None, sort=False): # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ class JsonSchemaValidationResult(NamedTuple): - """Represents JSON Schema validation result.""" + """ Represents JSON Schema validation result """ result: bool errors: list[Any] @@ -944,7 +944,6 @@ def validate_data( jsonschema.exceptions.RefResolutionError, jsonschema.exceptions.UnknownType ) as error: - from fmf.utils import JsonSchemaError raise JsonSchemaError(f'Errors found in provided schema: {error}')