Skip to content

Commit 2d0e968

Browse files
authored
Merge pull request #398 from python-openapi/feature/jsonschema-path-upgrade
Jsonschema-path upgrade
2 parents c3c785b + 400d455 commit 2d0e968

File tree

8 files changed

+116
-61
lines changed

8 files changed

+116
-61
lines changed

openapi_spec_validator/readers.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
import sys
2-
from collections.abc import Hashable
32
from os import path
43
from pathlib import Path
5-
from typing import Any
6-
from typing import Mapping
74
from typing import Tuple
85

96
from jsonschema_path.handlers import all_urls_handler
107
from jsonschema_path.handlers import file_handler
8+
from jsonschema_path.typing import Schema
119

1210

13-
def read_from_stdin(filename: str) -> Tuple[Mapping[Hashable, Any], str]:
11+
def read_from_stdin(filename: str) -> Tuple[Schema, str]:
1412
return file_handler(sys.stdin), "" # type: ignore
1513

1614

17-
def read_from_filename(filename: str) -> Tuple[Mapping[Hashable, Any], str]:
15+
def read_from_filename(filename: str) -> Tuple[Schema, str]:
1816
if not path.isfile(filename):
1917
raise OSError(f"No such file: {filename}")
2018

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,21 @@
11
"""OpenAIP spec validator schemas utils module."""
2-
from collections.abc import Hashable
32
from importlib.resources import as_file
43
from importlib.resources import files
54
from os import path
6-
from typing import Any
7-
from typing import Mapping
85
from typing import Tuple
96

107
from jsonschema_path.readers import FilePathReader
8+
from jsonschema_path.typing import Schema
119

1210

13-
def get_schema(version: str) -> Tuple[Mapping[Hashable, Any], str]:
11+
def get_schema(version: str) -> Tuple[Schema, str]:
1412
schema_path = f"resources/schemas/v{version}/schema.json"
1513
ref = files("openapi_spec_validator") / schema_path
1614
with as_file(ref) as resource_path:
1715
schema_path_full = path.join(path.dirname(__file__), resource_path)
1816
return FilePathReader(schema_path_full).read()
1917

2018

21-
def get_schema_content(version: str) -> Mapping[Hashable, Any]:
19+
def get_schema_content(version: str) -> Schema:
2220
content, _ = get_schema(version)
2321
return content

openapi_spec_validator/validation/keywords.py

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import string
22
from typing import TYPE_CHECKING
33
from typing import Any
4+
from typing import Sequence
45
from typing import Iterator
56
from typing import List
67
from typing import Optional
@@ -78,7 +79,8 @@ def _collect_properties(self, schema: SchemaPath) -> set[str]:
7879
props: set[str] = set()
7980

8081
if "properties" in schema:
81-
props.update((schema / "properties").keys())
82+
schema_props = (schema / "properties").keys()
83+
props.update(cast(Sequence[str], schema_props))
8284

8385
for kw in ("allOf", "anyOf", "oneOf"):
8486
if kw in schema:
@@ -96,11 +98,12 @@ def _collect_properties(self, schema: SchemaPath) -> set[str]:
9698
def __call__(
9799
self, schema: SchemaPath, require_properties: bool = True
98100
) -> Iterator[ValidationError]:
99-
if not hasattr(schema.content(), "__getitem__"):
101+
schema_value = schema.read_value()
102+
if not hasattr(schema_value, "__getitem__"):
100103
return
101104

102105
assert self.schema_ids_registry is not None
103-
schema_id = id(schema.content())
106+
schema_id = id(schema_value)
104107
if schema_id in self.schema_ids_registry:
105108
return
106109
self.schema_ids_registry.append(schema_id)
@@ -151,8 +154,8 @@ def __call__(
151154
require_properties=False,
152155
)
153156

154-
required = schema.getkey("required", [])
155-
properties = schema.get("properties", {}).keys()
157+
required = "required" in schema and (schema / "required").read_value() or []
158+
properties = "properties" in schema and (schema / "properties").keys() or []
156159
if "allOf" in schema:
157160
extra_properties = list(
158161
set(required) - set(properties) - set(nested_properties)
@@ -166,10 +169,12 @@ def __call__(
166169
)
167170

168171
if "default" in schema:
169-
default = schema["default"]
170-
nullable = schema.get("nullable", False)
171-
if default is not None or nullable is not True:
172-
yield from self.default_validator(schema, default)
172+
default_value = (schema / "default").read_value()
173+
nullable_value = False
174+
if "nullable" in schema:
175+
nullable_value = (schema / "nullable").read_value()
176+
if default_value is not None or nullable_value is not True:
177+
yield from self.default_validator(schema, default_value)
173178

174179

175180
class SchemasValidator(KeywordValidator):
@@ -203,9 +208,9 @@ def __call__(self, parameter: SchemaPath) -> Iterator[ValidationError]:
203208

204209
if "default" in parameter:
205210
# only possible in swagger 2.0
206-
default = parameter.getkey("default")
207-
if default is not None:
208-
yield from self.default_validator(parameter, default)
211+
if "default" in parameter:
212+
default_value = (parameter / "default").read_value()
213+
yield from self.default_validator(parameter, default_value)
209214

210215

211216
class ParametersValidator(KeywordValidator):
@@ -246,6 +251,7 @@ def media_type_validator(self) -> MediaTypeValidator:
246251

247252
def __call__(self, content: SchemaPath) -> Iterator[ValidationError]:
248253
for mimetype, media_type in content.items():
254+
assert isinstance(mimetype, str)
249255
yield from self.media_type_validator(mimetype, media_type)
250256

251257

@@ -291,6 +297,7 @@ def response_validator(self) -> ResponseValidator:
291297

292298
def __call__(self, responses: SchemaPath) -> Iterator[ValidationError]:
293299
for response_code, response in responses.items():
300+
assert isinstance(response_code, str)
294301
yield from self.response_validator(response_code, response)
295302

296303

@@ -317,15 +324,17 @@ def __call__(
317324
) -> Iterator[ValidationError]:
318325
assert self.operation_ids_registry is not None
319326

320-
operation_id = operation.getkey("operationId")
321-
if (
322-
operation_id is not None
323-
and operation_id in self.operation_ids_registry
324-
):
325-
yield DuplicateOperationIDError(
326-
f"Operation ID '{operation_id}' for '{name}' in '{url}' is not unique"
327-
)
328-
self.operation_ids_registry.append(operation_id)
327+
if "operationId" in operation:
328+
operation_id_value = (operation / "operationId").read_value()
329+
if (
330+
operation_id_value is not None
331+
and operation_id_value in self.operation_ids_registry
332+
):
333+
yield DuplicateOperationIDError(
334+
f"Operation ID '{operation_id_value}' for "
335+
f"'{name}' in '{url}' is not unique"
336+
)
337+
self.operation_ids_registry.append(operation_id_value)
329338

330339
if "responses" in operation:
331340
responses = operation / "responses"
@@ -392,6 +401,7 @@ def __call__(
392401
yield from self.parameters_validator(parameters)
393402

394403
for field_name, operation in path_item.items():
404+
assert isinstance(field_name, str)
395405
if field_name not in self.OPERATIONS:
396406
continue
397407

@@ -407,6 +417,7 @@ def path_validator(self) -> PathValidator:
407417

408418
def __call__(self, paths: SchemaPath) -> Iterator[ValidationError]:
409419
for url, path_item in paths.items():
420+
assert isinstance(url, str)
410421
yield from self.path_validator(url, path_item)
411422

412423

@@ -416,8 +427,9 @@ def schemas_validator(self) -> SchemasValidator:
416427
return cast(SchemasValidator, self.registry["schemas"])
417428

418429
def __call__(self, components: SchemaPath) -> Iterator[ValidationError]:
419-
schemas = components.get("schemas", {})
420-
yield from self.schemas_validator(schemas)
430+
if "schemas" in components:
431+
schemas = components / "schemas"
432+
yield from self.schemas_validator(schemas)
421433

422434

423435
class RootValidator(KeywordValidator):

openapi_spec_validator/validation/protocols.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,29 @@
1-
from typing import Any
2-
from typing import Hashable
31
from typing import Iterator
4-
from typing import Mapping
52
from typing import Optional
63
from typing import Protocol
74
from typing import runtime_checkable
85

6+
from jsonschema_path.typing import Schema
7+
98
from openapi_spec_validator.validation.exceptions import OpenAPIValidationError
109

1110

1211
@runtime_checkable
1312
class SupportsValidation(Protocol):
14-
def is_valid(self, instance: Mapping[Hashable, Any]) -> bool:
13+
def is_valid(self, instance: Schema) -> bool:
1514
...
1615

1716
def iter_errors(
1817
self,
19-
instance: Mapping[Hashable, Any],
18+
instance: Schema,
2019
base_uri: str = "",
2120
spec_url: Optional[str] = None,
2221
) -> Iterator[OpenAPIValidationError]:
2322
...
2423

2524
def validate(
2625
self,
27-
instance: Mapping[Hashable, Any],
26+
instance: Schema,
2827
base_uri: str = "",
2928
spec_url: Optional[str] = None,
3029
) -> None:

openapi_spec_validator/validation/proxies.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,15 @@ class DetectValidatorProxy:
6262
def __init__(self, choices: Mapping[Tuple[str, str], SpecValidatorProxy]):
6363
self.choices = choices
6464

65-
def detect(self, instance: Mapping[Hashable, Any]) -> SpecValidatorProxy:
65+
def detect(self, instance: Schema) -> SpecValidatorProxy:
6666
for (key, value), validator in self.choices.items():
6767
if key in instance and instance[key].startswith(value):
6868
return validator
6969
raise ValidatorDetectError("Spec schema version not detected")
7070

7171
def validate(
7272
self,
73-
instance: Mapping[Hashable, Any],
73+
instance: Schema,
7474
base_uri: str = "",
7575
spec_url: Optional[str] = None,
7676
) -> None:
@@ -80,14 +80,14 @@ def validate(
8080
):
8181
raise err
8282

83-
def is_valid(self, instance: Mapping[Hashable, Any]) -> bool:
83+
def is_valid(self, instance: Schema) -> bool:
8484
validator = self.detect(instance)
8585
error = next(validator.iter_errors(instance), None)
8686
return error is None
8787

8888
def iter_errors(
8989
self,
90-
instance: Mapping[Hashable, Any],
90+
instance: Schema,
9191
base_uri: str = "",
9292
spec_url: Optional[str] = None,
9393
) -> Iterator[OpenAPIValidationError]:

openapi_spec_validator/validation/validators.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def __init__(
5353

5454
if isinstance(schema, SchemaPath):
5555
self.schema_path = schema
56-
self.schema = schema.contents()
56+
self.schema = schema.read_value()
5757
else:
5858
self.schema = schema
5959
self.schema_path = SchemaPath.from_dict(

0 commit comments

Comments
 (0)