Skip to content

Commit 57ecb4a

Browse files
committed
improve docstring extraction #96
1 parent c32793c commit 57ecb4a

3 files changed

Lines changed: 45 additions & 7 deletions

File tree

drf_spectacular/openapi.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import inspect
21
import re
32
import typing
43
from operator import attrgetter
@@ -23,8 +22,8 @@
2322
from drf_spectacular.plumbing import (
2423
ComponentRegistry, ResolvedComponent, anyisinstance, append_meta, build_array_type,
2524
build_basic_type, build_choice_field, build_object_type, build_parameter_type, error,
26-
follow_field_source, force_instance, get_override, has_override, is_basic_type, is_field,
27-
is_serializer, resolve_regex_path_parameter, safe_ref, warn,
25+
follow_field_source, force_instance, get_doc, get_override, has_override, is_basic_type,
26+
is_field, is_serializer, resolve_regex_path_parameter, safe_ref, warn,
2827
)
2928
from drf_spectacular.settings import spectacular_settings
3029
from drf_spectacular.types import OpenApiTypes
@@ -171,8 +170,8 @@ def dict_helper(parameters):
171170
def get_description(self):
172171
""" override this for custom behaviour """
173172
action_or_method = getattr(self.view, getattr(self.view, 'action', self.method.lower()), None)
174-
view_doc = inspect.getdoc(self.view) or ''
175-
action_doc = inspect.getdoc(action_or_method) or ''
173+
view_doc = get_doc(self.view.__class__)
174+
action_doc = get_doc(action_or_method)
176175
return action_doc or view_doc
177176

178177
def get_summary(self):
@@ -645,7 +644,7 @@ def _map_basic_serializer(self, serializer, direction):
645644
return build_object_type(
646645
properties=properties,
647646
required=required,
648-
description=inspect.getdoc(serializer),
647+
description=get_doc(serializer.__class__),
649648
)
650649

651650
def _map_field_validators(self, field, schema):

drf_spectacular/plumbing.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
from django import __version__ as DJANGO_VERSION
1515
from django.urls.resolvers import _PATH_PARAMETER_COMPONENT_RE, get_resolver # type: ignore
1616
from django.utils.module_loading import import_string
17-
from rest_framework import exceptions, fields, serializers, versioning
17+
from rest_framework import (
18+
exceptions, fields, generics, mixins, serializers, versioning, views, viewsets,
19+
)
1820
from uritemplate import URITemplate
1921

2022
from drf_spectacular.settings import spectacular_settings
@@ -131,6 +133,40 @@ def get_override(obj, prop):
131133
return obj._spectacular_annotation[prop]
132134

133135

136+
def get_doc(obj):
137+
""" get doc string with fallback on obj's base classes (ignoring DRF documentation). """
138+
if not inspect.isclass(obj):
139+
return inspect.getdoc(obj) or ''
140+
141+
def safe_index(lst, item):
142+
try:
143+
return lst.index(item)
144+
except ValueError:
145+
return float("inf")
146+
147+
lib_doc_excludes = [
148+
serializers.Serializer,
149+
serializers.ModelSerializer,
150+
serializers.HyperlinkedModelSerializer,
151+
viewsets.ModelViewSet,
152+
viewsets.GenericViewSet,
153+
viewsets.ViewSet,
154+
viewsets.ReadOnlyModelViewSet,
155+
generics.GenericAPIView,
156+
mixins.ListModelMixin,
157+
mixins.CreateModelMixin,
158+
mixins.RetrieveModelMixin,
159+
mixins.UpdateModelMixin,
160+
mixins.DestroyModelMixin,
161+
views.APIView,
162+
]
163+
lib_barrier = min(safe_index(obj.__mro__, c) for c in lib_doc_excludes)
164+
for cls in obj.__mro__[:lib_barrier]:
165+
if cls.__doc__:
166+
return inspect.cleandoc(cls.__doc__)
167+
return ''
168+
169+
134170
def build_basic_type(obj):
135171
"""
136172
resolve either enum or actual type and yield schema template for modification

tests/test_extensions.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class XViewset(mixins.ListModelMixin, viewsets.GenericViewSet):
3535
class XView(APIView):
3636
""" underspecified library view """
3737
def get(self):
38+
""" docstring for GET """
3839
return Response(1.234) # pragma: no cover
3940

4041

@@ -53,6 +54,7 @@ def get(self, request):
5354
schema = generate_schema('x', view=XView)
5455
operation = schema['paths']['/x']['get']
5556
assert get_response_schema(operation)['type'] == 'number'
57+
assert operation['description'].strip() == 'docstring for GET'
5658

5759

5860
@api_view()
@@ -72,3 +74,4 @@ def view_replacement(self):
7274
schema = generate_schema('x', view_function=x_view_function)
7375
operation = schema['paths']['/x']['get']
7476
assert get_response_schema(operation)['type'] == 'number'
77+
assert operation['description'].strip() == 'underspecified library view'

0 commit comments

Comments
 (0)