Skip to content

Commit ffc7bf5

Browse files
authored
Merge pull request #1002 from python-openapi/feature/upgrade-jsonschema-path
Upgrade jsonschema-path 0.4.0b8 and openapi-spec-validator 0.8.0b3
2 parents f801a4f + 5f4e648 commit ffc7bf5

File tree

34 files changed

+897
-182
lines changed

34 files changed

+897
-182
lines changed

openapi_core/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
from openapi_core.shortcuts import validate_response
1515
from openapi_core.shortcuts import validate_webhook_request
1616
from openapi_core.shortcuts import validate_webhook_response
17-
from openapi_core.spec.paths import Spec
1817
from openapi_core.unmarshalling.request import V3RequestUnmarshaller
1918
from openapi_core.unmarshalling.request import V3WebhookRequestUnmarshaller
2019
from openapi_core.unmarshalling.request import V30RequestUnmarshaller
@@ -45,7 +44,6 @@
4544
__all__ = [
4645
"OpenAPI",
4746
"Config",
48-
"Spec",
4947
"unmarshal_request",
5048
"unmarshal_response",
5149
"unmarshal_apicall_request",

openapi_core/app.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ def from_file(
305305

306306
def _get_version(self) -> SpecVersion:
307307
try:
308-
return get_spec_version(self.spec.contents())
308+
return get_spec_version(self.spec.read_value())
309309
# backward compatibility
310310
except OpenAPIVersionNotFound:
311311
raise SpecError("Spec schema version not detected")
@@ -320,7 +320,7 @@ def check_spec(self) -> None:
320320

321321
try:
322322
validate(
323-
self.spec.contents(),
323+
self.spec.read_value(),
324324
base_uri=self.config.spec_base_uri
325325
or self.spec.accessor.resolver._base_uri, # type: ignore[attr-defined]
326326
cls=cls,

openapi_core/casting/schemas/casters.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,7 @@ def _cast_proparties(
132132
if schema_only:
133133
return value
134134

135-
additional_properties = self.schema.getkey(
136-
"additionalProperties", True
137-
)
135+
additional_properties = self.schema.get("additionalProperties", True)
138136
if additional_properties is not False:
139137
# free-form object
140138
if additional_properties is True:
@@ -199,20 +197,21 @@ def __init__(
199197

200198
def cast(self, value: Any) -> Any:
201199
# skip casting for nullable in OpenAPI 3.0
202-
if value is None and self.schema.getkey("nullable", False):
200+
if value is None and (self.schema / "nullable").read_bool(
201+
default=False
202+
):
203203
return value
204204

205-
schema_type = self.schema.getkey("type")
206-
205+
schema_type = (self.schema / "type").read_str(None)
207206
type_caster = self.get_type_caster(schema_type)
208207

209208
if value is None:
210209
return value
211210

212211
try:
213212
return type_caster(value)
214-
except (ValueError, TypeError):
215-
raise CastError(value, schema_type)
213+
except (ValueError, TypeError) as exc:
214+
raise CastError(value, schema_type) from exc
216215

217216
def get_type_caster(
218217
self,

openapi_core/casting/schemas/exceptions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class CastError(DeserializeError):
99
"""Schema cast operation error"""
1010

1111
value: Any
12-
type: str
12+
type: str | None
1313

1414
def __str__(self) -> str:
1515
return f"Failed to cast value to {self.type} type: {self.value}"

openapi_core/deserializing/media_types/deserializers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ def decode_property_content_type(
237237
prop_schema,
238238
mimetype=prop_content_type,
239239
)
240-
prop_schema_type = prop_schema.getkey("type", "")
240+
prop_schema_type = (prop_schema / "type").read_str("")
241241
if (
242242
self.mimetype.startswith("multipart")
243243
and prop_schema_type == "array"
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from typing import Any
2+
3+
from jsonschema_path import SchemaPath
4+
5+
from openapi_core.util import forcebool
6+
7+
8+
def cast_primitive(value: Any, schema: SchemaPath) -> Any:
9+
"""Cast a primitive value based on schema type."""
10+
schema_type = (schema / "type").read_str("")
11+
12+
if schema_type == "integer":
13+
return int(value)
14+
elif schema_type == "number":
15+
return float(value)
16+
elif schema_type == "boolean":
17+
return forcebool(value)
18+
19+
return value
20+
21+
22+
def cast_value(value: Any, schema: SchemaPath, cast: bool) -> Any:
23+
"""Recursively cast a value based on schema."""
24+
if not cast:
25+
return value
26+
27+
schema_type = (schema / "type").read_str("")
28+
29+
# Handle arrays
30+
if schema_type == "array":
31+
if not isinstance(value, list):
32+
raise ValueError(
33+
f"Expected list for array type, got {type(value)}"
34+
)
35+
items_schema = schema.get("items", SchemaPath.from_dict({}))
36+
return [cast_value(item, items_schema, cast) for item in value]
37+
38+
# Handle objects
39+
if schema_type == "object":
40+
if not isinstance(value, dict):
41+
raise ValueError(
42+
f"Expected dict for object type, got {type(value)}"
43+
)
44+
properties = schema.get("properties", SchemaPath.from_dict({}))
45+
result = {}
46+
for key, val in value.items():
47+
if key in properties:
48+
prop_schema = schema / "properties" / key
49+
result[key] = cast_value(val, prop_schema, cast)
50+
else:
51+
result[key] = val
52+
return result
53+
54+
# Handle primitives
55+
return cast_primitive(value, schema)

openapi_core/deserializing/styles/deserializers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def __init__(
2525
self.explode = explode
2626
self.name = name
2727
self.schema = schema
28-
self.schema_type = schema.getkey("type", "")
28+
self.schema_type = (schema / "type").read_str("")
2929
self.caster = caster
3030
self.deserializer_callable = deserializer_callable
3131

openapi_core/extensions/models/factories.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def create(
2727
schema: SchemaPath,
2828
fields: Iterable[Field],
2929
) -> Type[Any]:
30-
name = schema.getkey("x-model")
30+
name = (schema / "x-model").read_str(None)
3131
if name is None:
3232
return super().create(schema, fields)
3333

@@ -40,7 +40,7 @@ def create(
4040
schema: SchemaPath,
4141
fields: Iterable[Field],
4242
) -> Any:
43-
model_class_path = schema.getkey("x-model-path")
43+
model_class_path = (schema / "x-model-path").read_str(None)
4444
if model_class_path is None:
4545
return super().create(schema, fields)
4646

openapi_core/schema/encodings.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ def get_default_content_type(
2222
if prop_schema is None:
2323
return "text/plain"
2424

25-
prop_type = prop_schema.getkey("type")
25+
prop_type = (prop_schema / "type").read_str(None)
2626
if prop_type is None:
2727
return "text/plain" if encoding else "application/octet-stream"
2828

29-
prop_format = prop_schema.getkey("format")
29+
prop_format = (prop_schema / "format").read_str(None)
3030
if prop_type == "string" and prop_format in ["binary", "base64"]:
3131
return "application/octet-stream"
3232

openapi_core/schema/parameters.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def get_style(
1111
assert isinstance(param_or_header["style"], str)
1212
return param_or_header["style"]
1313

14-
location = param_or_header.getkey("in", default_location)
14+
location = (param_or_header / "in").read_str(default=default_location)
1515

1616
# determine default
1717
return "simple" if location in ["path", "header"] else "form"

0 commit comments

Comments
 (0)