def test_generates_doc_for_handler(self): # generate_api_docs() yields HandlerDocumentation objects for the # handlers passed in. resource = self.make_resource() docs = list(generate_api_docs([resource])) self.assertEqual(1, len(docs)) [doc] = docs self.assertIsInstance(doc, HandlerDocumentation) self.assertIs(type(resource.handler), doc.handler)
def test_handler_without_resource_uri(self): # generate_api_docs() raises an exception if a handler does not have a # resource_uri attribute. resource = OperationsResource(BaseHandler) docs = generate_api_docs([resource]) error = self.assertRaises(AssertionError, list, docs) self.assertEqual( "Missing resource_uri in %s" % type(resource.handler).__name__, str(error))
def test_generates_doc_for_multiple_handlers(self): # generate_api_docs() yields HandlerDocumentation objects for the # handlers passed in. resources = [self.make_resource() for _ in range(5)] docs = list(generate_api_docs(resources)) sorted_handlers = sorted( [type(resource.handler) for resource in resources], key=lambda handler_class: handler_class.__name__) self.assertEqual(sorted_handlers, [doc.handler for doc in docs])
def test_handlers_have_section_title(self): from maasserver import urls_api as urlconf resources = find_api_resources(urlconf) handlers = [] for doc in generate_api_docs(resources): handlers.append(doc.handler) handlers_missing_section_name = [ handler.__name__ for handler in handlers if not hasattr(handler, 'api_doc_section_name') ] self.assertEqual( [], handlers_missing_section_name, "%d handlers are missing an api_doc_section_name field." % len(handlers_missing_section_name))
def render_api_docs(): """Render ReST documentation for the REST API. This module's docstring forms the head of the documentation; details of the API methods follow. :return: Documentation, in ReST, for the API. :rtype: :class:`unicode` """ from maasserver import urls_api as urlconf module = sys.modules[__name__] output = StringIO() line = partial(print, file=output) line(getdoc(module)) line() line() line('Operations') line('``````````') line() def export_key(export): """Return a sortable key for an export. `op` is often `None`, which cannot be compared to non-`None` operations. """ (http_method, op), function = export if op is None: return http_method, "", function else: return http_method, op, function resources = find_api_resources(urlconf) for doc in generate_api_docs(resources): uri_template = doc.resource_uri_template exports = doc.handler.exports.items() # Derive a section title from the name of the handler class. section_name = doc.handler.api_doc_section_name line(section_name) line('=' * len(section_name)) line(dedent(doc.handler.__doc__).strip()) line() line() for (http_method, op), function in sorted(exports, key=export_key): operation = " op=%s" % op if op is not None else "" subsection = "``%s %s%s``" % (http_method, uri_template, operation) line("%s\n%s\n" % (subsection, '#' * len(subsection))) line() docstring = getdoc(function) if docstring is not None: for docline in dedent(docstring).splitlines(): if docline.strip() == '': # Blank line. Don't indent. line() else: # Print documentation line, indented. line(docline) line() line() line() line(generate_power_types_doc()) line() line() line(generate_pod_types_doc()) return output.getvalue()
def render_api_docs(): """Render ReST documentation for the REST API. This module's docstring forms the head of the documentation; details of the API methods follow. :return: Documentation, in ReST, for the API. :rtype: :class:`unicode` """ from maasserver import urls_api as urlconf module = sys.modules[__name__] output = StringIO() line = partial(print, file=output) line(getdoc(module)) line() line() line("Operations") line("``````````") line() def export_key(export): """Return a sortable key for an export. `op` is often `None`, which cannot be compared to non-`None` operations. """ (http_method, op), function = export if op is None: return http_method, "", function else: return http_method, op, function annotation_parser = APIDocstringParser() templates = APITemplateRenderer() resources = find_api_resources(urlconf) for doc in generate_api_docs(resources): uri_template = doc.resource_uri_template exports = doc.handler.exports.items() # Derive a section title from the name of the handler class. section_name = doc.handler.api_doc_section_name line(section_name) line("=" * len(section_name)) # Note: # The following dedent is useless in the following situation: # # def somefunc(foo) # """No indent here # # Here, there is an indent, so dedent doesn't do # anything. # """ # # This fixes the problem: # # def somefunc(foo) # """ # Indent here # # Now dedent works because the entire docstring appears # to be indented. # """ # # This also works because the dedent version is the same # as the non-dented version: # # def somefunc(foo) # """No indent here""" # line(dedent(doc.handler.__doc__).strip()) line() line() for (http_method, op), function in sorted(exports, key=export_key): operation = " op=%s" % op if op is not None else "" subsection = "``%s %s%s``" % (http_method, uri_template, operation) docstring = getdoc(function) if docstring is not None: if APIDocstringParser.is_annotated_docstring(docstring): operation = "op=%s" % op if op is not None else "" annotation_parser.parse( docstring, http_method, uri_template, operation ) line( templates.apply_template( os.path.dirname(__file__) + "/tmpl-apidoc.rst", annotation_parser, ) ) else: line("%s\n%s\n" % (subsection, "#" * len(subsection))) line() for docline in dedent(docstring).splitlines(): if docline.strip() == "": # Blank line. Don't indent. line() else: # Print documentation line, indented. line(docline) line() else: line("%s\n%s\n" % (subsection, "#" * len(subsection))) line() line() line() line(generate_power_types_doc()) line() line() line(generate_pod_types_doc()) return output.getvalue()