Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use the referencing library where available #253

Merged
merged 3 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 74 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
psss marked this conversation as resolved.
Show resolved Hide resolved

# 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
1 change: 0 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ repos:
- --recursive
- --in-place
- --aggressive
- --aggressive
psss marked this conversation as resolved.
Show resolved Hide resolved
- --hang-closing
- --max-line-length=99

Expand Down
Empty file added fmf/_compat/__init__.py
Empty file.
51 changes: 51 additions & 0 deletions fmf/_compat/jsonschema.py
Original file line number Diff line number Diff line change
@@ -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)
40 changes: 15 additions & 25 deletions fmf/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@
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
from ruamel.yaml import YAML, scalarstring
from ruamel.yaml.comments import CommentedMap

import fmf.base
from fmf._compat.jsonschema import get_validator

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Constants
Expand Down Expand Up @@ -419,7 +420,7 @@ class Logging:
_level = LOG_WARN

# Already initialized loggers by their name
_loggers = dict()
_loggers: dict[str, logging.Logger] = dict()
martinhoyer marked this conversation as resolved.
Show resolved Hide resolved

def __init__(self, name='fmf'):
# Use existing logger if already initialized
Expand Down Expand Up @@ -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."""
psss marked this conversation as resolved.
Show resolved Hide resolved
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)
Expand All @@ -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
psss marked this conversation as resolved.
Show resolved Hide resolved
raise JsonSchemaError(f'Errors found in provided schema: {error}')


Expand Down