Skip to content

Commit 7f56264

Browse files
committed
Expose resolved cache
1 parent 023da95 commit 7f56264

File tree

8 files changed

+223
-25
lines changed

8 files changed

+223
-25
lines changed

openapi_spec_validator/settings.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from pydantic import field_validator
2+
from pydantic_settings import BaseSettings
3+
from pydantic_settings import SettingsConfigDict
4+
5+
ENV_PREFIX = "OPENAPI_SPEC_VALIDATOR_"
6+
RESOLVED_CACHE_MAXSIZE_DEFAULT = 128
7+
8+
9+
class OpenAPISpecValidatorSettings(BaseSettings):
10+
model_config = SettingsConfigDict(
11+
env_prefix=ENV_PREFIX,
12+
extra="ignore",
13+
)
14+
15+
resolved_cache_maxsize: int = RESOLVED_CACHE_MAXSIZE_DEFAULT
16+
17+
@field_validator("resolved_cache_maxsize", mode="before")
18+
@classmethod
19+
def normalize_resolved_cache_maxsize(
20+
cls, value: int | str | None
21+
) -> int:
22+
if value is None:
23+
return RESOLVED_CACHE_MAXSIZE_DEFAULT
24+
25+
if isinstance(value, int):
26+
parsed_value = value
27+
elif isinstance(value, str):
28+
try:
29+
parsed_value = int(value)
30+
except ValueError:
31+
return RESOLVED_CACHE_MAXSIZE_DEFAULT
32+
else:
33+
return RESOLVED_CACHE_MAXSIZE_DEFAULT
34+
35+
if parsed_value < 0:
36+
return RESOLVED_CACHE_MAXSIZE_DEFAULT
37+
38+
return parsed_value
39+
40+
41+
def get_resolved_cache_maxsize() -> int:
42+
settings = OpenAPISpecValidatorSettings()
43+
return settings.resolved_cache_maxsize

openapi_spec_validator/shortcuts.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from jsonschema_path.handlers import all_urls_handler
88
from jsonschema_path.typing import Schema
99

10+
from openapi_spec_validator.settings import OpenAPISpecValidatorSettings
1011
from openapi_spec_validator.validation import OpenAPIV2SpecValidator
1112
from openapi_spec_validator.validation import OpenAPIV30SpecValidator
1213
from openapi_spec_validator.validation import OpenAPIV31SpecValidator
@@ -44,7 +45,12 @@ def validate(
4445
) -> None:
4546
if cls is None:
4647
cls = get_validator_cls(spec)
47-
sp = SchemaPath.from_dict(spec, base_uri=base_uri)
48+
settings = OpenAPISpecValidatorSettings()
49+
sp = SchemaPath.from_dict(
50+
spec,
51+
base_uri=base_uri,
52+
resolved_cache_maxsize=settings.resolved_cache_maxsize,
53+
)
4854
v = cls(sp)
4955
return v.validate()
5056

openapi_spec_validator/validation/validators.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from openapi_spec_validator.schemas import openapi_v31_schema_validator
1818
from openapi_spec_validator.schemas import openapi_v32_schema_validator
1919
from openapi_spec_validator.schemas.types import AnySchema
20+
from openapi_spec_validator.settings import OpenAPISpecValidatorSettings
2021
from openapi_spec_validator.validation import keywords
2122
from openapi_spec_validator.validation.decorators import unwraps_iter
2223
from openapi_spec_validator.validation.decorators import wraps_cached_iter
@@ -54,11 +55,13 @@ def __init__(
5455
self.schema_path = schema
5556
self.schema = schema.read_value()
5657
else:
58+
settings = OpenAPISpecValidatorSettings()
5759
self.schema = schema
5860
self.schema_path = SchemaPath.from_dict(
5961
self.schema,
6062
base_uri=self.base_uri,
6163
handlers=self.resolver_handlers,
64+
resolved_cache_maxsize=settings.resolved_cache_maxsize,
6265
)
6366

6467
self.keyword_validators_registry = KeywordValidatorRegistry(

poetry.lock

Lines changed: 53 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ dependencies = [
2828
"openapi-schema-validator >=0.7.3,<0.9.0",
2929
"jsonschema-path >=0.4.3,<0.5.0",
3030
"lazy-object-proxy >=1.7.1,<2.0",
31+
"pydantic-settings (>=2.0.0,<3.0.0)",
32+
"pydantic (>=2.0.0,<3.0.0)",
3133
]
3234

3335
[project.urls]

tests/bench/runner.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,11 @@
2121
from pathlib import Path
2222
from typing import Any
2323

24-
from jsonschema_path import SchemaPath
2524
from jsonschema_path.typing import Schema
2625

2726
from openapi_spec_validator import schemas
2827
from openapi_spec_validator import validate
2928
from openapi_spec_validator.readers import read_from_filename
30-
from openapi_spec_validator.shortcuts import get_validator_cls
3129

3230

3331
@dataclass
@@ -110,11 +108,7 @@ def get_spec_version(spec: Schema) -> str:
110108
def run_once(spec: Schema) -> float:
111109
"""Run validation once and return elapsed time."""
112110
t0 = time.perf_counter()
113-
cls = get_validator_cls(spec)
114-
sp = SchemaPath.from_dict(spec)
115-
v = cls(sp)
116-
v.validate()
117-
# validate(spec)
111+
validate(spec)
118112
return time.perf_counter() - t0
119113

120114

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

278272

@@ -348,7 +342,10 @@ def main():
348342
results.append(result.as_dict())
349343
if result.success:
350344
print(
351-
f" ✅ {result.median_s:.4f}s, {result.validations_per_sec:.2f} val/s"
345+
" ✅ {:.4f}s, {:.2f} val/s".format(
346+
result.median_s,
347+
result.validations_per_sec,
348+
)
352349
)
353350
else:
354351
print(f" ❌ Error: {result.error}")

tests/integration/test_shortcuts.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
from openapi_spec_validator import openapi_v2_spec_validator
88
from openapi_spec_validator import openapi_v30_spec_validator
99
from openapi_spec_validator import openapi_v32_spec_validator
10+
from openapi_spec_validator import shortcuts as shortcuts_module
1011
from openapi_spec_validator import validate
1112
from openapi_spec_validator import validate_spec
1213
from openapi_spec_validator import validate_spec_url
1314
from openapi_spec_validator import validate_url
15+
from openapi_spec_validator.settings import RESOLVED_CACHE_MAXSIZE_DEFAULT
1416
from openapi_spec_validator.validation.exceptions import OpenAPIValidationError
1517
from openapi_spec_validator.validation.exceptions import ValidatorDetectError
1618

@@ -23,6 +25,56 @@ def test_spec_schema_version_not_detected(self):
2325
validate(spec)
2426

2527

28+
def test_validate_uses_resolved_cache_maxsize_env(monkeypatch):
29+
captured: dict[str, int] = {}
30+
original_from_dict = shortcuts_module.SchemaPath.from_dict
31+
spec = {
32+
"openapi": "3.0.0",
33+
"info": {"title": "Test API", "version": "0.0.1"},
34+
"paths": {},
35+
}
36+
37+
def fake_from_dict(cls, *args, **kwargs):
38+
captured["resolved_cache_maxsize"] = kwargs["resolved_cache_maxsize"]
39+
return original_from_dict(*args, **kwargs)
40+
41+
monkeypatch.setenv("OPENAPI_SPEC_VALIDATOR_RESOLVED_CACHE_MAXSIZE", "256")
42+
monkeypatch.setattr(
43+
shortcuts_module.SchemaPath,
44+
"from_dict",
45+
classmethod(fake_from_dict),
46+
)
47+
48+
validate(spec, cls=OpenAPIV30SpecValidator)
49+
50+
assert captured["resolved_cache_maxsize"] == 256
51+
52+
53+
def test_validate_uses_default_resolved_cache_on_invalid_env(monkeypatch):
54+
captured: dict[str, int] = {}
55+
original_from_dict = shortcuts_module.SchemaPath.from_dict
56+
spec = {
57+
"openapi": "3.0.0",
58+
"info": {"title": "Test API", "version": "0.0.1"},
59+
"paths": {},
60+
}
61+
62+
def fake_from_dict(cls, *args, **kwargs):
63+
captured["resolved_cache_maxsize"] = kwargs["resolved_cache_maxsize"]
64+
return original_from_dict(*args, **kwargs)
65+
66+
monkeypatch.setenv("OPENAPI_SPEC_VALIDATOR_RESOLVED_CACHE_MAXSIZE", "-1")
67+
monkeypatch.setattr(
68+
shortcuts_module.SchemaPath,
69+
"from_dict",
70+
classmethod(fake_from_dict),
71+
)
72+
73+
validate(spec, cls=OpenAPIV30SpecValidator)
74+
75+
assert captured["resolved_cache_maxsize"] == RESOLVED_CACHE_MAXSIZE_DEFAULT
76+
77+
2678
class TestLocalValidateSpecUrl:
2779
def test_spec_schema_version_not_detected(self, factory):
2880
spec_path = "data/empty.yaml"

0 commit comments

Comments
 (0)