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: 15 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,21 @@ Python package

For more details, read about `Python package <https://openapi-spec-validator.readthedocs.io/en/latest/python.html>`__.

Performance tuning
******************

You can tune resolved-path caching with an environment variable:

.. code-block:: bash

OPENAPI_SPEC_VALIDATOR_RESOLVED_CACHE_MAXSIZE=2048

Rules:

* Default is ``128``.
* Set ``0`` to disable the resolved cache.
* Invalid values (non-integer or negative) fall back to ``128``.

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

Expand Down
5 changes: 5 additions & 0 deletions docs/cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,8 @@ CLI (Command Line Interface)
Legacy note:
``--errors`` / ``--error`` are deprecated and emit warnings by default.
Set ``OPENAPI_SPEC_VALIDATOR_WARN_DEPRECATED=0`` to silence warnings.

Performance note:
You can tune resolved-path caching with
``OPENAPI_SPEC_VALIDATOR_RESOLVED_CACHE_MAXSIZE``.
Default is ``128``; set ``0`` to disable.
16 changes: 16 additions & 0 deletions docs/python.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,19 @@ If you want to iterate through validation errors:
from openapi_spec_validator import OpenAPIV32SpecValidator

errors_iterator = OpenAPIV32SpecValidator(spec).iter_errors()

Resolved path cache
-------------------

``openapi-spec-validator`` can configure the ``jsonschema-path`` resolved
path cache through an environment variable:

.. code-block:: bash

OPENAPI_SPEC_VALIDATOR_RESOLVED_CACHE_MAXSIZE=2048

Rules:

* Default is ``128``.
* Set ``0`` to disable the resolved cache.
* Invalid values (non-integer or negative) fall back to ``128``.
43 changes: 43 additions & 0 deletions openapi_spec_validator/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from pydantic import field_validator
from pydantic_settings import BaseSettings
from pydantic_settings import SettingsConfigDict

ENV_PREFIX = "OPENAPI_SPEC_VALIDATOR_"
RESOLVED_CACHE_MAXSIZE_DEFAULT = 128


class OpenAPISpecValidatorSettings(BaseSettings):
model_config = SettingsConfigDict(
env_prefix=ENV_PREFIX,
extra="ignore",
)

resolved_cache_maxsize: int = RESOLVED_CACHE_MAXSIZE_DEFAULT

@field_validator("resolved_cache_maxsize", mode="before")
@classmethod
def normalize_resolved_cache_maxsize(
cls, value: int | str | None
) -> int:
if value is None:
return RESOLVED_CACHE_MAXSIZE_DEFAULT

if isinstance(value, int):
parsed_value = value
elif isinstance(value, str):
try:
parsed_value = int(value)
except ValueError:
return RESOLVED_CACHE_MAXSIZE_DEFAULT
else:
return RESOLVED_CACHE_MAXSIZE_DEFAULT

if parsed_value < 0:
return RESOLVED_CACHE_MAXSIZE_DEFAULT

return parsed_value


def get_resolved_cache_maxsize() -> int:
settings = OpenAPISpecValidatorSettings()
return settings.resolved_cache_maxsize
8 changes: 7 additions & 1 deletion openapi_spec_validator/shortcuts.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from jsonschema_path.handlers import all_urls_handler
from jsonschema_path.typing import Schema

from openapi_spec_validator.settings import OpenAPISpecValidatorSettings
from openapi_spec_validator.validation import OpenAPIV2SpecValidator
from openapi_spec_validator.validation import OpenAPIV30SpecValidator
from openapi_spec_validator.validation import OpenAPIV31SpecValidator
Expand Down Expand Up @@ -44,7 +45,12 @@ def validate(
) -> None:
if cls is None:
cls = get_validator_cls(spec)
sp = SchemaPath.from_dict(spec, base_uri=base_uri)
settings = OpenAPISpecValidatorSettings()
sp = SchemaPath.from_dict(
spec,
base_uri=base_uri,
resolved_cache_maxsize=settings.resolved_cache_maxsize,
)
v = cls(sp)
return v.validate()

Expand Down
3 changes: 3 additions & 0 deletions openapi_spec_validator/validation/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from openapi_spec_validator.schemas import openapi_v31_schema_validator
from openapi_spec_validator.schemas import openapi_v32_schema_validator
from openapi_spec_validator.schemas.types import AnySchema
from openapi_spec_validator.settings import OpenAPISpecValidatorSettings
from openapi_spec_validator.validation import keywords
from openapi_spec_validator.validation.decorators import unwraps_iter
from openapi_spec_validator.validation.decorators import wraps_cached_iter
Expand Down Expand Up @@ -54,11 +55,13 @@ def __init__(
self.schema_path = schema
self.schema = schema.read_value()
else:
settings = OpenAPISpecValidatorSettings()
self.schema = schema
self.schema_path = SchemaPath.from_dict(
self.schema,
base_uri=self.base_uri,
handlers=self.resolver_handlers,
resolved_cache_maxsize=settings.resolved_cache_maxsize,
)

self.keyword_validators_registry = KeywordValidatorRegistry(
Expand Down
67 changes: 53 additions & 14 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ classifiers = [
dependencies = [
"jsonschema >=4.24.0,<4.25.0",
"openapi-schema-validator >=0.7.3,<0.9.0",
"jsonschema-path >=0.4.2,<0.5.0",
"jsonschema-path >=0.4.3,<0.5.0",
"lazy-object-proxy >=1.7.1,<2.0",
"pydantic-settings (>=2.0.0,<3.0.0)",
"pydantic (>=2.0.0,<3.0.0)",
]

[project.urls]
Expand Down
17 changes: 7 additions & 10 deletions tests/bench/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,11 @@
from pathlib import Path
from typing import Any

from jsonschema_path import SchemaPath
from jsonschema_path.typing import Schema

from openapi_spec_validator import schemas
from openapi_spec_validator import validate
from openapi_spec_validator.readers import read_from_filename
from openapi_spec_validator.shortcuts import get_validator_cls


@dataclass
Expand Down Expand Up @@ -110,11 +108,7 @@ def get_spec_version(spec: Schema) -> str:
def run_once(spec: Schema) -> float:
"""Run validation once and return elapsed time."""
t0 = time.perf_counter()
cls = get_validator_cls(spec)
sp = SchemaPath.from_dict(spec)
v = cls(sp)
v.validate()
# validate(spec)
validate(spec)
return time.perf_counter() - t0


Expand Down Expand Up @@ -271,8 +265,8 @@ def get_synthetic_specs_iterator(
configs: list[tuple[int, int, str]],
) -> Iterator[tuple[dict[str, Any], str, float]]:
"""Iterator over synthetic specs based on provided configurations."""
for paths, schemas, size in configs:
spec = generate_synthetic_spec(paths, schemas)
for paths, schema_count, size in configs:
spec = generate_synthetic_spec(paths, schema_count)
yield spec, f"synthetic_{size}", 0


Expand Down Expand Up @@ -348,7 +342,10 @@ def main():
results.append(result.as_dict())
if result.success:
print(
f" ✅ {result.median_s:.4f}s, {result.validations_per_sec:.2f} val/s"
" ✅ {:.4f}s, {:.2f} val/s".format(
result.median_s,
result.validations_per_sec,
)
)
else:
print(f" ❌ Error: {result.error}")
Expand Down
Loading