Coverage for portality/lib/swagger.py: 95%
56 statements
« prev ^ index » next coverage.py v6.4.2, created at 2022-07-22 15:59 +0100
« prev ^ index » next coverage.py v6.4.2, created at 2022-07-22 15:59 +0100
1from copy import deepcopy
2from .dataobj import DataSchemaException
3from portality.lib.seamless import Construct
5class SwaggerSupport(object):
6 # Translation between our simple field types and swagger spec types.
7 # For all the ones that have a note to add support to the swagger-ui front-end,
8 # for now those fields will just display whatever is in the "type"
9 # section when viewing the interactive documentation. "type" must be
10 # a valid Swagger type ( https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#data-types )
11 # and "format" will just be ignored if it is not a default format in
12 # the Swagger spec *and* a format that the swagger-ui Javascript library understands.
13 # The spec says it's possible to define your own formats - we'll see.
14 DEFAULT_SWAGGER_TRANS = {
15 # The default translation from our coerce to swagger is {"type": "string"}
16 # if there is no matching entry in the trans dict here.
17 "unicode": {"type": "string"},
18 "utcdatetime": {"type": "string", "format": "date-time"},
19 "integer": {"type": "integer"},
20 "bool": {"type": "boolean"},
21 "float": {"type": "float"},
22 "isolang": {"type": "string", "format": "isolang"}, # TODO extend swagger-ui with isolang format support and let it produce example values etc. on the front-end
23 "url": {"type": "string", "format": "url"}, # TODO add suppport to swagger-ui doc frontend for URL or grab from somewhere we can't be the first!
24 "isolang_2letter": {"type": "string", "format": "isolang-alpha2"}, # TODO add support to swagger-ui front for this
25 "country_code": {"type": "string", "format": "country_code"}, # TODO add support to swagger-ui front for this
26 "currency_code": {"type": "string", "format": "currency_code"}, # TODO add support to swagger-ui front for this
27 "license": {"type": "string", "format": "license_type"}, # TODO add support to swagger-ui front for this. Ideal if we could display the list of allowed values from the coerce map.
28 "persistent_identifier_scheme": {"type": "string", "format": "persistent_identifier_scheme"}, # TODO add support to swagger-ui front for this. Ideal if we could display the list of allowed values from the coerce map.
29 "format": {"type": "string", "format": "format"}, # TODO add support to swagger-ui front for this. Ideal if we could display the list of allowed values from the coerce map.
30 "deposit_policy": {"type": "string", "format": "deposit_policy"}, # TODO add support to swagger-ui front for this. Ideal if we could display the list of allowed values from the coerce map.
31 }
33 def __init__(self, swagger_trans=None, *args, **kwargs):
34 # make a shortcut to the object.__getattribute__ function
35 og = object.__getattribute__
37 # if no subclass has set the swagger translation dict, then set it from default
38 try:
39 og(self, "_swagger_trans")
40 except:
41 self._swagger_trans = swagger_trans if swagger_trans is not None else deepcopy(self.DEFAULT_SWAGGER_TRANS)
43 # super(SwaggerSupport, self).__init__(*args, **kwargs) # UT fails with args and kwargs - takes only one argument, object to initialize
44 super(SwaggerSupport, self).__init__()
46 def struct_to_swag(self, struct=None, schema_title='', **kwargs):
47 if not struct:
48 if not self._struct:
49 raise DataSchemaException("No struct to translate to Swagger.")
50 struct = self._struct
53 swag = {
54 "properties": self.__struct_to_swag_properties(struct=struct, **kwargs)
55 }
56 required = deepcopy(struct.get('required', []))
57 if len(required) > 0:
58 swag["required"] = required
60 if schema_title:
61 swag['title'] = schema_title
63 return swag
65 def __struct_to_swag_properties(self, struct=None, path=''):
66 '''A recursive function to translate the current DataObject's struct to Swagger Spec.'''
67 # If no struct is specified this is the first call, so set the
68 # operating struct to the entire current DO struct.
70 if not (isinstance(struct, dict) or isinstance(struct, Construct)):
71 raise DataSchemaException("The struct whose properties we're translating to Swagger should always be a dict-like object.")
73 swag_properties = {}
75 # convert simple fields
76 for simple_field, instructions in iter(struct.get('fields', {}).items()):
77 # no point adding to the path here, it's not gonna recurse any further from this field
78 swag_properties[simple_field] = self._swagger_trans.get(instructions['coerce'], {"type": "string"})
80 # convert objects
81 for obj in struct.get('objects', []):
82 newpath = obj if not path else path + '.' + obj
83 instructions = struct.get('structs', {}).get(obj, {})
85 swag_properties[obj] = {}
86 swag_properties[obj]['title'] = newpath
87 swag_properties[obj]['type'] = 'object'
88 swag_properties[obj]['properties'] = self.__struct_to_swag_properties(struct=instructions, path=newpath) # recursive call, process sub-struct(s)
89 required = deepcopy(instructions.get('required', []))
90 if len(required) > 0:
91 swag_properties[obj]['required'] = required
93 # convert lists
94 for l, instructions in iter(struct.get('lists', {}).items()):
95 newpath = l if not path else path + '.' + l
97 swag_properties[l] = {}
98 swag_properties[l]['type'] = 'array'
99 swag_properties[l]['items'] = {}
100 if instructions['contains'] == 'field':
101 swag_properties[l]['items'] = self._swagger_trans.get(instructions['coerce'], {"type": "string"})
102 elif instructions['contains'] == 'object':
103 swag_properties[l]['items']['type'] = 'object'
104 swag_properties[l]['items']['title'] = newpath
105 swag_properties[l]['items']['properties'] = self.__struct_to_swag_properties(struct=struct.get('structs', {}).get(l, {}), path=newpath) # recursive call, process sub-struct(s)
107 required = deepcopy(struct.get('structs', {}).get(l, {}).get('required', []))
108 if len(required) > 0:
109 swag_properties[l]['items']['required'] = required
110 else:
111 raise DataSchemaException("Instructions for list {x} unclear. Conversion to Swagger Spec only supports lists containing \"field\" and \"object\" items. Found: {y}".format(x=newpath, y=instructions['contains']))
113 return swag_properties