def test_annotations_present(self): """Tests to ensure annotations-present is functioning.""" docstring_no_annotations = self.sample_api_docstring self.assertFalse(APIDocstringParser.is_annotated_docstring( docstring_no_annotations)) docstring_annotations = self.sample_api_annotated_docstring self.assertTrue(APIDocstringParser.is_annotated_docstring( docstring_annotations))
def describe_actions(handler): """Describe the actions that `handler` supports. For each action, which could be a CRUD operation or a custom (piggybacked) operation, a dict with the following entries is generated: method: string, HTTP method. name: string, a human-friendly name for the action. doc: string, documentation about the action. op: string or None, the op parameter to pass in requests for non-CRUD/ReSTful requests. restful: Indicates if this is a CRUD/ReSTful action. """ from maasserver.api import support # Circular import. getname = support.OperationsResource.crudmap.get for signature, function in handler.exports.items(): http_method, operation = signature name = getname(http_method) if operation is None else operation ap = APIDocstringParser() doc = getdoc(function) if doc is not None: if APIDocstringParser.is_annotated_docstring(doc): # Because the docstring contains annotations, we # need to construct a string suitable for output # to stdout that matches the style used for # non-annotated docstrings in the CLI. ap.parse(doc) d = ap.get_dict() if d['description_title'] != "": doc = d['description_title'] + "\n\n" doc += d['description'] + "\n\n" else: doc = d['description'] + "\n\n" # Here, we add the params, but we skip params # surrounded by curly brackets (e.g. {foo}) # because these indicate params that appear in # the URI (e.g. /zone/{name}/). I.e. positional # arguments. These already appear in the CLI # help command output so we don't want duplicates. for p in d['params']: if p['name'].find("{") == -1 and p['name'].find("}") == -1: required = "Required. " if p['options']['required'] == "false": required = "Optional. " doc += (":param %s: %s%s" % (p['name'], required, p['description'])) doc += (":type %s: %s\n " % (p['name'], p['type'])) yield dict( method=http_method, name=name, doc=doc, op=operation, restful=(operation is None))
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()