Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
15 changes: 14 additions & 1 deletion .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,20 @@ on:

jobs:
tests:
name: "py${{ matrix.python-version }}-${{ matrix.os }}"
name: "py${{ matrix.python-version }}-${{ matrix.os }}-${{ matrix.backend }}"
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
os: [windows-latest, ubuntu-latest]
backend: ['jsonschema']
include:
- python-version: '3.12'
os: ubuntu-latest
backend: 'jsonschema-rs'
- python-version: '3.13'
os: windows-latest
backend: 'jsonschema-rs'
fail-fast: false
steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -49,9 +57,14 @@ jobs:
- name: Install dependencies
run: poetry install --all-extras

- name: Install jsonschema-rs
if: matrix.backend != 'jsonschema'
run: poetry run pip install ${{ matrix.backend }}

- name: Test
env:
PYTEST_ADDOPTS: "--color=yes"
OPENAPI_SPEC_VALIDATOR_SCHEMA_VALIDATOR_BACKEND: ${{ matrix.backend }}
run: poetry run pytest

- name: Static type check
Expand Down
17 changes: 17 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,23 @@ Rules:
* Set ``0`` to disable the resolved cache.
* Invalid values (non-integer or negative) fall back to ``128``.

You can also choose schema validator backend:

.. code-block:: bash

OPENAPI_SPEC_VALIDATOR_SCHEMA_VALIDATOR_BACKEND=jsonschema-rs

Allowed values are ``auto`` (default), ``jsonschema``, and
``jsonschema-rs``.
Invalid values raise a warning and fall back to ``auto``.

If you select the ``jsonschema-rs`` backend, make sure the optional
``jsonschema-rs`` package is installed:

.. code-block:: bash

pip install jsonschema-rs

Related projects
################

Expand Down
4 changes: 4 additions & 0 deletions docs/cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,7 @@ Performance note:
You can tune resolved-path caching with
``OPENAPI_SPEC_VALIDATOR_RESOLVED_CACHE_MAXSIZE``.
Default is ``128``; set ``0`` to disable.

You can also select schema validator backend with
``OPENAPI_SPEC_VALIDATOR_SCHEMA_VALIDATOR_BACKEND``
(``auto``/``jsonschema``/``jsonschema-rs``).
11 changes: 11 additions & 0 deletions docs/python.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,14 @@ Rules:
* Default is ``128``.
* Set ``0`` to disable the resolved cache.
* Invalid values (non-integer or negative) fall back to ``128``.

Schema validator backend can be selected with:

.. code-block:: bash

OPENAPI_SPEC_VALIDATOR_SCHEMA_VALIDATOR_BACKEND=jsonschema-rs

Allowed values are ``auto`` (default), ``jsonschema``, and
``jsonschema-rs`` (requires the ``jsonschema-rs`` Python package to be
installed, for example via ``pip install jsonschema-rs``).
Invalid values raise a warning and fall back to ``auto``.
23 changes: 22 additions & 1 deletion openapi_spec_validator/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from jsonschema.exceptions import best_match

from openapi_spec_validator import __version__
from openapi_spec_validator import schemas
from openapi_spec_validator.readers import read_from_filename
from openapi_spec_validator.readers import read_from_stdin
from openapi_spec_validator.shortcuts import get_validator_cls
Expand Down Expand Up @@ -38,6 +39,7 @@ def print_validationerror(
exc: ValidationError,
subschema_errors: str = "best-match",
index: int | None = None,
supports_subschema_details: bool = True,
) -> None:
if index is None:
print(f"{filename}: Validation Error: {exc}")
Expand All @@ -48,6 +50,13 @@ def print_validationerror(
print(exc.cause)
if not exc.context:
return
if not supports_subschema_details:
print("\n\n# Subschema details\n")
print(
"Subschema error details are not available "
"with jsonschema-rs backend."
)
return
if subschema_errors == "all":
print("\n\n# Due to one of those errors\n")
print("\n\n\n".join("## " + str(e) for e in exc.context))
Expand Down Expand Up @@ -139,6 +148,10 @@ def main(args: Sequence[str] | None = None) -> None:
if subschema_errors is None:
subschema_errors = "best-match"

supports_subschema_details = (
schemas.get_validator_backend() != "jsonschema-rs"
)

for filename in args_parsed.file:
# choose source
reader = read_from_filename
Expand Down Expand Up @@ -181,6 +194,9 @@ def main(args: Sequence[str] | None = None) -> None:
err,
subschema_errors,
index=idx,
supports_subschema_details=(
supports_subschema_details
),
)
print(f"{filename}: {len(errors)} validation errors found")
sys.exit(1)
Expand All @@ -189,7 +205,12 @@ def main(args: Sequence[str] | None = None) -> None:

validate(spec, base_uri=base_uri, cls=validator_cls)
except ValidationError as exc:
print_validationerror(filename, exc, subschema_errors)
print_validationerror(
filename,
exc,
subschema_errors,
supports_subschema_details=supports_subschema_details,
)
sys.exit(1)
except Exception as exc:
print_error(filename, exc)
Expand Down
28 changes: 15 additions & 13 deletions openapi_spec_validator/schemas/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
"""OpenAIP spec validator schemas module."""
"""OpenAPI spec validator schemas module."""

from functools import partial

from jsonschema.validators import Draft4Validator
from jsonschema.validators import Draft202012Validator
from lazy_object_proxy import Proxy

from openapi_spec_validator.schemas.backend import get_validator_backend
from openapi_spec_validator.schemas.backend import get_validator_for
from openapi_spec_validator.schemas.utils import get_schema_content

__all__ = ["schema_v2", "schema_v3", "schema_v30", "schema_v31", "schema_v32"]
__all__ = [
"schema_v2",
"schema_v3",
"schema_v30",
"schema_v31",
"schema_v32",
"get_validator_backend",
]

get_schema_content_v2 = partial(get_schema_content, "2.0")
get_schema_content_v30 = partial(get_schema_content, "3.0")
Expand All @@ -23,12 +30,7 @@
# alias to the latest v3 version
schema_v3 = schema_v32

get_openapi_v2_schema_validator = partial(Draft4Validator, schema_v2)
get_openapi_v30_schema_validator = partial(Draft4Validator, schema_v30)
get_openapi_v31_schema_validator = partial(Draft202012Validator, schema_v31)
get_openapi_v32_schema_validator = partial(Draft202012Validator, schema_v32)

openapi_v2_schema_validator = Proxy(get_openapi_v2_schema_validator)
openapi_v30_schema_validator = Proxy(get_openapi_v30_schema_validator)
openapi_v31_schema_validator = Proxy(get_openapi_v31_schema_validator)
openapi_v32_schema_validator = Proxy(get_openapi_v32_schema_validator)
openapi_v2_schema_validator = Proxy(partial(get_validator_for, schema_v2))
openapi_v30_schema_validator = Proxy(partial(get_validator_for, schema_v30))
openapi_v31_schema_validator = Proxy(partial(get_validator_for, schema_v31))
openapi_v32_schema_validator = Proxy(partial(get_validator_for, schema_v32))
43 changes: 43 additions & 0 deletions openapi_spec_validator/schemas/backend/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Schema validator backend selection and factories."""

from typing import Any

from openapi_spec_validator.schemas.backend.jsonschema import (
create_validator as create_jsonschema_validator,
)
from openapi_spec_validator.schemas.backend.jsonschema_rs import (
create_validator as create_jsonschema_rs_validator,
)
from openapi_spec_validator.schemas.backend.jsonschema_rs import (
has_jsonschema_rs_validators,
)
from openapi_spec_validator.settings import get_schema_validator_backend


def _use_jsonschema_rs() -> bool:
backend_mode = get_schema_validator_backend()
available = has_jsonschema_rs_validators()

if backend_mode == "jsonschema":
return False
if backend_mode == "jsonschema-rs":
if not available:
raise RuntimeError(
"OPENAPI_SPEC_VALIDATOR_SCHEMA_VALIDATOR_BACKEND="
"jsonschema-rs is set but jsonschema-rs is not available. "
"Install it with: pip install jsonschema-rs"
)
return True
return available


def get_validator_backend() -> str:
if _use_jsonschema_rs():
return "jsonschema-rs"
return "jsonschema"


def get_validator_for(schema: dict[str, Any]) -> Any:
if _use_jsonschema_rs():
return create_jsonschema_rs_validator(dict(schema))
return create_jsonschema_validator(schema)
8 changes: 8 additions & 0 deletions openapi_spec_validator/schemas/backend/jsonschema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from typing import Any

from jsonschema.validators import validator_for


def create_validator(schema: dict[str, Any]) -> Any:
validator_cls = validator_for(schema)
return validator_cls(schema)
Loading