|
24 | 24 |
|
25 | 25 | from rest_framework import VERSION, exceptions, serializers, status
|
26 | 26 | from rest_framework.compat import (
|
27 |
| - INDENT_SEPARATORS, LONG_SEPARATORS, SHORT_SEPARATORS, coreapi, |
28 |
| - pygments_css |
| 27 | + INDENT_SEPARATORS, LONG_SEPARATORS, SHORT_SEPARATORS, coreapi, coreschema, |
| 28 | + pygments_css, urlparse, yaml |
29 | 29 | )
|
30 | 30 | from rest_framework.exceptions import ParseError
|
31 | 31 | from rest_framework.request import is_form_media_type, override_method
|
@@ -932,3 +932,119 @@ def render(self, data, media_type=None, renderer_context=None):
|
932 | 932 | indent = bool(renderer_context.get('indent', 0))
|
933 | 933 | codec = coreapi.codecs.CoreJSONCodec()
|
934 | 934 | return codec.dump(data, indent=indent)
|
| 935 | + |
| 936 | + |
| 937 | +class _BaseOpenAPIRenderer: |
| 938 | + def get_schema(self, instance): |
| 939 | + CLASS_TO_TYPENAME = { |
| 940 | + coreschema.Object: 'object', |
| 941 | + coreschema.Array: 'array', |
| 942 | + coreschema.Number: 'number', |
| 943 | + coreschema.Integer: 'integer', |
| 944 | + coreschema.String: 'string', |
| 945 | + coreschema.Boolean: 'boolean', |
| 946 | + } |
| 947 | + |
| 948 | + schema = {} |
| 949 | + if instance.__class__ in CLASS_TO_TYPENAME: |
| 950 | + schema['type'] = CLASS_TO_TYPENAME[instance.__class__] |
| 951 | + schema['title'] = instance.title, |
| 952 | + schema['description'] = instance.description |
| 953 | + if hasattr(instance, 'enum'): |
| 954 | + schema['enum'] = instance.enum |
| 955 | + return schema |
| 956 | + |
| 957 | + def get_parameters(self, link): |
| 958 | + parameters = [] |
| 959 | + for field in link.fields: |
| 960 | + if field.location not in ['path', 'query']: |
| 961 | + continue |
| 962 | + parameter = { |
| 963 | + 'name': field.name, |
| 964 | + 'in': field.location, |
| 965 | + } |
| 966 | + if field.required: |
| 967 | + parameter['required'] = True |
| 968 | + if field.description: |
| 969 | + parameter['description'] = field.description |
| 970 | + if field.schema: |
| 971 | + parameter['schema'] = self.get_schema(field.schema) |
| 972 | + parameters.append(parameter) |
| 973 | + return parameters |
| 974 | + |
| 975 | + def get_operation(self, link, name, tag): |
| 976 | + operation_id = "%s_%s" % (tag, name) if tag else name |
| 977 | + parameters = self.get_parameters(link) |
| 978 | + |
| 979 | + operation = { |
| 980 | + 'operationId': operation_id, |
| 981 | + } |
| 982 | + if link.title: |
| 983 | + operation['summary'] = link.title |
| 984 | + if link.description: |
| 985 | + operation['description'] = link.description |
| 986 | + if parameters: |
| 987 | + operation['parameters'] = parameters |
| 988 | + if tag: |
| 989 | + operation['tags'] = [tag] |
| 990 | + return operation |
| 991 | + |
| 992 | + def get_paths(self, document): |
| 993 | + paths = {} |
| 994 | + |
| 995 | + tag = None |
| 996 | + for name, link in document.links.items(): |
| 997 | + path = urlparse.urlparse(link.url).path |
| 998 | + method = link.action.lower() |
| 999 | + paths.setdefault(path, {}) |
| 1000 | + paths[path][method] = self.get_operation(link, name, tag=tag) |
| 1001 | + |
| 1002 | + for tag, section in document.data.items(): |
| 1003 | + for name, link in section.links.items(): |
| 1004 | + path = urlparse.urlparse(link.url).path |
| 1005 | + method = link.action.lower() |
| 1006 | + paths.setdefault(path, {}) |
| 1007 | + paths[path][method] = self.get_operation(link, name, tag=tag) |
| 1008 | + |
| 1009 | + return paths |
| 1010 | + |
| 1011 | + def get_structure(self, data): |
| 1012 | + return { |
| 1013 | + 'openapi': '3.0.0', |
| 1014 | + 'info': { |
| 1015 | + 'version': '', |
| 1016 | + 'title': data.title, |
| 1017 | + 'description': data.description |
| 1018 | + }, |
| 1019 | + 'servers': [{ |
| 1020 | + 'url': data.url |
| 1021 | + }], |
| 1022 | + 'paths': self.get_paths(data) |
| 1023 | + } |
| 1024 | + |
| 1025 | + |
| 1026 | +class OpenAPIRenderer(_BaseOpenAPIRenderer): |
| 1027 | + media_type = 'application/vnd.oai.openapi' |
| 1028 | + charset = None |
| 1029 | + format = 'openapi' |
| 1030 | + |
| 1031 | + def __init__(self): |
| 1032 | + assert coreapi, 'Using OpenAPIRenderer, but `coreapi` is not installed.' |
| 1033 | + assert yaml, 'Using OpenAPIRenderer, but `pyyaml` is not installed.' |
| 1034 | + |
| 1035 | + def render(self, data, media_type=None, renderer_context=None): |
| 1036 | + structure = self.get_structure(data) |
| 1037 | + return yaml.dump(structure, default_flow_style=False).encode('utf-8') |
| 1038 | + |
| 1039 | + |
| 1040 | +class JSONOpenAPIRenderer(_BaseOpenAPIRenderer): |
| 1041 | + media_type = 'application/vnd.oai.openapi+json' |
| 1042 | + charset = None |
| 1043 | + format = 'openapi-json' |
| 1044 | + |
| 1045 | + def __init__(self): |
| 1046 | + assert coreapi, 'Using JSONOpenAPIRenderer, but `coreapi` is not installed.' |
| 1047 | + |
| 1048 | + def render(self, data, media_type=None, renderer_context=None): |
| 1049 | + structure = self.get_structure(data) |
| 1050 | + return json.dumps(structure, indent=4).encode('utf-8') |
0 commit comments