Generating Public and Private API Documentation in Sphinx
Matthew Louis / August 2024 (1445 Words, 9 Minutes)
Motivation
In my last blog post, I talked about my experience learning Sphinx. One thing I encountered is that I wanted the users to not see any of the private methods/classes/modules in the documentation (becuase they’re not part of the public-facing API and they’re subject to change), but I also wanted it to be available in nice html format for developers if they were interested. I wanted to essentially include a separate private and public API in the same documentation page. Doing this with autodoc is difficult, because there’s no way to explicitly exclude public members (private members are excluded by default, and can easily be un-excluded by specifying :private-members:
in the automodule directive in the generated .rst
files). Another thing is that, if you want to include private members (along with public members), you can specify that in the autodoc_default_options
in the conf.py
, but that configuration is used for generating all of the documentation, so it wasn’t immediately clear how I could change this for generating a separate public and private API.
A Solution
One way I came up with to solve this was to manually edit the templates used by autodoc to create the .rst
documentation, and so I created a separate template for the public and private documentation in the _templates
directory. The main difference being in this section of the package.rst_t
:
_templates/private/package.rst_t
_templates/public/package.rst_t
In these templates, you can see that there’s a custom description (a warning in the case of the private API documentation), a custom reference (either .. _private_api
or .. _public_api
), and a custom directive .. api_type:: private
or .. api_type:: public
. When autodoc processes these templates, these custom directives are read and influence how the documentation is generated. This directive is implemented in the conf.py
setup(app)
function
the api_type
directive is implemented via
and a series of custom functions for skipping members based on teh specified api_type
and to generate the documentation, a custom build script must also be specified. In the Makefile:
and in this case some added dependencies that allow for customizing the names of the generated .rst
files via a python script reanme.py
which is implemented in the makefile via
A note on Filtering Warnings
This approach requires generating documentation using autodoc twice, and it may be the case that the same module has both private and public functions, so you’ll get an error saying 'duplicate object description of public/module1, other instance in private/index.rst, use :no-index: for one of them'
. This is because the generated .rst
documentation literally references the same module in both the private and public_api index.rst
, but what Sphinx doesn’t know in this case is that the set of members for which documentation is generated in the different documentations for that module are mutually exclusive (i.e. the public/index.rst
only generates documentation for the public members, and the private/index.rst
only for the private members). In most cases, a duplicate object description will lead to a broken toctree, but in this case it’s actually okay because of this mutual exclusivity. To suppress these annoying warnings when generating the documentation, we have to add a custom filter to the Sphinx logger (this is done in the setup(app)
function of the conf.py
as shown before) defined by
Conclusions and Critiques
I’d say the end result (shown in the documentation here) is what I wanted, but I’ve realized that it’s kind of inconvenient (as a developer) to only see the private methods/functions/classes/etc. and it’s a lot nicer to just see everything. But of course, you can’t do this in a single Sphinx documentation page, because otherwise each public function would have references to both the public API and the developer API. So I guess the best approach for having a unified developer API (with both public and private members) is to create two separate API’s, which is often what’s done anyways. Although, if the inconvenience is no bother as a developer, and it’s more convenient to just build and deploy one set of documentation, then this approach could still be useful.
Comments
If you're logged in, you may write a comment here. (markdown formatting is supported)
No comments yet.