Add support for SerializerMethodField (#179)

Closes #137, #179.
This commit is contained in:
John Carter
2018-08-08 06:23:36 +12:00
committed by Cristi Vîjdea
parent 1dd7cfe043
commit 748b5d3c2f
12 changed files with 507 additions and 4 deletions
+1
View File
@@ -14,6 +14,7 @@ SWAGGER_DEFAULTS = {
'drf_yasg.inspectors.DictFieldInspector',
'drf_yasg.inspectors.HiddenFieldInspector',
'drf_yasg.inspectors.RelatedFieldInspector',
'drf_yasg.inspectors.SerializerMethodFieldInspector',
'drf_yasg.inspectors.SimpleFieldInspector',
'drf_yasg.inspectors.StringDefaultFieldInspector',
],
+2 -2
View File
@@ -5,7 +5,7 @@ from .base import (
from .field import (
CamelCaseJSONFilter, ChoiceFieldInspector, DictFieldInspector, FileFieldInspector, HiddenFieldInspector,
InlineSerializerInspector, RecursiveFieldInspector, ReferencingSerializerInspector, RelatedFieldInspector,
SimpleFieldInspector, StringDefaultFieldInspector
SerializerMethodFieldInspector, SimpleFieldInspector, StringDefaultFieldInspector
)
from .query import CoreAPICompatInspector, DjangoRestResponsePagination
from .view import SwaggerAutoSchema
@@ -25,7 +25,7 @@ __all__ = [
# field inspectors
'InlineSerializerInspector', 'RecursiveFieldInspector', 'ReferencingSerializerInspector', 'RelatedFieldInspector',
'SimpleFieldInspector', 'FileFieldInspector', 'ChoiceFieldInspector', 'DictFieldInspector',
'StringDefaultFieldInspector', 'CamelCaseJSONFilter', 'HiddenFieldInspector',
'StringDefaultFieldInspector', 'CamelCaseJSONFilter', 'HiddenFieldInspector', 'SerializerMethodFieldInspector',
# view inspectors
'SwaggerAutoSchema',
+124
View File
@@ -1,5 +1,8 @@
import datetime
import inspect
import logging
import operator
import uuid
from collections import OrderedDict
from decimal import Decimal
@@ -13,6 +16,12 @@ from ..errors import SwaggerGenerationError
from ..utils import decimal_as_float, filter_none, get_serializer_ref_name
from .base import FieldInspector, NotHandled, SerializerInspector
try:
# Python>=3.5
import typing
except ImportError:
typing = None
logger = logging.getLogger(__name__)
@@ -409,6 +418,118 @@ def get_basic_type_info(field):
return result
def decimal_return_type():
return openapi.TYPE_STRING if rest_framework_settings.COERCE_DECIMAL_TO_STRING else openapi.TYPE_NUMBER
raw_type_info = [
(bool, (openapi.TYPE_BOOLEAN, None)),
(int, (openapi.TYPE_INTEGER, None)),
(float, (openapi.TYPE_NUMBER, None)),
(Decimal, (decimal_return_type, openapi.FORMAT_DECIMAL)),
(uuid.UUID, (openapi.TYPE_STRING, openapi.FORMAT_UUID)),
(datetime.datetime, (openapi.TYPE_STRING, openapi.FORMAT_DATETIME)),
(datetime.date, (openapi.TYPE_STRING, openapi.FORMAT_DATE)),
# TODO - support typing.List etc
]
hinting_type_info = raw_type_info
def get_basic_type_info_from_hint(hint_class):
"""Given a class (eg from a SerializerMethodField's return type hint,
return its basic type information - ``type``, ``format``, ``pattern``,
and any applicable min/max limit values.
:param hint_class: the class
:return: the extracted attributes as a dictionary, or ``None`` if the field type is not known
:rtype: OrderedDict
"""
for check_class, type_format in hinting_type_info:
if issubclass(hint_class, check_class):
swagger_type, format = type_format
if callable(swagger_type):
swagger_type = swagger_type()
# if callable(format):
# format = format(klass)
break
else: # pragma: no cover
return None
pattern = None
result = OrderedDict([
('type', swagger_type),
('format', format),
('pattern', pattern)
])
return result
class SerializerMethodFieldInspector(FieldInspector):
"""Provides conversion for SerializerMethodField, optionally using information from the swagger_method_field
decorator
"""
def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs):
if not isinstance(field, serializers.SerializerMethodField):
return NotHandled
method = getattr(field.parent, field.method_name)
if method is None:
return NotHandled
serializer = getattr(method, "_swagger_serializer", None)
if serializer:
# attribute added by the swagger_serializer_method decorator
serializer = getattr(method, '_swagger_serializer', None)
# in order of preference for description, use:
# 1) field.help_text from SerializerMethodField(help_text)
# 2) serializer.help_text from swagger_serializer_method(serializer)
# 3) method's docstring
description = field.help_text
if description is None:
description = getattr(serializer, 'help_text', None)
if description is None:
description = method.__doc__
label = field.label
if label is None:
label = getattr(serializer, 'label', None)
if inspect.isclass(serializer):
serializer_kwargs = {
"help_text": description,
"label": label,
"read_only": True,
}
serializer = method._swagger_serializer(**serializer_kwargs)
else:
serializer.help_text = description
serializer.label = label
serializer.read_only = True
return self.probe_field_inspectors(serializer, swagger_object_type, use_references, read_only=True)
elif typing:
# look for Python 3.5+ style type hinting of the return value
hint_class = inspect.signature(method).return_annotation
if not issubclass(hint_class, inspect._empty):
type_info = get_basic_type_info_from_hint(hint_class)
if type_info is not None:
SwaggerType, ChildSwaggerType = self._get_partial_types(field, swagger_object_type,
use_references, **kwargs)
return SwaggerType(**type_info)
return NotHandled
class SimpleFieldInspector(FieldInspector):
"""Provides conversions for fields which can be described using just ``type``, ``format``, ``pattern``
and min/max validators.
@@ -531,6 +652,7 @@ else:
"""Hack to force ``djangorestframework_camel_case`` to camelize a plain string."""
return next(iter(camelize({s: ''})))
def camelize_schema(schema_or_ref, components):
"""Recursively camelize property names for the given schema using ``djangorestframework_camel_case``."""
schema = openapi.resolve_ref(schema_or_ref, components)
@@ -545,6 +667,7 @@ else:
return schema_or_ref
class CamelCaseJSONFilter(FieldInspector):
"""Converts property names to camelCase if ``CamelCaseJSONParser`` or ``CamelCaseJSONRenderer`` are used."""
@@ -569,6 +692,7 @@ except ImportError: # pragma: no cover
else:
class RecursiveFieldInspector(FieldInspector):
"""Provides conversion for RecursiveField (https://github.com/heywbj/django-rest-framework-recursive)"""
def field_to_swagger_object(self, field, swagger_object_type, use_references, **kwargs):
if isinstance(field, RecursiveField) and swagger_object_type == openapi.Schema:
assert use_references is True, "Can not create schema for RecursiveField when use_references is False"
+17
View File
@@ -170,6 +170,23 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_bo
return decorator
def swagger_serializer_method(serializer):
"""
Decorates the method of a serializers.SerializerMethodField
to hint as to how Swagger should be generated for this field.
:param serializer: serializer class or instance
:return:
"""
def decorator(serializer_method):
# stash the serializer for SerializerMethodFieldInspector to find
serializer_method._swagger_serializer = serializer
return serializer_method
return decorator
def is_list_view(path, method, view):
"""Check if the given path/method appears to represent a list view (as opposed to a detail/instance view).