Skip to content

Commit 5efb067

Browse files
committed
Validators refactor
1 parent ff01252 commit 5efb067

20 files changed

+941
-478
lines changed

docs/python.rst

+8-8
Original file line numberDiff line numberDiff line change
@@ -36,22 +36,22 @@ You can also validate spec from url:
3636
3737
In order to explicitly validate a:
3838

39-
* Swagger / OpenAPI 2.0 spec, import ``openapi_v2_spec_validator``
40-
* OpenAPI 3.0 spec, import ``openapi_v30_spec_validator``
41-
* OpenAPI 3.1 spec, import ``openapi_v31_spec_validator``
39+
* Swagger / OpenAPI 2.0 spec, import ``OpenAPIV2SpecValidator``
40+
* OpenAPI 3.0 spec, import ``OpenAPIV30SpecValidator``
41+
* OpenAPI 3.1 spec, import ``OpenAPIV31SpecValidator``
4242

43-
and pass the validator to ``validate_spec`` or ``validate_spec_url`` function:
43+
and pass the validator class to ``validate_spec`` or ``validate_spec_url`` function:
4444

4545
.. code:: python
4646
47-
validate_spec(spec_dict, validator=openapi_v31_spec_validator)
47+
validate_spec(spec_dict, cls=OpenAPIV31SpecValidator)
4848
49-
You can also explicitly import ``openapi_v3_spec_validator`` which is a shortcut to the latest v3 release.
49+
You can also explicitly import ``OpenAPIV3SpecValidator`` which is a shortcut to the latest v3 release.
5050

5151
If you want to iterate through validation errors:
5252

5353
.. code:: python
5454
55-
from openapi_spec_validator import openapi_v3_spec_validator
55+
from openapi_spec_validator import OpenAPIV31SpecValidator
5656
57-
errors_iterator = openapi_v3_spec_validator.iter_errors(spec)
57+
errors_iterator = OpenAPIV31SpecValidator(spec).iter_errors()

openapi_spec_validator/__init__.py

+8
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

+15-12
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

+5-1
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

+10
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

+36-8
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

+25-42
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(
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

0 commit comments

Comments
 (0)