sharedexample.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. # Copyright 2015 ksyun.com, Inc. or its affiliates. All Rights Reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License"). You
  4. # may not use this file except in compliance with the License. A copy of
  5. # the License is located at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # or in the "license" file accompanying this file. This file is
  10. # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
  11. # ANY KIND, either express or implied. See the License for the specific
  12. # language governing permissions and limitations under the License.
  13. import re
  14. import numbers
  15. from kscore.utils import parse_timestamp
  16. from datetime import datetime
  17. class SharedExampleDocumenter(object):
  18. def document_shared_example(self, example, prefix, section,
  19. operation_model):
  20. """Documents a single shared example based on its definition.
  21. :param example: The model of the example
  22. :param prefix: The prefix to use in the method example.
  23. :param section: The section to write to.
  24. :param operation_model: The model of the operation used in the example
  25. """
  26. section.style.new_paragraph()
  27. section.write(example.get('description'))
  28. section.style.new_line()
  29. self.document_input(section, example, prefix,
  30. operation_model.input_shape)
  31. self.document_output(section, example, operation_model.output_shape)
  32. def document_input(self, section, example, prefix, shape):
  33. input_section = section.add_new_section('input')
  34. input_section.style.start_codeblock()
  35. if prefix is not None:
  36. input_section.write(prefix)
  37. params = example.get('input', {})
  38. comments = example.get('comments')
  39. if comments:
  40. comments = comments.get('input')
  41. param_section = input_section.add_new_section('parameters')
  42. self._document_params(param_section, params, comments, [], shape)
  43. closing_section = input_section.add_new_section('input-close')
  44. closing_section.style.new_line()
  45. closing_section.style.new_line()
  46. closing_section.write('print(response)')
  47. closing_section.style.end_codeblock()
  48. def document_output(self, section, example, shape):
  49. output_section = section.add_new_section('output')
  50. output_section.style.new_line()
  51. output_section.write('Expected Output:')
  52. output_section.style.new_line()
  53. output_section.style.start_codeblock()
  54. params = example.get('output', {})
  55. # There might not be an output, but we will return metadata anyway
  56. params['ResponseMetadata'] = {"...": "..."}
  57. comments = example.get('comments')
  58. if comments:
  59. comments = comments.get('output')
  60. self._document_dict(output_section, params, comments, [], shape, True)
  61. closing_section = output_section.add_new_section('output-close')
  62. closing_section.style.end_codeblock()
  63. def _document(self, section, value, comments, path, shape):
  64. """
  65. :param section: The section to add the docs to.
  66. :param value: The input / output values representing the parameters that
  67. are included in the example.
  68. :param comments: The dictionary containing all the comments to be
  69. applied to the example.
  70. :param path: A list describing where the documenter is in traversing the
  71. parameters. This is used to find the equivalent location
  72. in the comments dictionary.
  73. """
  74. if isinstance(value, dict):
  75. self._document_dict(section, value, comments, path, shape)
  76. elif isinstance(value, list):
  77. self._document_list(section, value, comments, path, shape)
  78. elif isinstance(value, numbers.Number):
  79. self._document_number(section, value, path)
  80. elif shape and shape.type_name == 'timestamp':
  81. self._document_datetime(section, value, path)
  82. else:
  83. self._document_str(section, value, path)
  84. def _document_dict(self, section, value, comments, path, shape,
  85. top_level=False):
  86. dict_section = section.add_new_section('dict-value')
  87. self._start_nested_value(dict_section, '{')
  88. for key, val in value.items():
  89. path.append('.%s' % key)
  90. item_section = dict_section.add_new_section(key)
  91. item_section.style.new_line()
  92. item_comment = self._get_comment(path, comments)
  93. if item_comment:
  94. item_section.write(item_comment)
  95. item_section.style.new_line()
  96. item_section.write("'%s': " % key)
  97. # Shape could be none if there is no output besides ResponseMetadata
  98. item_shape = None
  99. if shape:
  100. if shape.type_name == 'structure':
  101. item_shape = shape.members.get(key)
  102. elif shape.type_name == 'map':
  103. item_shape = shape.value
  104. self._document(item_section, val, comments, path, item_shape)
  105. path.pop()
  106. dict_section_end = dict_section.add_new_section('ending-brace')
  107. self._end_nested_value(dict_section_end, '}')
  108. if not top_level:
  109. dict_section_end.write(',')
  110. def _document_params(self, section, value, comments, path, shape):
  111. param_section = section.add_new_section('param-values')
  112. self._start_nested_value(param_section, '(')
  113. for key, val in value.items():
  114. path.append('.%s' % key)
  115. item_section = param_section.add_new_section(key)
  116. item_section.style.new_line()
  117. item_comment = self._get_comment(path, comments)
  118. if item_comment:
  119. item_section.write(item_comment)
  120. item_section.style.new_line()
  121. item_section.write(key + '=')
  122. # Shape could be none if there are no input parameters
  123. item_shape = None
  124. if shape:
  125. item_shape = shape.members.get(key)
  126. self._document(item_section, val, comments, path, item_shape)
  127. path.pop()
  128. param_section_end = param_section.add_new_section('ending-parenthesis')
  129. self._end_nested_value(param_section_end, ')')
  130. def _document_list(self, section, value, comments, path, shape):
  131. list_section = section.add_new_section('list-section')
  132. self._start_nested_value(list_section, '[')
  133. item_shape = shape.member
  134. for index, val in enumerate(value):
  135. item_section = list_section.add_new_section(index)
  136. item_section.style.new_line()
  137. path.append('[%s]' % index)
  138. item_comment = self._get_comment(path, comments)
  139. if item_comment:
  140. item_section.write(item_comment)
  141. item_section.style.new_line()
  142. self._document(item_section, val, comments, path, item_shape)
  143. path.pop()
  144. list_section_end = list_section.add_new_section('ending-bracket')
  145. self._end_nested_value(list_section_end, '],')
  146. def _document_str(self, section, value, path):
  147. # We do the string conversion because this might accept a type that
  148. # we don't specifically address.
  149. section.write("'%s'," % str(value))
  150. def _document_number(self, section, value, path):
  151. section.write("%s," % str(value))
  152. def _document_datetime(self, section, value, path):
  153. datetime_tuple = parse_timestamp(value).timetuple()
  154. datetime_str = str(datetime_tuple[0])
  155. for i in range(1, len(datetime_tuple)):
  156. datetime_str += ", " + str(datetime_tuple[i])
  157. section.write("datetime(%s)," % datetime_str)
  158. def _get_comment(self, path, comments):
  159. key = re.sub('^\.', '', ''.join(path))
  160. if comments and key in comments:
  161. return '# ' + comments[key]
  162. else:
  163. return ''
  164. def _start_nested_value(self, section, start):
  165. section.write(start)
  166. section.style.indent()
  167. section.style.indent()
  168. def _end_nested_value(self, section, end):
  169. section.style.dedent()
  170. section.style.dedent()
  171. section.style.new_line()
  172. section.write(end)
  173. def document_shared_examples(section, operation_model, example_prefix,
  174. shared_examples):
  175. """Documents the shared examples
  176. :param section: The section to write to.
  177. :param operation_model: The model of the operation.
  178. :param example_prefix: The prefix to use in the method example.
  179. :param shared_examples: The shared JSON examples from the model.
  180. """
  181. container_section = section.add_new_section('shared-examples')
  182. container_section.style.new_paragraph()
  183. container_section.style.bold('Examples')
  184. documenter = SharedExampleDocumenter()
  185. for example in shared_examples:
  186. documenter.document_shared_example(
  187. example=example,
  188. section=container_section.add_new_section(example['id']),
  189. prefix=example_prefix,
  190. operation_model=operation_model
  191. )