Skip to content

Commit 37f210a

Browse files
carltongibsonLucidiotdongfangtianyu
authored
Added OpenAPI Schema Generation. (#6532)
Co-authored-by: Lucidiot <lucidiot@protonmail.com> Co-authored-by: dongfangtianyu <dongfangtianyu@qq.com>
1 parent a91e6a0 commit 37f210a

18 files changed

+1669
-732
lines changed

rest_framework/filters.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ def get_schema_fields(self, view):
3737
assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`'
3838
return []
3939

40+
def get_schema_operation_parameters(self, view):
41+
return []
42+
4043

4144
class SearchFilter(BaseFilterBackend):
4245
# The URL query parameter used for the search.
@@ -156,6 +159,19 @@ def get_schema_fields(self, view):
156159
)
157160
]
158161

162+
def get_schema_operation_parameters(self, view):
163+
return [
164+
{
165+
'name': self.search_param,
166+
'required': False,
167+
'in': 'query',
168+
'description': force_text(self.search_description),
169+
'schema': {
170+
'type': 'string',
171+
},
172+
},
173+
]
174+
159175

160176
class OrderingFilter(BaseFilterBackend):
161177
# The URL query parameter used for the ordering.
@@ -287,6 +303,19 @@ def get_schema_fields(self, view):
287303
)
288304
]
289305

306+
def get_schema_operation_parameters(self, view):
307+
return [
308+
{
309+
'name': self.ordering_param,
310+
'required': False,
311+
'in': 'query',
312+
'description': force_text(self.ordering_description),
313+
'schema': {
314+
'type': 'string',
315+
},
316+
},
317+
]
318+
290319

291320
class DjangoObjectPermissionsFilter(BaseFilterBackend):
292321
"""
Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,56 @@
11
from django.core.management.base import BaseCommand
22

3-
from rest_framework.compat import coreapi
4-
from rest_framework.renderers import (
5-
CoreJSONRenderer, JSONOpenAPIRenderer, OpenAPIRenderer
6-
)
7-
from rest_framework.schemas.generators import SchemaGenerator
3+
from rest_framework import renderers
4+
from rest_framework.schemas import coreapi
5+
from rest_framework.schemas.openapi import SchemaGenerator
6+
7+
OPENAPI_MODE = 'openapi'
8+
COREAPI_MODE = 'coreapi'
89

910

1011
class Command(BaseCommand):
1112
help = "Generates configured API schema for project."
1213

14+
def get_mode(self):
15+
return COREAPI_MODE if coreapi.is_enabled() else OPENAPI_MODE
16+
1317
def add_arguments(self, parser):
14-
parser.add_argument('--title', dest="title", default=None, type=str)
18+
parser.add_argument('--title', dest="title", default='', type=str)
1519
parser.add_argument('--url', dest="url", default=None, type=str)
1620
parser.add_argument('--description', dest="description", default=None, type=str)
17-
parser.add_argument('--format', dest="format", choices=['openapi', 'openapi-json', 'corejson'], default='openapi', type=str)
21+
if self.get_mode() == COREAPI_MODE:
22+
parser.add_argument('--format', dest="format", choices=['openapi', 'openapi-json', 'corejson'], default='openapi', type=str)
23+
else:
24+
parser.add_argument('--format', dest="format", choices=['openapi', 'openapi-json'], default='openapi', type=str)
1825

1926
def handle(self, *args, **options):
20-
assert coreapi is not None, 'coreapi must be installed.'
21-
22-
generator = SchemaGenerator(
27+
generator_class = self.get_generator_class()
28+
generator = generator_class(
2329
url=options['url'],
2430
title=options['title'],
2531
description=options['description']
2632
)
27-
2833
schema = generator.get_schema(request=None, public=True)
29-
3034
renderer = self.get_renderer(options['format'])
3135
output = renderer.render(schema, renderer_context={})
3236
self.stdout.write(output.decode())
3337

3438
def get_renderer(self, format):
39+
if self.get_mode() == COREAPI_MODE:
40+
renderer_cls = {
41+
'corejson': renderers.CoreJSONRenderer,
42+
'openapi': renderers.CoreAPIOpenAPIRenderer,
43+
'openapi-json': renderers.CoreAPIJSONOpenAPIRenderer,
44+
}[format]
45+
return renderer_cls()
46+
3547
renderer_cls = {
36-
'corejson': CoreJSONRenderer,
37-
'openapi': OpenAPIRenderer,
38-
'openapi-json': JSONOpenAPIRenderer,
48+
'openapi': renderers.OpenAPIRenderer,
49+
'openapi-json': renderers.JSONOpenAPIRenderer,
3950
}[format]
40-
4151
return renderer_cls()
52+
53+
def get_generator_class(self):
54+
if self.get_mode() == COREAPI_MODE:
55+
return coreapi.SchemaGenerator
56+
return SchemaGenerator

rest_framework/pagination.py

Lines changed: 86 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@ def get_schema_fields(self, view):
148148
assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
149149
return []
150150

151+
def get_schema_operation_parameters(self, view):
152+
return []
153+
151154

152155
class PageNumberPagination(BasePagination):
153156
"""
@@ -301,6 +304,32 @@ def get_schema_fields(self, view):
301304
)
302305
return fields
303306

307+
def get_schema_operation_parameters(self, view):
308+
parameters = [
309+
{
310+
'name': self.page_query_param,
311+
'required': False,
312+
'in': 'query',
313+
'description': force_text(self.page_query_description),
314+
'schema': {
315+
'type': 'integer',
316+
},
317+
},
318+
]
319+
if self.page_size_query_param is not None:
320+
parameters.append(
321+
{
322+
'name': self.page_size_query_param,
323+
'required': False,
324+
'in': 'query',
325+
'description': force_text(self.page_size_query_description),
326+
'schema': {
327+
'type': 'integer',
328+
},
329+
},
330+
)
331+
return parameters
332+
304333

305334
class LimitOffsetPagination(BasePagination):
306335
"""
@@ -430,6 +459,15 @@ def to_html(self):
430459
context = self.get_html_context()
431460
return template.render(context)
432461

462+
def get_count(self, queryset):
463+
"""
464+
Determine an object count, supporting either querysets or regular lists.
465+
"""
466+
try:
467+
return queryset.count()
468+
except (AttributeError, TypeError):
469+
return len(queryset)
470+
433471
def get_schema_fields(self, view):
434472
assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
435473
assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`'
@@ -454,14 +492,28 @@ def get_schema_fields(self, view):
454492
)
455493
]
456494

457-
def get_count(self, queryset):
458-
"""
459-
Determine an object count, supporting either querysets or regular lists.
460-
"""
461-
try:
462-
return queryset.count()
463-
except (AttributeError, TypeError):
464-
return len(queryset)
495+
def get_schema_operation_parameters(self, view):
496+
parameters = [
497+
{
498+
'name': self.limit_query_param,
499+
'required': False,
500+
'in': 'query',
501+
'description': force_text(self.limit_query_description),
502+
'schema': {
503+
'type': 'integer',
504+
},
505+
},
506+
{
507+
'name': self.offset_query_param,
508+
'required': False,
509+
'in': 'query',
510+
'description': force_text(self.offset_query_description),
511+
'schema': {
512+
'type': 'integer',
513+
},
514+
},
515+
]
516+
return parameters
465517

466518

467519
class CursorPagination(BasePagination):
@@ -816,3 +868,29 @@ def get_schema_fields(self, view):
816868
)
817869
)
818870
return fields
871+
872+
def get_schema_operation_parameters(self, view):
873+
parameters = [
874+
{
875+
'name': self.cursor_query_param,
876+
'required': False,
877+
'in': 'query',
878+
'description': force_text(self.cursor_query_description),
879+
'schema': {
880+
'type': 'integer',
881+
},
882+
}
883+
]
884+
if self.page_size_query_param is not None:
885+
parameters.append(
886+
{
887+
'name': self.page_size_query_param,
888+
'required': False,
889+
'in': 'query',
890+
'description': force_text(self.page_size_query_description),
891+
'schema': {
892+
'type': 'integer',
893+
},
894+
}
895+
)
896+
return parameters

rest_framework/renderers.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,28 +1013,49 @@ def get_structure(self, data):
10131013
}
10141014

10151015

1016-
class OpenAPIRenderer(_BaseOpenAPIRenderer):
1016+
class CoreAPIOpenAPIRenderer(_BaseOpenAPIRenderer):
10171017
media_type = 'application/vnd.oai.openapi'
10181018
charset = None
10191019
format = 'openapi'
10201020

10211021
def __init__(self):
1022-
assert coreapi, 'Using OpenAPIRenderer, but `coreapi` is not installed.'
1023-
assert yaml, 'Using OpenAPIRenderer, but `pyyaml` is not installed.'
1022+
assert coreapi, 'Using CoreAPIOpenAPIRenderer, but `coreapi` is not installed.'
1023+
assert yaml, 'Using CoreAPIOpenAPIRenderer, but `pyyaml` is not installed.'
10241024

10251025
def render(self, data, media_type=None, renderer_context=None):
10261026
structure = self.get_structure(data)
10271027
return yaml.dump(structure, default_flow_style=False).encode()
10281028

10291029

1030-
class JSONOpenAPIRenderer(_BaseOpenAPIRenderer):
1030+
class CoreAPIJSONOpenAPIRenderer(_BaseOpenAPIRenderer):
10311031
media_type = 'application/vnd.oai.openapi+json'
10321032
charset = None
10331033
format = 'openapi-json'
10341034

10351035
def __init__(self):
1036-
assert coreapi, 'Using JSONOpenAPIRenderer, but `coreapi` is not installed.'
1036+
assert coreapi, 'Using CoreAPIJSONOpenAPIRenderer, but `coreapi` is not installed.'
10371037

10381038
def render(self, data, media_type=None, renderer_context=None):
10391039
structure = self.get_structure(data)
1040-
return json.dumps(structure, indent=4).encode()
1040+
return json.dumps(structure, indent=4).encode('utf-8')
1041+
1042+
1043+
class OpenAPIRenderer(BaseRenderer):
1044+
media_type = 'application/vnd.oai.openapi'
1045+
charset = None
1046+
format = 'openapi'
1047+
1048+
def __init__(self):
1049+
assert yaml, 'Using OpenAPIRenderer, but `pyyaml` is not installed.'
1050+
1051+
def render(self, data, media_type=None, renderer_context=None):
1052+
return yaml.dump(data, default_flow_style=False).encode('utf-8')
1053+
1054+
1055+
class JSONOpenAPIRenderer(BaseRenderer):
1056+
media_type = 'application/vnd.oai.openapi+json'
1057+
charset = None
1058+
format = 'openapi-json'
1059+
1060+
def render(self, data, media_type=None, renderer_context=None):
1061+
return json.dumps(data, indent=2).encode('utf-8')

rest_framework/schemas/__init__.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,32 @@
2222
"""
2323
from rest_framework.settings import api_settings
2424

25-
from .generators import SchemaGenerator
26-
from .inspectors import AutoSchema, DefaultSchema, ManualSchema # noqa
25+
from . import coreapi, openapi
26+
from .inspectors import DefaultSchema # noqa
27+
from .coreapi import AutoSchema, ManualSchema, SchemaGenerator # noqa
2728

2829

2930
def get_schema_view(
3031
title=None, url=None, description=None, urlconf=None, renderer_classes=None,
31-
public=False, patterns=None, generator_class=SchemaGenerator,
32+
public=False, patterns=None, generator_class=None,
3233
authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES,
3334
permission_classes=api_settings.DEFAULT_PERMISSION_CLASSES):
3435
"""
3536
Return a schema view.
3637
"""
37-
# Avoid import cycle on APIView
38-
from .views import SchemaView
38+
if generator_class is None:
39+
if coreapi.is_enabled():
40+
generator_class = coreapi.SchemaGenerator
41+
else:
42+
generator_class = openapi.SchemaGenerator
43+
3944
generator = generator_class(
4045
title=title, url=url, description=description,
4146
urlconf=urlconf, patterns=patterns,
4247
)
48+
49+
# Avoid import cycle on APIView
50+
from .views import SchemaView
4351
return SchemaView.as_view(
4452
renderer_classes=renderer_classes,
4553
schema_generator=generator,

0 commit comments

Comments
 (0)
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