Skip to content

Commit e72e7d6

Browse files
committed
Validators refactor
1 parent ff01252 commit e72e7d6

File tree

19 files changed

+927
-440
lines changed

19 files changed

+927
-440
lines changed

openapi_spec_validator/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
"""OpenAPI spec validator module."""
22
from openapi_spec_validator.shortcuts import validate_spec
33
from openapi_spec_validator.shortcuts import validate_spec_url
4+
from openapi_spec_validator.validation import OpenAPIV2SpecValidator
5+
from openapi_spec_validator.validation import OpenAPIV3SpecValidator
6+
from openapi_spec_validator.validation import OpenAPIV30SpecValidator
7+
from openapi_spec_validator.validation import OpenAPIV31SpecValidator
48
from openapi_spec_validator.validation import openapi_v2_spec_validator
59
from openapi_spec_validator.validation import openapi_v3_spec_validator
610
from openapi_spec_validator.validation import openapi_v30_spec_validator
@@ -17,6 +21,10 @@
1721
"openapi_v3_spec_validator",
1822
"openapi_v30_spec_validator",
1923
"openapi_v31_spec_validator",
24+
"OpenAPIV2SpecValidator",
25+
"OpenAPIV3SpecValidator",
26+
"OpenAPIV30SpecValidator",
27+
"OpenAPIV31SpecValidator",
2028
"validate_spec",
2129
"validate_spec_url",
2230
]

openapi_spec_validator/__main__.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99

1010
from openapi_spec_validator.readers import read_from_filename
1111
from openapi_spec_validator.readers import read_from_stdin
12-
from openapi_spec_validator.validation import openapi_spec_validator_proxy
13-
from openapi_spec_validator.validation import openapi_v2_spec_validator
14-
from openapi_spec_validator.validation import openapi_v30_spec_validator
15-
from openapi_spec_validator.validation import openapi_v31_spec_validator
12+
from openapi_spec_validator.shortcuts import get_validator_cls
13+
from openapi_spec_validator.validation import OpenAPIV2SpecValidator
14+
from openapi_spec_validator.validation import OpenAPIV30SpecValidator
15+
from openapi_spec_validator.validation import OpenAPIV31SpecValidator
1616

1717
logger = logging.getLogger(__name__)
1818
logging.basicConfig(
@@ -91,19 +91,22 @@ def main(args: Optional[Sequence[str]] = None) -> None:
9191

9292
# choose the validator
9393
validators = {
94-
"detect": openapi_spec_validator_proxy,
95-
"2.0": openapi_v2_spec_validator,
96-
"3.0": openapi_v30_spec_validator,
97-
"3.1": openapi_v31_spec_validator,
94+
"2.0": OpenAPIV2SpecValidator,
95+
"3.0": OpenAPIV30SpecValidator,
96+
"3.1": OpenAPIV31SpecValidator,
9897
# backward compatibility
99-
"3.0.0": openapi_v30_spec_validator,
100-
"3.1.0": openapi_v31_spec_validator,
98+
"3.0.0": OpenAPIV30SpecValidator,
99+
"3.1.0": OpenAPIV31SpecValidator,
101100
}
102-
validator = validators[args_parsed.schema]
101+
if args_parsed.schema == "detect":
102+
validator_cls = get_validator_cls(spec)
103+
else:
104+
validator_cls = validators[args_parsed.schema]
103105

106+
validator = validator_cls(spec, base_uri=base_uri)
104107
# validate
105108
try:
106-
validator.validate(spec, base_uri=base_uri)
109+
validator.validate()
107110
except ValidationError as exc:
108111
print_validationerror(filename, exc, args_parsed.errors)
109112
sys.exit(1)

openapi_spec_validator/exceptions.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
1-
class OpenAPISpecValidatorError(Exception):
1+
class OpenAPIError(Exception):
2+
pass
3+
4+
5+
class OpenAPISpecValidatorError(OpenAPIError):
26
pass

openapi_spec_validator/schemas/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"""OpenAIP spec validator schemas module."""
22
from functools import partial
33

4+
from jsonschema.validators import Draft4Validator
5+
from jsonschema.validators import Draft202012Validator
46
from lazy_object_proxy import Proxy
57

68
from openapi_spec_validator.schemas.utils import get_schema_content
@@ -17,3 +19,11 @@
1719

1820
# alias to the latest v3 version
1921
schema_v3 = schema_v31
22+
23+
get_openapi_v2_schema_validator = partial(Draft4Validator, schema_v2)
24+
get_openapi_v30_schema_validator = partial(Draft4Validator, schema_v30)
25+
get_openapi_v31_schema_validator = partial(Draft202012Validator, schema_v31)
26+
27+
openapi_v2_schema_validator = Proxy(get_openapi_v2_schema_validator)
28+
openapi_v30_schema_validator = Proxy(get_openapi_v30_schema_validator)
29+
openapi_v31_schema_validator = Proxy(get_openapi_v31_schema_validator)

openapi_spec_validator/shortcuts.py

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,57 @@
11
"""OpenAPI spec validator shortcuts module."""
2+
import warnings
23
from typing import Any
34
from typing import Hashable
45
from typing import Mapping
56
from typing import Optional
7+
from typing import Type
68

79
from jsonschema_spec.handlers import all_urls_handler
10+
from jsonschema_spec.typing import Schema
811

9-
from openapi_spec_validator.validation import openapi_spec_validator_proxy
12+
from openapi_spec_validator.validation import OpenAPIV2SpecValidator
13+
from openapi_spec_validator.validation import OpenAPIV30SpecValidator
14+
from openapi_spec_validator.validation import OpenAPIV31SpecValidator
15+
from openapi_spec_validator.validation.finders import SpecFinder
16+
from openapi_spec_validator.validation.finders import SpecVersion
1017
from openapi_spec_validator.validation.protocols import SupportsValidation
18+
from openapi_spec_validator.validation.types import SpecValidatorType
19+
from openapi_spec_validator.validation.validators import SpecValidator
20+
21+
SPECS: Mapping[SpecVersion, SpecValidatorType] = {
22+
SpecVersion("swagger", "2.0"): OpenAPIV2SpecValidator,
23+
SpecVersion("openapi", "3.0"): OpenAPIV30SpecValidator,
24+
SpecVersion("openapi", "3.1"): OpenAPIV31SpecValidator,
25+
}
26+
27+
28+
def get_validator_cls(spec: Schema) -> SpecValidatorType:
29+
return SpecFinder(SPECS).find(spec)
1130

1231

1332
def validate_spec(
14-
spec: Mapping[Hashable, Any],
33+
spec: Schema,
1534
base_uri: str = "",
16-
validator: SupportsValidation = openapi_spec_validator_proxy,
35+
validator: Optional[SupportsValidation] = None,
36+
cls: Optional[SpecValidatorType] = None,
1737
spec_url: Optional[str] = None,
1838
) -> None:
19-
return validator.validate(spec, base_uri=base_uri, spec_url=spec_url)
39+
if validator is not None:
40+
warnings.warn(
41+
"validator parameter is deprecated. Use cls instead.",
42+
DeprecationWarning,
43+
)
44+
return validator.validate(spec, base_uri=base_uri, spec_url=spec_url)
45+
if cls is None:
46+
cls = get_validator_cls(spec)
47+
v = cls(spec)
48+
return v.validate()
2049

2150

2251
def validate_spec_url(
2352
spec_url: str,
24-
validator: SupportsValidation = openapi_spec_validator_proxy,
53+
validator: Optional[SupportsValidation] = None,
54+
cls: Optional[Type[SpecValidator]] = None,
2555
) -> None:
2656
spec = all_urls_handler(spec_url)
27-
return validator.validate(spec, base_uri=spec_url)
57+
return validate_spec(spec, base_uri=spec_url, validator=validator, cls=cls)

openapi_spec_validator/validation/__init__.py

Lines changed: 25 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
from functools import partial
22

3-
from jsonschema.validators import Draft4Validator
4-
from jsonschema.validators import Draft202012Validator
53
from jsonschema_spec.handlers import default_handlers
64
from lazy_object_proxy import Proxy
75
from openapi_schema_validator import oas30_format_checker
@@ -13,54 +11,51 @@
1311
from openapi_spec_validator.schemas import schema_v30
1412
from openapi_spec_validator.schemas import schema_v31
1513
from openapi_spec_validator.validation.proxies import DetectValidatorProxy
16-
from openapi_spec_validator.validation.validators import SpecValidator
14+
from openapi_spec_validator.validation.proxies import SpecValidatorProxy
15+
from openapi_spec_validator.validation.validators import OpenAPIV2SpecValidator
16+
from openapi_spec_validator.validation.validators import (
17+
OpenAPIV30SpecValidator,
18+
)
19+
from openapi_spec_validator.validation.validators import (
20+
OpenAPIV31SpecValidator,
21+
)
1722

1823
__all__ = [
1924
"openapi_v2_spec_validator",
2025
"openapi_v3_spec_validator",
2126
"openapi_v30_spec_validator",
2227
"openapi_v31_spec_validator",
2328
"openapi_spec_validator_proxy",
29+
"OpenAPIV2SpecValidator",
30+
"OpenAPIV3SpecValidator",
31+
"OpenAPIV30SpecValidator",
32+
"OpenAPIV31SpecValidator",
2433
]
2534

2635
# v2.0 spec
27-
get_openapi_v2_schema_validator = partial(Draft4Validator, schema_v2)
28-
openapi_v2_schema_validator = Proxy(get_openapi_v2_schema_validator)
29-
get_openapi_v2_spec_validator = partial(
30-
SpecValidator,
31-
openapi_v2_schema_validator,
32-
OAS30Validator,
33-
oas30_format_checker,
34-
resolver_handlers=default_handlers,
36+
openapi_v2_spec_validator = SpecValidatorProxy(
37+
OpenAPIV2SpecValidator,
38+
deprecated="openapi_v2_spec_validator",
39+
use="OpenAPIV2SpecValidator",
3540
)
36-
openapi_v2_spec_validator = Proxy(get_openapi_v2_spec_validator)
3741

3842
# v3.0 spec
39-
get_openapi_v30_schema_validator = partial(Draft4Validator, schema_v30)
40-
openapi_v30_schema_validator = Proxy(get_openapi_v30_schema_validator)
41-
get_openapi_v30_spec_validator = partial(
42-
SpecValidator,
43-
openapi_v30_schema_validator,
44-
OAS30Validator,
45-
oas30_format_checker,
46-
resolver_handlers=default_handlers,
43+
openapi_v30_spec_validator = SpecValidatorProxy(
44+
OpenAPIV30SpecValidator,
45+
deprecated="openapi_v30_spec_validator",
46+
use="OpenAPIV30SpecValidator",
4747
)
48-
openapi_v30_spec_validator = Proxy(get_openapi_v30_spec_validator)
4948

5049
# v3.1 spec
51-
get_openapi_v31_schema_validator = partial(Draft202012Validator, schema_v31)
52-
openapi_v31_schema_validator = Proxy(get_openapi_v31_schema_validator)
53-
get_openapi_v31_spec_validator = partial(
54-
SpecValidator,
55-
openapi_v31_schema_validator,
56-
OAS31Validator,
57-
oas31_format_checker,
58-
resolver_handlers=default_handlers,
50+
openapi_v31_spec_validator = SpecValidatorProxy(
51+
OpenAPIV31SpecValidator,
52+
deprecated="openapi_v31_spec_validator",
53+
use="OpenAPIV31SpecValidator",
5954
)
60-
openapi_v31_spec_validator = Proxy(get_openapi_v31_spec_validator)
6155

6256
# alias to the latest v3 version
6357
openapi_v3_spec_validator = openapi_v31_spec_validator
58+
OpenAPIV3SpecValidator = OpenAPIV31SpecValidator
6459

6560
# detect version spec
6661
openapi_spec_validator_proxy = DetectValidatorProxy(
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
from typing import Generic
2+
from typing import Iterable
3+
from typing import Iterator
4+
from typing import List
5+
from typing import TypeVar
6+
7+
T = TypeVar('T')
8+
9+
10+
class CachedIterable(Iterable[T], Generic[T]):
11+
"""
12+
A cache-implementing wrapper for an iterator.
13+
Note that this is class is `Iterable[T]` rather than `Iterator[T]`.
14+
It should not be iterated by his own.
15+
"""
16+
17+
cache: List[T]
18+
iter: Iterator[T]
19+
completed: bool
20+
21+
def __init__(self, it: Iterator[T]):
22+
self.iter = iter(it)
23+
self.cache = list()
24+
self.completed = False
25+
26+
def __iter__(self) -> Iterator[T]:
27+
return CachedIterator(self)
28+
29+
def __next__(self) -> T:
30+
try:
31+
item = next(self.iter)
32+
except StopIteration:
33+
self.completed = True
34+
raise
35+
else:
36+
self.cache.append(item)
37+
return item
38+
39+
def __del__(self) -> None:
40+
del self.cache
41+
42+
43+
class CachedIterator(Iterator[T], Generic[T]):
44+
"""
45+
A cache-using wrapper for an iterator.
46+
This class is only constructed by `CachedIterable` and cannot be used without it.
47+
"""
48+
49+
parent: CachedIterable[T]
50+
position: int
51+
52+
def __init__(self, parent: CachedIterable[T]):
53+
self.parent = parent
54+
self.position = 0
55+
56+
def __next__(self) -> T:
57+
if (self.position < len(self.parent.cache)):
58+
item = self.parent.cache[self.position]
59+
elif (self.parent.completed):
60+
raise StopIteration
61+
else:
62+
item = next(self.parent)
63+
64+
self.position += 1
65+
return item
Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,49 @@
11
"""OpenAPI spec validator validation decorators module."""
22
import logging
3+
from functools import cache
34
from functools import wraps
45
from typing import Any
56
from typing import Callable
7+
from typing import Iterable
68
from typing import Iterator
79
from typing import Type
10+
from typing import TypeVar
811

912
from jsonschema.exceptions import ValidationError
1013

14+
from openapi_spec_validator.validation.caches import CachedIterable
15+
from openapi_spec_validator.validation.exceptions import OpenAPIValidationError
16+
17+
Args = TypeVar('Args')
18+
T = TypeVar('T')
19+
1120
log = logging.getLogger(__name__)
1221

22+
def wraps_errors(func: Callable[..., Any]) -> Callable[..., Iterator[ValidationError]]:
23+
@wraps(func)
24+
def wrapper(*args: Any, **kwds: Any) -> Iterator[ValidationError]:
25+
errors = func(*args, **kwds)
26+
for err in errors:
27+
if not isinstance(err, OpenAPIValidationError):
28+
# wrap other exceptions with library specific version
29+
yield OpenAPIValidationError.create_from(err)
30+
else:
31+
yield err
1332

14-
class ValidationErrorWrapper:
15-
def __init__(self, error_class: Type[ValidationError]):
16-
self.error_class = error_class
33+
return wrapper
1734

18-
def __call__(self, f: Callable[..., Any]) -> Callable[..., Any]:
19-
@wraps(f)
20-
def wrapper(*args: Any, **kwds: Any) -> Iterator[ValidationError]:
21-
errors = f(*args, **kwds)
22-
for err in errors:
23-
if not isinstance(err, self.error_class):
24-
# wrap other exceptions with library specific version
25-
yield self.error_class.create_from(err)
26-
else:
27-
yield err
35+
def wraps_cached_iter(func: Callable[[Args], Iterator[T]]) -> Callable[[Args], CachedIterable[T]]:
36+
@wraps(func)
37+
def wrapper(*args: Any, **kwargs: Any) -> CachedIterable[T]:
38+
result = func(*args, **kwargs)
39+
return CachedIterable(result)
40+
41+
return wrapper
2842

29-
return wrapper
43+
def unwraps_iter(func: Callable[[Args], Iterable[T]]) -> Callable[[Args], Iterator[T]]:
44+
@wraps(func)
45+
def wrapper(*args: Any, **kwargs: Any) -> Iterator[T]:
46+
result = func(*args, **kwargs)
47+
return iter(result)
48+
49+
return wrapper

0 commit comments

Comments
 (0)