Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions openapi_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from openapi_core.shortcuts import validate_response
from openapi_core.shortcuts import validate_webhook_request
from openapi_core.shortcuts import validate_webhook_response
from openapi_core.spec.paths import Spec
from openapi_core.unmarshalling.request import V3RequestUnmarshaller
from openapi_core.unmarshalling.request import V3WebhookRequestUnmarshaller
from openapi_core.unmarshalling.request import V30RequestUnmarshaller
Expand Down Expand Up @@ -45,7 +44,6 @@
__all__ = [
"OpenAPI",
"Config",
"Spec",
"unmarshal_request",
"unmarshal_response",
"unmarshal_apicall_request",
Expand Down
4 changes: 2 additions & 2 deletions openapi_core/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ def from_file(

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

try:
validate(
self.spec.contents(),
self.spec.read_value(),
base_uri=self.config.spec_base_uri
or self.spec.accessor.resolver._base_uri, # type: ignore[attr-defined]
cls=cls,
Expand Down
15 changes: 7 additions & 8 deletions openapi_core/casting/schemas/casters.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,7 @@ def _cast_proparties(
if schema_only:
return value

additional_properties = self.schema.getkey(
"additionalProperties", True
)
additional_properties = self.schema.get("additionalProperties", True)
if additional_properties is not False:
# free-form object
if additional_properties is True:
Expand Down Expand Up @@ -199,20 +197,21 @@ def __init__(

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

schema_type = self.schema.getkey("type")

schema_type = (self.schema / "type").read_str(None)
type_caster = self.get_type_caster(schema_type)

if value is None:
return value

try:
return type_caster(value)
except (ValueError, TypeError):
raise CastError(value, schema_type)
except (ValueError, TypeError) as exc:
raise CastError(value, schema_type) from exc

def get_type_caster(
self,
Expand Down
2 changes: 1 addition & 1 deletion openapi_core/casting/schemas/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class CastError(DeserializeError):
"""Schema cast operation error"""

value: Any
type: str
type: str | None

def __str__(self) -> str:
return f"Failed to cast value to {self.type} type: {self.value}"
2 changes: 1 addition & 1 deletion openapi_core/deserializing/media_types/deserializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ def decode_property_content_type(
prop_schema,
mimetype=prop_content_type,
)
prop_schema_type = prop_schema.getkey("type", "")
prop_schema_type = (prop_schema / "type").read_str("")
if (
self.mimetype.startswith("multipart")
and prop_schema_type == "array"
Expand Down
55 changes: 55 additions & 0 deletions openapi_core/deserializing/styles/casters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from typing import Any

from jsonschema_path import SchemaPath

from openapi_core.util import forcebool


def cast_primitive(value: Any, schema: SchemaPath) -> Any:
"""Cast a primitive value based on schema type."""
schema_type = (schema / "type").read_str("")

if schema_type == "integer":
return int(value)
elif schema_type == "number":
return float(value)
elif schema_type == "boolean":
return forcebool(value)

return value


def cast_value(value: Any, schema: SchemaPath, cast: bool) -> Any:
"""Recursively cast a value based on schema."""
if not cast:
return value

schema_type = (schema / "type").read_str("")

# Handle arrays
if schema_type == "array":
if not isinstance(value, list):
raise ValueError(
f"Expected list for array type, got {type(value)}"
)
items_schema = schema.get("items", SchemaPath.from_dict({}))
return [cast_value(item, items_schema, cast) for item in value]

# Handle objects
if schema_type == "object":
if not isinstance(value, dict):
raise ValueError(
f"Expected dict for object type, got {type(value)}"
)
properties = schema.get("properties", SchemaPath.from_dict({}))
result = {}
for key, val in value.items():
if key in properties:
prop_schema = schema / "properties" / key
result[key] = cast_value(val, prop_schema, cast)
else:
result[key] = val
return result

# Handle primitives
return cast_primitive(value, schema)
2 changes: 1 addition & 1 deletion openapi_core/deserializing/styles/deserializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def __init__(
self.explode = explode
self.name = name
self.schema = schema
self.schema_type = schema.getkey("type", "")
self.schema_type = (schema / "type").read_str("")
self.caster = caster
self.deserializer_callable = deserializer_callable

Expand Down
4 changes: 2 additions & 2 deletions openapi_core/extensions/models/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def create(
schema: SchemaPath,
fields: Iterable[Field],
) -> Type[Any]:
name = schema.getkey("x-model")
name = (schema / "x-model").read_str(None)
if name is None:
return super().create(schema, fields)

Expand All @@ -40,7 +40,7 @@ def create(
schema: SchemaPath,
fields: Iterable[Field],
) -> Any:
model_class_path = schema.getkey("x-model-path")
model_class_path = (schema / "x-model-path").read_str(None)
if model_class_path is None:
return super().create(schema, fields)

Expand Down
4 changes: 2 additions & 2 deletions openapi_core/schema/encodings.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ def get_default_content_type(
if prop_schema is None:
return "text/plain"

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

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

Expand Down
2 changes: 1 addition & 1 deletion openapi_core/schema/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def get_style(
assert isinstance(param_or_header["style"], str)
return param_or_header["style"]

location = param_or_header.getkey("in", default_location)
location = (param_or_header / "in").read_str(default=default_location)

# determine default
return "simple" if location in ["path", "header"] else "form"
Expand Down
2 changes: 1 addition & 1 deletion openapi_core/schema/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@


def get_properties(schema: SchemaPath) -> Dict[str, Any]:
properties = schema.get("properties", {})
properties = schema.get("properties", SchemaPath.from_dict({}))
properties_dict = dict(list(properties.items()))
return properties_dict
2 changes: 1 addition & 1 deletion openapi_core/schema/servers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def get_server_default_variables(server: SchemaPath) -> Dict[str, Any]:

defaults = {}
variables = server / "variables"
for name, variable in list(variables.items()):
for name, variable in list(variables.str_items()):
defaults[name] = variable["default"]
return defaults

Expand Down
2 changes: 1 addition & 1 deletion openapi_core/security/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ class SecurityProviderFactory:
}

def create(self, scheme: SchemaPath) -> Any:
scheme_type = scheme["type"]
scheme_type = (scheme / "type").read_str()
provider_class = self.PROVIDERS[scheme_type]
return provider_class(scheme)
4 changes: 2 additions & 2 deletions openapi_core/security/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ def __call__(self, parameters: RequestParameters) -> Any:

class ApiKeyProvider(BaseProvider):
def __call__(self, parameters: RequestParameters) -> Any:
name = self.scheme["name"]
location = self.scheme["in"]
name = (self.scheme / "name").read_str()
location = (self.scheme / "in").read_str()
source = getattr(parameters, location)
if name not in source:
raise SecurityProviderError("Missing api key parameter.")
Expand Down
Empty file removed openapi_core/spec/__init__.py
Empty file.
13 changes: 0 additions & 13 deletions openapi_core/spec/paths.py

This file was deleted.

4 changes: 2 additions & 2 deletions openapi_core/templating/media_types/finders.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ def find(self, mimetype: str) -> MediaType:

# range mime type
if mime_type:
for key, value in self.content.items():
for key, value in self.content.str_items():
if fnmatch.fnmatch(mime_type, key):
return MediaType(key, parameters, value)

raise MediaTypeNotFound(mimetype, list(self.content.keys()))
raise MediaTypeNotFound(mimetype, list(self.content.str_keys()))

def _parse_mimetype(self, mimetype: str) -> Tuple[str, Mapping[str, str]]:
mimetype_parts = mimetype.split(";")
Expand Down
4 changes: 2 additions & 2 deletions openapi_core/templating/paths/iterators.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def __call__(
paths = spec / self.paths_part
if not paths.exists():
raise PathsNotFound(paths.as_uri())
for path_name, path in list(paths.items()):
for path_name, path in list(paths.str_items()):
if name == path_name:
path_result = TemplateResult(path_name, {})
yield Path(path, path_result)
Expand All @@ -44,7 +44,7 @@ def __call__(
if not paths.exists():
raise PathsNotFound(paths.as_uri())
template_paths: List[Path] = []
for path_pattern, path in list(paths.items()):
for path_pattern, path in list(paths.str_items()):
# simple path.
# Return right away since it is always the most concrete
if name.endswith(path_pattern):
Expand Down
4 changes: 3 additions & 1 deletion openapi_core/templating/responses/finders.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ def find(self, http_status: str = "default") -> SchemaPath:
return self.responses / http_status_range

if "default" not in self.responses:
raise ResponseNotFound(http_status, list(self.responses.keys()))
raise ResponseNotFound(
http_status, list(self.responses.str_keys())
)

return self.responses / "default"
4 changes: 2 additions & 2 deletions openapi_core/unmarshalling/schemas/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def create(
if schema is None:
raise TypeError("Invalid schema")

if schema.getkey("deprecated", False):
if (schema / "deprecated").read_bool(default=False):
warnings.warn("The schema is deprecated", DeprecationWarning)

if extra_format_validators is None:
Expand All @@ -54,7 +54,7 @@ def create(
extra_format_validators=extra_format_validators,
)

schema_format = schema.getkey("format")
schema_format = (schema / "format").read_str(None)

formats_unmarshaller = FormatsUnmarshaller(
format_unmarshallers or self.format_unmarshallers,
Expand Down
12 changes: 6 additions & 6 deletions openapi_core/unmarshalling/schemas/unmarshallers.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,7 @@ def _unmarshal_properties(
if schema_only:
return properties

additional_properties = self.schema.getkey(
"additionalProperties", True
)
additional_properties = self.schema.get("additionalProperties", True)
if additional_properties is not False:
# free-form object
if additional_properties is True:
Expand Down Expand Up @@ -244,10 +242,12 @@ def unmarshal(self, value: Any) -> Any:
self.schema_validator.validate(value)

# skip unmarshalling for nullable in OpenAPI 3.0
if value is None and self.schema.getkey("nullable", False):
if value is None and (self.schema / "nullable").read_bool(
default=False
):
return value

schema_type = self.schema.getkey("type")
schema_type = (self.schema / "type").read_str_or_list(None)
type_unmarshaller = self.get_type_unmarshaller(schema_type)
typed = type_unmarshaller(value)
# skip finding format for None
Expand Down Expand Up @@ -307,5 +307,5 @@ def find_format(self, value: Any) -> Optional[str]:
if primitive_type != "string":
continue
if "format" in schema:
return str(schema.getkey("format"))
return (schema / "format").read_str()
return None
4 changes: 3 additions & 1 deletion openapi_core/validation/request/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ class ParameterValidationError(RequestValidationError):

@classmethod
def from_spec(cls, spec: SchemaPath) -> "ParameterValidationError":
return cls(spec["name"], spec["in"])
name = (spec / "name").read_str()
location = (spec / "in").read_str()
return cls(name, location)

def __str__(self) -> str:
return f"{self.location.title()} parameter error: {self.name}"
Expand Down
Loading
Loading