From ad3a0fcf448471c5115ec7de8e0ce90da1965f55 Mon Sep 17 00:00:00 2001 From: Brian Curtin Date: Mon, 6 Feb 2017 11:35:13 -0500 Subject: [PATCH] Enforce inclusion of pulic proxy methods in docs Per the approach on https://review.openstack.org/#/c/428276/, and in general even outside of that change, we should have more strict enforcement on the inclusion of our public proxy APIs in the documentation. This tool runs as a part of our doc build, via `tox -e docs`, and collects all of the public proxy methods and then compares that list to the list of methods that were produced by Sphinx. For right now this will only output warnings, but we should eventually turn on enforcer_warnings_as_errors in doc/source/conf.py so that we can truly enforce these things. If you don't document something, it'll kill the doc build and then kill the docs gate job, so undocumented code won't be accepted. We'll need to leave it off for the time being as we transition into it. Change-Id: I96743de7e0790da98d758415e084a26a92aa3c70 --- doc/source/conf.py | 5 ++ doc/source/enforcer.py | 113 +++++++++++++++++++++++++++++++++++++++++ test-requirements.txt | 1 + 3 files changed, 119 insertions(+) create mode 100644 doc/source/enforcer.py diff --git a/doc/source/conf.py b/doc/source/conf.py index 05443eb3..f31d3b96 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -18,6 +18,7 @@ import sys import openstackdocstheme sys.path.insert(0, os.path.abspath('../..')) +sys.path.insert(0, os.path.abspath('.')) # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be @@ -25,8 +26,12 @@ sys.path.insert(0, os.path.abspath('../..')) extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', + 'enforcer' ] +# When True, this will raise an exception that kills sphinx-build. +enforcer_warnings_as_errors = False + # autodoc generation is a bit aggressive and a nuisance when doing heavy # text edit cycles. # execute "export SPHINX_DEBUG=1" in your terminal to disable diff --git a/doc/source/enforcer.py b/doc/source/enforcer.py new file mode 100644 index 00000000..f75f9a30 --- /dev/null +++ b/doc/source/enforcer.py @@ -0,0 +1,113 @@ +import importlib +import os + +from bs4 import BeautifulSoup +from sphinx import errors + +# NOTE: We do this because I can't find any way to pass "-v" +# into sphinx-build through pbr... +DEBUG = True if os.getenv("ENFORCER_DEBUG") else False + +WRITTEN_METHODS = set() + + +class EnforcementError(errors.SphinxError): + """A mismatch between what exists and what's documented""" + category = "Enforcer" + + +def get_proxy_methods(): + """Return a set of public names on all proxies""" + names = ["openstack.bare_metal.v1._proxy", + "openstack.block_store.v2._proxy", + "openstack.cluster.v1._proxy", + "openstack.compute.v2._proxy", + "openstack.database.v1._proxy", + "openstack.identity.v2._proxy", + "openstack.identity.v3._proxy", + "openstack.image.v1._proxy", + "openstack.image.v2._proxy", + "openstack.key_manager.v1._proxy", + "openstack.message.v1._proxy", + "openstack.message.v2._proxy", + "openstack.metric.v1._proxy", + "openstack.network.v2._proxy", + "openstack.object_store.v1._proxy", + "openstack.orchestration.v1._proxy", + "openstack.telemetry.v2._proxy", + "openstack.telemetry.alarm.v2._proxy", + "openstack.workflow.v2._proxy"] + + modules = (importlib.import_module(name) for name in names) + + methods = set() + for module in modules: + # We're not going to use the Proxy for anything other than a `dir` + # so just pass a dummy value so we can create the instance. + instance = module.Proxy("") + # We only document public names + names = [name for name in dir(instance) if not name.startswith("_")] + good_names = [module.__name__ + ".Proxy." + name for name in names] + methods.update(good_names) + + return methods + + +def page_context(app, pagename, templatename, context, doctree): + """Handle html-page-context-event + + This event is emitted once the builder has the contents to create + an HTML page, but before the template is rendered. This is the point + where we'll know what documentation is going to be written, so + gather all of the method names that are about to be included + so we can check which ones were or were not processed earlier + by autodoc. + """ + if "users/proxies" in pagename: + soup = BeautifulSoup(context["body"], "html.parser") + dts = soup.find_all("dt") + ids = [dt.get("id") for dt in dts] + + written = 0 + for id in ids: + if id is not None and "_proxy.Proxy" in id: + WRITTEN_METHODS.add(id) + written += 1 + + if DEBUG: + app.info("ENFORCER: Wrote %d proxy methods for %s" % ( + written, pagename)) + + +def build_finished(app, exception): + """Handle build-finished event + + This event is emitted once the builder has written all of the output. + At this point we just compare what we know was written to what we know + exists within the modules and share the results. + + When enforcer_warnings_as_errors=True in conf.py, this method + will raise EnforcementError on any failures in order to signal failure. + """ + all_methods = get_proxy_methods() + + app.info("ENFORCER: %d proxy methods exist" % len(all_methods)) + app.info("ENFORCER: %d proxy methods written" % len(WRITTEN_METHODS)) + missing = all_methods - WRITTEN_METHODS + missing_count = len(missing) + app.info("ENFORCER: Found %d missing proxy methods " + "in the output" % missing_count) + + for name in sorted(missing): + app.warn("ENFORCER: %s was not included in the output" % name) + + if app.config.enforcer_warnings_as_errors: + raise EnforcementError( + "There are %d undocumented proxy methods" % missing_count) + + +def setup(app): + app.add_config_value("enforcer_warnings_as_errors", False, "env") + + app.connect("html-page-context", page_context) + app.connect("build-finished", build_finished) diff --git a/test-requirements.txt b/test-requirements.txt index 7557c662..b6f7509d 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -3,6 +3,7 @@ # process, which may cause wedges in the gate later. hacking<0.11,>=0.10.0 +beautifulsoup4 # MIT coverage>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD mock>=2.0 # BSD