Documenting Python With Markdown

Rob Blackbourn
2 min readFeb 2, 2020

I have a love hate relationship with the sphinx document generator. It is a hugely powerful set of tools, but I find the learning curve frustratingly steep.

What I really want to do is use markdown. The mkdocs project uses markdown as it’s document syntax, so I’m ready to go! but wait; how can I automatically generate the documentation from docstrings in my code?

After a little googling I discovered an excellent package called mkautodoc which is used by the encode group. This was close to what I was looking for, but it uses a custom docstring format, and I have a lot of code that’s already documented. So I wrote my own which can be found here.

For the impatient, the package can be installed from pypi.org:

$ pip install jetblack-serialization

The mkdocs.yaml file should have the following extensions:

markdown_extensions:
- admonition
- codehilite
- jetblack_markdown.autodoc

And the final markdown looks like this:

# ModuleThe following documents the entire module@[jetblack_markdown.metadata]The following documents a single class@[jetblack_markdown.metadata:ClassDescriptor]

Go here to see how it looks, and for further documentation.

Under the hood.

The extension works in two phases. First it gathers the type information and docstrings for the requested module, class, function, etc., and creates a descriptor object. It then renders a jinja2 template, passing in the descriptor.

This two phase approach makes the rendering completely customisable. One of the optional parameters that can be passed to the extension is template_folder in which the file main.jinja2 will be used to render the descriptor.

For example the following (rather hideous) bit of formatting renders properties. I’ve tried to bold the key components.

<code><var class="autodoc-varname">{{ property.name }}</var><span class="autodoc-punctuation"> -> </span><span class="autodoc-vartype">{{ property.type }}</span></code>
{%- if property.is_settable %}
<br /><code><var class="autodoc-varname">{{ property.name }}</var><span class="autodoc-punctuation">: </span><span class="autodoc-vartype">{{ property.type }}</span><span class="autodoc-punctuation"> = ...</span></code>
{%- endif -%}
{%- if property.is_deletable %}
<br /><code><span class="autodoc-keyword">del</span><span class="autodoc-punctuation"> </span><var class="autodoc-varname">{{ property.name }}</var></code>
{%- endif -%}

Which comes out like this:

name -> str
name: str = ...
del name

I’m not really sure if I like that yet, but it’s what I’ve come up with so far. However if you don’t like it, just change it!

Docstrings

I’m using a docstring parser project from here. A couple of formats are supported, but the one I’ve settled on is the google docs style.

This looks like this:

def makeExtension(*args, **kwargs) -> Extension:
"""Make the extension
This hook *function* gets picked up by the markdown processor
when the extension is listed
```python
output = markdown.markdown(
content, extensions=[
"admonition",
"codehilite",
"jetblack_markdown.autodoc",
])
print(output)
```
Returns:
Extension: The extension
"""
return AutodocExtension(*args, **kwargs)

Now you can see the markdown! I’ve used a * around function to italicise, and added a code block.

I have a feeling I’m going to enjoy documentation a little more in the future.

--

--