Skip to content

OpenAPI: Map renderers/parsers for request/response media-types. #6865

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 6 commits into from
Nov 6, 2019
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
34 changes: 29 additions & 5 deletions rest_framework/schemas/openapi.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import warnings
from operator import attrgetter
from urllib.parse import urljoin

from django.core.validators import (
Expand All @@ -8,7 +9,7 @@
from django.db import models
from django.utils.encoding import force_str

from rest_framework import exceptions, serializers
from rest_framework import exceptions, renderers, serializers
from rest_framework.compat import uritemplate
from rest_framework.fields import _UnvalidatedField, empty

Expand Down Expand Up @@ -78,7 +79,9 @@ def get_schema(self, request=None, public=False):

class AutoSchema(ViewInspector):

content_types = ['application/json']
request_media_types = []
response_media_types = []

method_mapping = {
'get': 'Retrieve',
'post': 'Create',
Expand Down Expand Up @@ -336,6 +339,12 @@ def _map_field(self, field):
self._map_min_max(field, content)
return content

if isinstance(field, serializers.FileField):
return {
'type': 'string',
'format': 'binary'
}

# Simplest cases, default to 'string' type:
FIELD_CLASS_SCHEMA_TYPE = {
serializers.BooleanField: 'boolean',
Expand Down Expand Up @@ -430,9 +439,20 @@ def _get_pagninator(self):
pagination_class = getattr(self.view, 'pagination_class', None)
if pagination_class:
return pagination_class()

return None

def map_parsers(self, path, method):
return list(map(attrgetter('media_type'), self.view.parser_classes))

def map_renderers(self, path, method):
media_types = []
for renderer in self.view.renderer_classes:
# BrowsableAPIRenderer not relevant to OpenAPI spec
if renderer == renderers.BrowsableAPIRenderer:
continue
media_types.append(renderer.media_type)
return media_types

def _get_serializer(self, method, path):
view = self.view

Expand All @@ -452,6 +472,8 @@ def _get_request_body(self, path, method):
if method not in ('PUT', 'PATCH', 'POST'):
return {}

self.request_media_types = self.map_parsers(path, method)

serializer = self._get_serializer(path, method)

if not isinstance(serializer, serializers.Serializer):
Expand All @@ -469,7 +491,7 @@ def _get_request_body(self, path, method):
return {
'content': {
ct: {'schema': content}
for ct in self.content_types
for ct in self.request_media_types
}
}

Expand All @@ -482,6 +504,8 @@ def _get_responses(self, path, method):
}
}

self.response_media_types = self.map_renderers(path, method)

item_schema = {}
serializer = self._get_serializer(path, method)

Expand Down Expand Up @@ -509,7 +533,7 @@ def _get_responses(self, path, method):
'200': {
'content': {
ct: {'schema': response_schema}
for ct in self.content_types
for ct in self.response_media_types
},
# description is a mandatory property,
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#responseObject
Expand Down
73 changes: 73 additions & 0 deletions tests/schemas/test_openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

from rest_framework import filters, generics, pagination, routers, serializers
from rest_framework.compat import uritemplate
from rest_framework.parsers import JSONParser, MultiPartParser
from rest_framework.renderers import JSONRenderer
from rest_framework.request import Request
from rest_framework.schemas.openapi import AutoSchema, SchemaGenerator

Expand Down Expand Up @@ -339,6 +341,77 @@ class View(generics.DestroyAPIView):
},
}

def test_parser_mapping(self):
"""Test that view's parsers are mapped to OA media types"""
path = '/{id}/'
method = 'POST'

class View(generics.CreateAPIView):
serializer_class = views.ExampleSerializer
parser_classes = [JSONParser, MultiPartParser]

view = create_view(
View,
method,
create_request(path),
)
inspector = AutoSchema()
inspector.view = view

request_body = inspector._get_request_body(path, method)

assert len(request_body['content'].keys()) == 2
assert 'multipart/form-data' in request_body['content']
assert 'application/json' in request_body['content']

def test_renderer_mapping(self):
"""Test that view's renderers are mapped to OA media types"""
path = '/{id}/'
method = 'GET'

class View(generics.CreateAPIView):
serializer_class = views.ExampleSerializer
renderer_classes = [JSONRenderer]

view = create_view(
View,
method,
create_request(path),
)
inspector = AutoSchema()
inspector.view = view

responses = inspector._get_responses(path, method)
# TODO this should be changed once the multiple response
# schema support is there
success_response = responses['200']

assert len(success_response['content'].keys()) == 1
assert 'application/json' in success_response['content']

def test_serializer_filefield(self):
path = '/{id}/'
method = 'POST'

class ItemSerializer(serializers.Serializer):
attachment = serializers.FileField()

class View(generics.CreateAPIView):
serializer_class = ItemSerializer

view = create_view(
View,
method,
create_request(path),
)
inspector = AutoSchema()
inspector.view = view

request_body = inspector._get_request_body(path, method)
mp_media = request_body['content']['multipart/form-data']
attachment = mp_media['schema']['properties']['attachment']
assert attachment['format'] == 'binary'

def test_retrieve_response_body_generation(self):
"""
Test that a list of properties is returned for retrieve item views.
Expand Down
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy