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

1from copy import deepcopy 

2from .dataobj import DataSchemaException 

3from portality.lib.seamless import Construct 

4 

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 } 

32 

33 def __init__(self, swagger_trans=None, *args, **kwargs): 

34 # make a shortcut to the object.__getattribute__ function 

35 og = object.__getattribute__ 

36 

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) 

42 

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__() 

45 

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 

51 

52 

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 

59 

60 if schema_title: 

61 swag['title'] = schema_title 

62 

63 return swag 

64 

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. 

69 

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.") 

72 

73 swag_properties = {} 

74 

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"}) 

79 

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, {}) 

84 

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 

92 

93 # convert lists 

94 for l, instructions in iter(struct.get('lists', {}).items()): 

95 newpath = l if not path else path + '.' + l 

96 

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) 

106 

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'])) 

112 

113 return swag_properties