Skip to content

Commit 04d8cf8

Browse files
committed
Validators refactor
1 parent ff01252 commit 04d8cf8

File tree

19 files changed

+933
-470
lines changed

19 files changed

+933
-470
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 & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,55 @@
11
"""OpenAPI spec validator shortcuts module."""
2-
from typing import Any
3-
from typing import Hashable
2+
import warnings
43
from typing import Mapping
54
from typing import Optional
5+
from typing import Type
66

77
from jsonschema_spec.handlers import all_urls_handler
8+
from jsonschema_spec.typing import Schema
89

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

1229

1330
def validate_spec(
14-
spec: Mapping[Hashable, Any],
31+
spec: Schema,
1532
base_uri: str = "",
16-
validator: SupportsValidation = openapi_spec_validator_proxy,
33+
validator: Optional[SupportsValidation] = None,
34+
cls: Optional[SpecValidatorType] = None,
1735
spec_url: Optional[str] = None,
1836
) -> None:
19-
return validator.validate(spec, base_uri=base_uri, spec_url=spec_url)
37+
if validator is not None:
38+
warnings.warn(
39+
"validator parameter is deprecated. Use cls instead.",
40+
DeprecationWarning,
41+
)
42+
return validator.validate(spec, base_uri=base_uri, spec_url=spec_url)
43+
if cls is None:
44+
cls = get_validator_cls(spec)
45+
v = cls(spec)
46+
return v.validate()
2047

2148

2249
def validate_spec_url(
2350
spec_url: str,
24-
validator: SupportsValidation = openapi_spec_validator_proxy,
51+
validator: Optional[SupportsValidation] = None,
52+
cls: Optional[Type[SpecValidator]] = None,
2553
) -> None:
2654
spec = all_urls_handler(spec_url)
27-
return validator.validate(spec, base_uri=spec_url)
55+
return validate_spec(spec, base_uri=spec_url, validator=validator, cls=cls)

openapi_spec_validator/validation/__init__.py

Lines changed: 25 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,49 @@
1-
from functools import partial
2-
3-
from jsonschema.validators import Draft4Validator
4-
from jsonschema.validators import Draft202012Validator
5-
from jsonschema_spec.handlers import default_handlers
6-
from lazy_object_proxy import Proxy
7-
from openapi_schema_validator import oas30_format_checker
8-
from openapi_schema_validator import oas31_format_checker
9-
from openapi_schema_validator.validators import OAS30Validator
10-
from openapi_schema_validator.validators import OAS31Validator
11-
12-
from openapi_spec_validator.schemas import schema_v2
13-
from openapi_spec_validator.schemas import schema_v30
14-
from openapi_spec_validator.schemas import schema_v31
151
from openapi_spec_validator.validation.proxies import DetectValidatorProxy
16-
from openapi_spec_validator.validation.validators import SpecValidator
2+
from openapi_spec_validator.validation.proxies import SpecValidatorProxy
3+
from openapi_spec_validator.validation.validators import OpenAPIV2SpecValidator
4+
from openapi_spec_validator.validation.validators import (
5+
OpenAPIV30SpecValidator,
6+
)
7+
from openapi_spec_validator.validation.validators import (
8+
OpenAPIV31SpecValidator,
9+
)
1710

1811
__all__ = [
1912
"openapi_v2_spec_validator",
2013
"openapi_v3_spec_validator",
2114
"openapi_v30_spec_validator",
2215
"openapi_v31_spec_validator",
2316
"openapi_spec_validator_proxy",
17+
"OpenAPIV2SpecValidator",
18+
"OpenAPIV3SpecValidator",
19+
"OpenAPIV30SpecValidator",
20+
"OpenAPIV31SpecValidator",
2421
]
2522

2623
# 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,
24+
openapi_v2_spec_validator = SpecValidatorProxy(
25+
OpenAPIV2SpecValidator,
26+
deprecated="openapi_v2_spec_validator",
27+
use="OpenAPIV2SpecValidator",
3528
)
36-
openapi_v2_spec_validator = Proxy(get_openapi_v2_spec_validator)
3729

3830
# 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,
31+
openapi_v30_spec_validator = SpecValidatorProxy(
32+
OpenAPIV30SpecValidator,
33+
deprecated="openapi_v30_spec_validator",
34+
use="OpenAPIV30SpecValidator",
4735
)
48-
openapi_v30_spec_validator = Proxy(get_openapi_v30_spec_validator)
4936

5037
# 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,
38+
openapi_v31_spec_validator = SpecValidatorProxy(
39+
OpenAPIV31SpecValidator,
40+
deprecated="openapi_v31_spec_validator",
41+
use="OpenAPIV31SpecValidator",
5942
)
60-
openapi_v31_spec_validator = Proxy(get_openapi_v31_spec_validator)
6143

6244
# alias to the latest v3 version
6345
openapi_v3_spec_validator = openapi_v31_spec_validator
46+
OpenAPIV3SpecValidator = OpenAPIV31SpecValidator
6447

6548
# detect version spec
6649
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

openapi_spec_validator/validation/decorators.py

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,54 @@
33
from functools import wraps
44
from typing import Any
55
from typing import Callable
6+
from typing import Iterable
67
from typing import Iterator
7-
from typing import Type
8+
from typing import TypeVar
89

910
from jsonschema.exceptions import ValidationError
1011

12+
from openapi_spec_validator.validation.caches import CachedIterable
13+
from openapi_spec_validator.validation.exceptions import OpenAPIValidationError
14+
15+
Args = TypeVar("Args")
16+
T = TypeVar("T")
17+
1118
log = logging.getLogger(__name__)
1219

1320

14-
class ValidationErrorWrapper:
15-
def __init__(self, error_class: Type[ValidationError]):
16-
self.error_class = error_class
21+
def wraps_errors(
22+
func: Callable[..., Any]
23+
) -> Callable[..., Iterator[ValidationError]]:
24+
@wraps(func)
25+
def wrapper(*args: Any, **kwds: Any) -> Iterator[ValidationError]:
26+
errors = func(*args, **kwds)
27+
for err in errors:
28+
if not isinstance(err, OpenAPIValidationError):
29+
# wrap other exceptions with library specific version
30+
yield OpenAPIValidationError.create_from(err)
31+
else:
32+
yield err
33+
34+
return wrapper
35+
36+
37+
def wraps_cached_iter(
38+
func: Callable[[Args], Iterator[T]]
39+
) -> Callable[[Args], CachedIterable[T]]:
40+
@wraps(func)
41+
def wrapper(*args: Any, **kwargs: Any) -> CachedIterable[T]:
42+
result = func(*args, **kwargs)
43+
return CachedIterable(result)
44+
45+
return wrapper
46+
1747

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
48+
def unwraps_iter(
49+
func: Callable[[Args], Iterable[T]]
50+
) -> Callable[[Args], Iterator[T]]:
51+
@wraps(func)
52+
def wrapper(*args: Any, **kwargs: Any) -> Iterator[T]:
53+
result = func(*args, **kwargs)
54+
return iter(result)
2855

29-
return wrapper
56+
return wrapper

0 commit comments

Comments
 (0)