doc.py 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. # -*- coding: utf-8 -*-
  2. import typing
  3. from apispec import APISpec
  4. from apispec import Path
  5. from apispec.ext.marshmallow.swagger import schema2jsonschema
  6. from hapic.context import ContextInterface
  7. from hapic.context import RouteRepresentation
  8. from hapic.decorator import DecoratedController
  9. from hapic.description import ControllerDescription
  10. def bottle_generate_operations(
  11. spec,
  12. route: RouteRepresentation,
  13. description: ControllerDescription,
  14. ):
  15. method_operations = dict()
  16. # TODO : Convert many=True in list of elems
  17. # schema based
  18. if description.input_body:
  19. schema_class = spec.schema_class_resolver(
  20. description.input_body.wrapper.processor.schema)
  21. method_operations.setdefault('parameters', []).append({
  22. 'in': 'body',
  23. 'name': 'body',
  24. 'schema': {
  25. '$ref': '#/definitions/{}'.format(
  26. spec.schema_name_resolver(schema_class))
  27. }
  28. })
  29. if description.output_body:
  30. schema_class = spec.schema_class_resolver(
  31. description.output_body.wrapper.processor.schema)
  32. method_operations.setdefault('responses', {})\
  33. [int(description.output_body.wrapper.default_http_code)] = {
  34. 'description': str(description.output_body.wrapper.default_http_code), # nopep8
  35. 'schema': {
  36. '$ref': '#/definitions/{}'.format(
  37. spec.schema_name_resolver(schema_class)
  38. )
  39. }
  40. }
  41. if description.output_file:
  42. method_operations.setdefault('produces', []).extend(
  43. description.output_file.wrapper.output_types
  44. )
  45. method_operations.setdefault('responses', {})\
  46. [int(description.output_file.wrapper.default_http_code)] = {
  47. 'description': str(description.output_file.wrapper.default_http_code), # nopep8
  48. }
  49. if description.errors:
  50. for error in description.errors:
  51. schema_class = spec.schema_class_resolver(error.wrapper.schema)
  52. method_operations.setdefault('responses', {})\
  53. [int(error.wrapper.http_code)] = {
  54. 'description': str(error.wrapper.http_code),
  55. 'schema': {
  56. '$ref': '#/definitions/{}'.format(
  57. spec.schema_name_resolver(schema_class)
  58. )
  59. }
  60. }
  61. # jsonschema based
  62. if description.input_path:
  63. schema_class = spec.schema_class_resolver(
  64. description.input_path.wrapper.processor.schema)
  65. # TODO: look schema2parameters ?
  66. jsonschema = schema2jsonschema(schema_class, spec=spec)
  67. for name, schema in jsonschema.get('properties', {}).items():
  68. method_operations.setdefault('parameters', []).append({
  69. 'in': 'path',
  70. 'name': name,
  71. 'required': name in jsonschema.get('required', []),
  72. 'type': schema['type']
  73. })
  74. if description.input_query:
  75. schema_class = spec.schema_class_resolver(
  76. description.input_query.wrapper.processor.schema)
  77. jsonschema = schema2jsonschema(schema_class, spec=spec)
  78. for name, schema in jsonschema.get('properties', {}).items():
  79. method_operations.setdefault('parameters', []).append({
  80. 'in': 'query',
  81. 'name': name,
  82. 'required': name in jsonschema.get('required', []),
  83. 'type': schema['type']
  84. })
  85. if description.input_files:
  86. method_operations.setdefault('consumes', []).append('multipart/form-data')
  87. for field_name, field in description.input_files.wrapper.processor.schema.fields.items():
  88. method_operations.setdefault('parameters', []).append({
  89. 'in': 'formData',
  90. 'name': field_name,
  91. 'required': field.required,
  92. 'type': 'file',
  93. })
  94. operations = {
  95. route.method.lower(): method_operations,
  96. }
  97. return operations
  98. class DocGenerator(object):
  99. def get_doc(
  100. self,
  101. controllers: typing.List[DecoratedController],
  102. context: ContextInterface,
  103. title: str='',
  104. description: str='',
  105. ) -> dict:
  106. spec = APISpec(
  107. title=title,
  108. info=dict(description=description),
  109. version='1.0.0',
  110. plugins=(
  111. 'apispec.ext.marshmallow',
  112. ),
  113. auto_referencing=True,
  114. )
  115. schemas = []
  116. # parse schemas
  117. for controller in controllers:
  118. description = controller.description
  119. if description.input_body:
  120. schemas.append(spec.schema_class_resolver(
  121. description.input_body.wrapper.processor.schema
  122. ))
  123. if description.input_forms:
  124. schemas.append(spec.schema_class_resolver(
  125. description.input_forms.wrapper.processor.schema
  126. ))
  127. if description.output_body:
  128. schemas.append(spec.schema_class_resolver(
  129. description.output_body.wrapper.processor.schema
  130. ))
  131. if description.errors:
  132. for error in description.errors:
  133. schemas.append(
  134. spec.schema_class_resolver(error.wrapper.schema))
  135. for schema in set(schemas):
  136. spec.definition(spec.schema_name_resolver(schema), schema=schema)
  137. # add views
  138. # with app.test_request_context():
  139. paths = {}
  140. for controller in controllers:
  141. route = context.find_route(controller)
  142. swagger_path = context.get_swagger_path(route.rule)
  143. operations = bottle_generate_operations(
  144. spec,
  145. route,
  146. controller.description,
  147. )
  148. # TODO BS 20171114: TMP code waiting refact of doc
  149. doc_string = controller.reference.get_doc_string()
  150. if doc_string:
  151. for method in operations.keys():
  152. operations[method]['description'] = doc_string
  153. path = Path(path=swagger_path, operations=operations)
  154. if swagger_path in paths:
  155. paths[swagger_path].update(path)
  156. else:
  157. paths[swagger_path] = path
  158. spec.add_path(path)
  159. return spec.to_dict()