Skip to content

Media type finder #307

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 30, 2021
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
4 changes: 2 additions & 2 deletions openapi_core/contrib/falcon/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from falcon.status_codes import (
HTTP_400, HTTP_404, HTTP_405, HTTP_415,
)
from openapi_core.schema.media_types.exceptions import InvalidContentType
from openapi_core.templating.media_types.exceptions import MediaTypeNotFound
from openapi_core.templating.paths.exceptions import (
ServerNotFound, OperationNotFound, PathNotFound,
)
Expand All @@ -17,7 +17,7 @@ class FalconOpenAPIErrorsHandler(object):
ServerNotFound: 400,
OperationNotFound: 405,
PathNotFound: 404,
InvalidContentType: 415,
MediaTypeNotFound: 415,
}

FALCON_STATUS_CODES = {
Expand Down
4 changes: 2 additions & 2 deletions openapi_core/contrib/flask/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from flask.globals import current_app
from flask.json import dumps

from openapi_core.schema.media_types.exceptions import InvalidContentType
from openapi_core.templating.media_types.exceptions import MediaTypeNotFound
from openapi_core.templating.paths.exceptions import (
ServerNotFound, OperationNotFound, PathNotFound,
)
Expand All @@ -14,7 +14,7 @@ class FlaskOpenAPIErrorsHandler(object):
ServerNotFound: 400,
OperationNotFound: 405,
PathNotFound: 404,
InvalidContentType: 415,
MediaTypeNotFound: 415,
}

@classmethod
Expand Down
17 changes: 0 additions & 17 deletions openapi_core/schema/content/exceptions.py

This file was deleted.

18 changes: 1 addition & 17 deletions openapi_core/schema/content/models.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,5 @@
"""OpenAPI core content models module"""
import fnmatch

from six import iteritems

from openapi_core.schema.content.exceptions import MimeTypeNotFound


class Content(dict):

def __getitem__(self, mimetype):
try:
return super(Content, self).__getitem__(mimetype)
except KeyError:
pass

for key, value in iteritems(self):
if fnmatch.fnmatch(mimetype, key):
return value

raise MimeTypeNotFound(mimetype, list(self.keys()))
pass
8 changes: 0 additions & 8 deletions openapi_core/schema/request_bodies/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
"""OpenAPI core request bodies models module"""
from openapi_core.schema.content.exceptions import MimeTypeNotFound
from openapi_core.schema.media_types.exceptions import InvalidContentType


class RequestBody(object):
Expand All @@ -11,9 +9,3 @@ def __init__(self, content, required=False, extensions=None):
self.required = required

self.extensions = extensions and dict(extensions) or {}

def __getitem__(self, mimetype):
try:
return self.content[mimetype]
except MimeTypeNotFound:
raise InvalidContentType(mimetype)
11 changes: 0 additions & 11 deletions openapi_core/schema/responses/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
"""OpenAPI core responses models module"""
from openapi_core.schema.content.exceptions import MimeTypeNotFound
from openapi_core.schema.media_types.exceptions import InvalidContentType


class Response(object):
Expand All @@ -15,12 +13,3 @@ def __init__(
self.links = links and dict(links) or {}

self.extensions = extensions and dict(extensions) or {}

def __getitem__(self, mimetype):
return self.get_content_type(mimetype)

def get_content_type(self, mimetype):
try:
return self.content[mimetype]
except MimeTypeNotFound:
raise InvalidContentType(mimetype)
Empty file.
19 changes: 19 additions & 0 deletions openapi_core/templating/media_types/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import attr

from openapi_core.exceptions import OpenAPIError


class MediaTypeFinderError(OpenAPIError):
"""Media type finder error"""


@attr.s(hash=True)
class MediaTypeNotFound(MediaTypeFinderError):
mimetype = attr.ib()
availableMimetypes = attr.ib()

def __str__(self):
return (
"Content for the following mimetype not found: {0}. "
"Valid mimetypes: {1}"
).format(self.mimetype, self.availableMimetypes)
24 changes: 24 additions & 0 deletions openapi_core/templating/media_types/finders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""OpenAPI core templating media types finders module"""
import fnmatch

from six import iteritems

from openapi_core.templating.media_types.exceptions import MediaTypeNotFound


class MediaTypeFinder(object):

def __init__(self, content):
self.content = content

def find(self, request):
try:
return self.content[request.mimetype]
except KeyError:
pass

for key, value in iteritems(self.content):
if fnmatch.fnmatch(request.mimetype, key):
return value

raise MediaTypeNotFound(request.mimetype, list(self.content.keys()))
7 changes: 4 additions & 3 deletions openapi_core/validation/request/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@

from openapi_core.casting.schemas.exceptions import CastError
from openapi_core.deserializing.exceptions import DeserializeError
from openapi_core.schema.media_types.exceptions import InvalidContentType
from openapi_core.schema.parameters.exceptions import (
MissingRequiredParameter, MissingParameter,
)
from openapi_core.schema.request_bodies.exceptions import MissingRequestBody
from openapi_core.security.exceptions import SecurityError
from openapi_core.templating.media_types.exceptions import MediaTypeFinderError
from openapi_core.templating.paths.exceptions import PathError
from openapi_core.unmarshalling.schemas.enums import UnmarshalContext
from openapi_core.unmarshalling.schemas.exceptions import (
Expand Down Expand Up @@ -154,8 +154,9 @@ def _get_body(self, request, operation):
return None, []

try:
media_type = operation.request_body[request.mimetype]
except InvalidContentType as exc:
media_type = self._get_media_type(
operation.request_body.content, request)
except MediaTypeFinderError as exc:
return None, [exc, ]

try:
Expand Down
7 changes: 4 additions & 3 deletions openapi_core/validation/response/validators.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"""OpenAPI core validation response validators module"""
from openapi_core.casting.schemas.exceptions import CastError
from openapi_core.deserializing.exceptions import DeserializeError
from openapi_core.schema.media_types.exceptions import InvalidContentType
from openapi_core.schema.responses.exceptions import (
InvalidResponse, MissingResponseContent,
)
from openapi_core.templating.media_types.exceptions import MediaTypeFinderError
from openapi_core.templating.paths.exceptions import PathError
from openapi_core.unmarshalling.schemas.enums import UnmarshalContext
from openapi_core.unmarshalling.schemas.exceptions import (
Expand Down Expand Up @@ -70,8 +70,9 @@ def _get_data(self, response, operation_response):
return None, []

try:
media_type = operation_response[response.mimetype]
except InvalidContentType as exc:
media_type = self._get_media_type(
operation_response.content, response)
except MediaTypeFinderError as exc:
return None, [exc, ]

try:
Expand Down
5 changes: 5 additions & 0 deletions openapi_core/validation/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ def _find_path(self, request):
finder = PathFinder(self.spec, base_url=self.base_url)
return finder.find(request)

def _get_media_type(self, content, request_or_response):
from openapi_core.templating.media_types.finders import MediaTypeFinder
finder = MediaTypeFinder(content)
return finder.find(request_or_response)

def _deserialise_media_type(self, media_type, value):
from openapi_core.deserializing.media_types.factories import (
MediaTypeDeserializersFactory,
Expand Down
7 changes: 4 additions & 3 deletions tests/integration/contrib/falcon/test_falcon_middlewares.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,13 @@ def view_response_callable(request, response, id):
'errors': [
{
'class': (
"<class 'openapi_core.schema.media_types.exceptions."
"InvalidContentType'>"
"<class 'openapi_core.templating.media_types."
"exceptions.MediaTypeNotFound'>"
),
'status': 415,
'title': (
'Content for following mimetype not found: text/html'
"Content for the following mimetype not found: "
"text/html. Valid mimetypes: ['application/json']"
)
}
]
Expand Down
7 changes: 4 additions & 3 deletions tests/integration/contrib/flask/test_flask_decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,13 @@ def view_response_callable(*args, **kwargs):
'errors': [
{
'class': (
"<class 'openapi_core.schema.media_types.exceptions."
"InvalidContentType'>"
"<class 'openapi_core.templating.media_types."
"exceptions.MediaTypeNotFound'>"
),
'status': 415,
'title': (
'Content for following mimetype not found: text/html'
"Content for the following mimetype not found: "
"text/html. Valid mimetypes: ['application/json']"
)
}
]
Expand Down
7 changes: 4 additions & 3 deletions tests/integration/contrib/flask/test_flask_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,13 @@ def test_invalid_content_type(self, client):
'errors': [
{
'class': (
"<class 'openapi_core.schema.media_types.exceptions."
"InvalidContentType'>"
"<class 'openapi_core.templating.media_types."
"exceptions.MediaTypeNotFound'>"
),
'status': 415,
'title': (
'Content for following mimetype not found: text/html'
"Content for the following mimetype not found: "
"text/html. Valid mimetypes: ['application/json']"
)
}
]
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/validation/test_petstore.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
EmptyParameterValue,
)
from openapi_core.extensions.models.models import BaseModel
from openapi_core.schema.media_types.exceptions import InvalidContentType
from openapi_core.schema.parameters.exceptions import (
MissingRequiredParameter,
)
from openapi_core.schema.schemas.enums import SchemaType
from openapi_core.shortcuts import (
create_spec, validate_parameters, validate_body, validate_data,
)
from openapi_core.templating.media_types.exceptions import MediaTypeNotFound
from openapi_core.templating.paths.exceptions import (
ServerNotFound,
)
Expand Down Expand Up @@ -662,7 +662,7 @@ def test_post_pets_raises_invalid_mimetype(self, spec):
},
)

with pytest.raises(InvalidContentType):
with pytest.raises(MediaTypeNotFound):
validate_body(spec, request)

def test_post_pets_missing_cookie(self, spec, spec_dict):
Expand Down
8 changes: 3 additions & 5 deletions tests/integration/validation/test_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,14 @@

from openapi_core.casting.schemas.exceptions import CastError
from openapi_core.deserializing.exceptions import DeserializeError
from openapi_core.schema.media_types.exceptions import (
InvalidContentType,
)
from openapi_core.extensions.models.models import BaseModel
from openapi_core.schema.parameters.exceptions import MissingRequiredParameter
from openapi_core.schema.request_bodies.exceptions import MissingRequestBody
from openapi_core.schema.responses.exceptions import (
MissingResponseContent, InvalidResponse,
)
from openapi_core.shortcuts import create_spec
from openapi_core.templating.media_types.exceptions import MediaTypeNotFound
from openapi_core.templating.paths.exceptions import (
PathNotFound, OperationNotFound,
)
Expand Down Expand Up @@ -184,7 +182,7 @@ def test_invalid_content_type(self, validator):
result = validator.validate(request)

assert len(result.errors) == 1
assert type(result.errors[0]) == InvalidContentType
assert type(result.errors[0]) == MediaTypeNotFound
assert result.body is None
assert result.parameters == RequestParameters(
header={
Expand Down Expand Up @@ -463,7 +461,7 @@ def test_invalid_content_type(self, validator):
result = validator.validate(request, response)

assert len(result.errors) == 1
assert type(result.errors[0]) == InvalidContentType
assert type(result.errors[0]) == MediaTypeNotFound
assert result.data is None
assert result.headers == {}

Expand Down