Doc generators » Python docs

A mod­ern, mo­bile-friend­ly Sphinx-alike Python doc­u­men­ta­tion gen­er­a­tor with a first-class search func­tion­al­i­ty. Gen­er­at­ed by in­spect­ing Python mod­ules and us­ing ei­ther em­bed­ded doc­strings or ex­ter­nal reST files to pop­u­late the doc­u­men­ta­tion.

One of the de­sign goals is pro­vid­ing a sim­i­lar us­er ex­pe­ri­ence to the Doxy­gen doc­u­men­ta­tion theme.

Ba­sic us­age

The base is con­tained in a sin­gle Python script and re­lat­ed style/tem­plate files, for ad­vanced fea­tures such as math ren­der­ing it’ll make use of in­ter­nals of some m.css plug­ins. Clone the m.css GitHub re­pos­i­to­ry and look in­to the documentation/ di­rec­to­ry:

git clone git://
cd m.css/documentation

The script re­quires Python 3.6 and de­pends on Jin­ja2 for tem­plat­ing and do­cu­tils for reST markup ren­der­ing. You can in­stall the de­pen­den­cies via pip or your dis­tri­bu­tion pack­age man­ag­er, in most cas­es you’ll prob­a­bly have them al­ready in­stalled:

# You may need sudo here
pip3 install docutils jinja2

Next, you need a con­fig­u­ra­tion file which tells the script what mod­ules to in­spect, how to name the project and where to put the out­put. In this ex­am­ple, we’ll gen­er­ate doc­u­men­ta­tion for the Python builtin math mod­ule:

PROJECT_TITLE = "Python math"
INPUT_MODULES = ['math']

Now, run the script and pass path to the con­fig­u­ra­tion file to it:

./ path/to/

This will gen­er­ate an output/ di­rec­to­ry next to the file and fill it with the gen­er­at­ed out­put. Open index.html to see the re­sult.


  • Theme tai­lored from scratch for Python-spe­cif­ic lan­guage fea­tures
  • Us­es code in­spec­tion to query mod­ules, class­es, da­ta, func­tions and their sig­na­tures, does not re­ly on er­ror-prone source code pars­ing
  • Does not force the doc­u­men­ta­tion writ­er to ex­plic­it­ly list all sym­bols in or­der to have them doc­u­ment­ed
  • Can use both in-code doc­strings and ex­ter­nal reST files to de­scribe the APIs, giv­ing the us­er a con­trol over the code size vs doc­u­men­ta­tion ver­bosi­ty trade­off


To­geth­er with the above PROJECT_TITLE and INPUT_MODULES vari­ables men­tioned above, the con­fig­u­ra­tion file sup­ports the fol­low­ing vari­ables. The op­tions are sim­i­lar to the Doxy­gen con­fig, but free of the Doxy­gen-spe­cif­ic nam­ing and con­straints.

Vari­able De­scrip­tion
PROJECT_TITLE: str Project ti­tle. Ren­dered in top navbar, page ti­tle and fine print. If not set, "My Python Project" is used.
PROJECT_SUBTITLE: str Project sub­ti­tle. If set, ap­pend­ed in a thin­ner font to PROJECT_TITLE.
PROJECT_LOGO: str URL of an im­age to use as a log in the top navbar. De­fault is none.
MAIN_PROJECT_URL: str If set and PROJECT_SUBTITLE is al­so set, then PROJECT_TITLE in the top navbar will link to this URL and PROJECT_SUBTITLE to the doc­u­men­ta­tion main page, sim­i­lar­ly as shown here.
INPUT: str Base in­put di­rec­to­ry. If not set, con­fig file base dir is used. Rel­a­tive paths are rel­a­tive to con­fig file base dir.
OUTPUT: str Where to save the out­put. Rel­a­tive paths are rel­a­tive to INPUT; if not set, output/ is used.
INPUT_MODULES: List[Any] List of mod­ules to gen­er­ate the docs from. Val­ues can be ei­ther strings or mod­ule ob­jects. See Mod­ule in­spec­tion for more in­for­ma­tion.
INPUT_PAGES: List[str] List of reST files for stand­alone pages. See Pages for more in­for­ma­tion.
THEME_COLOR: str Col­or for <meta name="theme-color" />, cor­re­spond­ing to the CSS style. If emp­ty, no <meta> tag is ren­dered. See Theme se­lec­tion for more in­for­ma­tion.
FAVICON: str Fav­i­con URL, used to pop­u­late <link rel="icon" />. If emp­ty, no <link> tag is ren­dered. Rel­a­tive paths are searched rel­a­tive to INPUT and to the script dir as a fall­back. See Theme se­lec­tion for more in­for­ma­tion.
STYLESHEETS: List[str] List of CSS files to in­clude. Rel­a­tive paths are searched rel­a­tive to INPUT and to the script dir as a fall­back. See Theme se­lec­tion for more in­for­ma­tion.
HTML_HEADER: str HTML code to put at the end of the <head> el­e­ment. Use­ful for link­ing ar­bi­trary JavaScript code or, for ex­am­ple, adding <link> CSS stylesheets with ad­di­tion­al prop­er­ties and IDs that are oth­er­wise not pos­si­ble with just STYLESHEETS.
EXTRA_FILES: List[str] List of ex­tra files to copy (for ex­am­ple ad­di­tion­al CSS files that are @imported from the pri­ma­ry one). Rel­a­tive paths are searched rel­a­tive to INPUT and to the script dir as a fall­back.
LINKS_NAVBAR1: List[Any] Left navbar col­umn links. See Navbar links for more in­for­ma­tion.
LINKS_NAVBAR2: List[Any] Right navbar col­umn links. See Navbar links for more in­for­ma­tion.
PAGE_HEADER: str reST markup to put at the top of ev­ery page. If not set, noth­ing is added any­where. The {url} place­hold­er is re­placed with cur­rent file URL.
FINE_PRINT: str reST markup to put in­to the foot­er. If not set, a de­fault gener­ic text is used. If emp­ty, no foot­er is ren­dered at all.
FORMATTED_METADATA: List[str] Which meata­da­ta fields should be for­mat­ted in doc­u­men­ta­tion pages. By de­fault on­ly the summary field is.
PLUGINS: List[str] List of plug­ins to use. See Plug­ins for more in­for­ma­tion.
PLUGIN_PATHS: List[str] Ad­di­tion­al plug­in search paths. Rel­a­tive paths are rel­a­tive to INPUT.
CLASS_INDEX_EXPAND_LEVELS How many lev­els of the class in­dex tree to ex­pand. 0 means on­ly the top-lev­el sym­bols are shown. If not set, 1 is used.
CLASS_INDEX_EXPAND_INNER Whether to ex­pand in­ner class­es in the class in­dex. If not set, False is used.
NAME_MAPPING: Dict[str, str] Ad­di­tion­al name map­ping in ad­di­tion to what’s fig­ured out from the __all__ mem­bers
PYBIND11_COMPATIBILITY: bool En­able some ad­di­tion­al tricks for bet­ter com­pat­i­bil­i­ty with py­bind11. If not set, False is used. See py­bind11 com­pat­i­bil­i­ty for more in­for­ma­tion.
ATTRS_COMPATIBILITY: bool En­able some ad­di­tion­al tricks for bet­ter com­pat­i­bil­i­ty with at­trs. If not set, False is used. See at­trs com­pat­i­bil­i­ty for more in­for­ma­tion.
SEARCH_DISABLED: bool Dis­able search func­tion­al­i­ty. If this op­tion is set, no search da­ta is com­piled and the ren­dered HTML does not con­tain search-re­lat­ed UI or sup­port. If not set, False is used.
SEARCH_DOWNLOAD_BINARY Down­load search da­ta as a bi­na­ry to save band­width and ini­tial pro­cess­ing time. If not set, False is used. See Search op­tions for more in­for­ma­tion.
SEARCH_HELP: str reST markup to dis­play as help text on emp­ty search pop­up. If not set, a de­fault mes­sage is used. Has ef­fect on­ly if SEARCH_DISABLED is not True.
SEARCH_BASE_URL: str Base URL for OpenSearch-based search en­gine sug­ges­tions for web browsers. See Search op­tions for more in­for­ma­tion. Has ef­fect on­ly if SEARCH_DISABLED is not True.
SEARCH_EXTERNAL_URL: str URL for ex­ter­nal search. The {query} place­hold­er is re­placed with ur­len­cod­ed search string. If not set, no ex­ter­nal search is of­fered. See Search op­tions for more in­for­ma­tion. Has ef­fect on­ly if SEARCH_DISABLED is not True.
DOCUTILS_SETTINGS: Dict[Any] Ad­di­tion­al do­cu­tils set­tings. Key/val­ue pairs as de­scribed in the docs.
URL_FORMATTER: Callable Func­tion for cre­at­ing file­names and URLs for mod­ules, class­es, pages and in­dex pages. See Cus­tom URL for­mat­ters for more in­for­ma­tion.
ID_FORMATTER: Callable Func­tion for cre­at­ing link an­chors for mod­ule and class mem­bers. See Cus­tom URL for­mat­ters for more in­for­ma­tion.

Theme se­lec­tion

By de­fault, the dark m.css theme to­geth­er with doc­u­men­ta­tion-theme-spe­cif­ic ad­di­tions is used, which cor­re­sponds to the fol­low­ing con­fig­u­ra­tion:

THEME_COLOR = '#22272e'
FAVICON = 'favicon-dark.png'

If you have a site al­ready us­ing the m-dark.compiled.css file, there’s an­oth­er file called m-dark.documentation.compiled.css, which con­tains just the doc­u­men­ta­tion-theme-spe­cif­ic ad­di­tions so you can re­use the al­ready cached m-dark.compiled.css file from your main site:

THEME_COLOR = '#22272e'
FAVICON = 'favicon-dark.png'

If you pre­fer the light m.css theme in­stead, use the fol­low­ing con­fig­u­ra­tion (and, sim­i­lar­ly, you can use m-light.compiled.css to­geth­er with m-light.documentation.compiled-css in place of m-light+documentation.compiled.css:

THEME_COLOR = '#cb4b16'
FAVICON = 'favicon-light.png'

See the CSS files sec­tion be­low for more in­for­ma­tion about cus­tomiz­ing the CSS files.

Search op­tions

Sym­bol search is im­ple­ment­ed us­ing JavaScript Typed Ar­rays and does not need any serv­er-side func­tion­al­i­ty to per­form well — the client au­to­mat­i­cal­ly down­loads a tight­ly packed bi­na­ry con­tain­ing search da­ta and per­forms search di­rect­ly on it.

How­ev­er, due to re­stric­tions of Chro­mi­um-based browsers, it’s not pos­si­ble to down­load da­ta us­ing XMLHttpRequest when served from a lo­cal file-sys­tem. Be­cause of that, the search de­faults to pro­duc­ing a Base85-en­cod­ed rep­re­sen­ta­tion of the search bi­na­ry and load­ing that asyn­chronous­ly as a plain JavaScript file. This re­sults in the search da­ta be­ing 25% larg­er, but since this is for serv­ing from a lo­cal filesys­tem, it’s not con­sid­ered a prob­lem. If your docs are ac­cessed through a serv­er (or you don’t need Chrome sup­port), set the SEARCH_DOWNLOAD_BINARY op­tion to True. The search da­ta are by de­fault fetched from the cur­rent di­rec­to­ry on the web­serv­er, if you want to sup­ply a dif­fer­ent lo­ca­tion, set it to a string and pro­vide a cus­tom URL for­mat­ter.

The site can pro­vide search en­gine meta­da­ta us­ing the OpenSearch spec­i­fi­ca­tion. On sup­port­ed browsers this means you can add the search field to search en­gines and search di­rect­ly from the ad­dress bar. To en­able search en­gine meta­da­ta, point SEARCH_BASE_URL to base URL of your doc­u­men­ta­tion, for ex­am­ple:


In gen­er­al, even with­out the above set­ting, ap­pend­ing ?q={query}#search to the URL will di­rect­ly open the search pop­up with re­sults for {query}.

If SEARCH_EXTERNAL_URL is spec­i­fied, full-text search us­ing an ex­ter­nal search en­gine is of­fered if noth­ing is found for giv­en string or if the us­er has JavaScript dis­abled. It’s rec­om­mend­ed to re­strict the search to a par­tic­u­lar do­main or add ad­di­tion­al key­words to the search query to fil­ter out ir­rel­e­vant re­sults. Ex­am­ple, us­ing Google search en­gine and re­strict­ing the search to a sub­do­main:


Cus­tom URL for­mat­ters

The URL_FORMATTER op­tion al­lows you to con­trol how all file­names and gen­er­at­ed URLs look like. It takes an en­try type and a “path” as a list of strings (so for ex­am­ple my.module.Class is rep­re­sent­ed as ['my', 'module', 'Class']), re­turn­ing a tu­ple of a file­name and an URL. Those can be the same, but al­so dif­fer­ent (for ex­am­ple a file get­ting saved in­to my/module/Class/index.html but the ac­tu­al URL be­ing The de­fault im­ple­men­ta­tion looks like this, pro­duc­ing both file­names and URLs in the form of my.module.Class.html:

def default_url_formatter(type: EntryType, path: List[str]) -> Tuple[str, str]:
    if type == EntryType.STATIC:
        url = os.path.basename(path[0])

        # Encode version information into the search driver
        if url == 'search.js':
            url = 'search-v{}.js'.format(searchdata_format_version)

        return url, url

    url = '.'.join(path) + '.html'
    assert '/' not in url
    return url, url

The type is an enum, if you don’t want to fid­dle with im­ports, com­pare against a string, which is one of 'PAGE', 'MODULE', 'CLASS', 'SPECIAL' or 'STATIC'. The 'SPECIAL' is for in­dex pages and in that case the path has al­ways just one item, one of 'pages', 'modules' or 'classes'. The 'STATIC' is for stat­ic da­ta such as im­ages or CSS files and the path is ab­so­lute in­put file­name in­clud­ing the ex­ten­sion and ex­cept for search da­ta (which are gen­er­at­ed on-the-fly) it al­ways ex­ists. If the stat­ic path is an URL, the URL for­mat­ter is not called.

The ID_FORMATTER han­dles for­mat­ting of an­chors on a page. Again it takes an en­try type (which in this case is al­ways one of 'ENUM', 'ENUM_VALUE', 'FUNCTION', 'PROPERTY', 'DATA' or, in case of py­bind11 code, 'OVERLOADED_FUNCTION'. The sec­ond pa­ram­e­ter is again a path, be­ing al­ways just one item ex­cept for 'ENUM_VALUE' (in which case it’s enum name and val­ue name to­geth­er) and for 'OVERLOADED_FUNCTION', in which case it con­tains al­so a llist of ar­gu­ment types. The de­fault im­ple­men­ta­tion sim­ply re­turns the the path con­cate­nat­ed with dash­es:

def default_id_formatter(type: EntryType, path: List[str]) -> str:
    return '-'.join(path)

Mod­ule in­spec­tion

By de­fault, if a mod­ule con­tains the __all__ at­tribute, all names list­ed there are ex­posed in the doc­u­men­ta­tion. Oth­er­wise, all mod­ule (and class) mem­bers are ex­tract­ed us­ing inspect.getmembers(), skip­ping names imported from else­where and un­doc­u­ment­ed un­der­scored names. Ad­di­tion­al­ly, class da­ta mem­bers with type an­no­ta­tions (but with no val­ues) are pulled out from __annotations__, al­low­ing you to ex­pose (and doc­u­ment) al­so fields that might oth­er­wise on­ly be pop­u­lat­ed from __init__():

class MyClass:
    a_float: float
    string_value: str

De­tect­ing if a mod­ule is a sub­mod­ule of the cur­rent pack­age or if it’s imported from else­where is tricky, the script thus in­cludes on­ly sub­mod­ules that have their __package__ prop­er­ty the same or one lev­el be­low the par­ent pack­age. If a mod­ule’s __package__ is emp­ty, it’s as­sumed to be a plain mod­ule (in­stead of a pack­age) and since those can’t have sub­mod­ules, all found sub­mod­ules in it are ig­nored.


By de­fault, the first para­graph of a mod­ule-lev­el, class-lev­el and func­tion-lev­el doc­string is used as a doc sum­ma­ry, copied as-is to the out­put with­out for­mat­ting it in any way. What fol­lows is put (again with­out for­mat­ting) para­graph-by-para­graph in­to de­tailed docs.

"""Module summary

First paragraph of module detailed docs."""

class Foo:
    """Class summary"""

    def bar(self):
        """Function summary"""

Us­ing just doc­strings, how­ev­er, comes with a few lim­i­ta­tions:

  • Class and mod­ule-lev­el vari­ables can’t have a doc­string at­tached due to how Python works
  • Be­cause not ev­ery Python API can be doc­u­ment­ed us­ing doc­strings, the out­put con­tains ev­ery­thing, in­clud­ing un­doc­u­ment­ed names
  • In­stance vari­ables added in­side __init__() are not ex­tract­ed, as this would re­quire pars­ing Python code di­rect­ly (which is what Sphinx has to do to sup­port these). You can work around this by adding an­no­tat­ed “dec­la­ra­tions” to the class as shown above, how­ev­er no doc­strings can be spec­i­fied for those ei­ther.

To over­come the lim­i­ta­tions, ex­ter­nal­ly-sup­plied doc­u­men­ta­tion pro­vides means to doc­u­ment names that can’t have a doc­string at­tached, and to­geth­er with the m.sphinx plug­in ex­pand­ing for­mat­ting ca­pa­bil­i­ties be­yond plain text.

Func­tion and vari­able an­no­ta­tions

The script us­es inspect.signature() to query func­tion pa­ram­e­ter / re­turn type an­no­ta­tions to­geth­er with de­fault val­ues and dis­plays them in the out­put. Sim­i­lar is for mod­ule and class vari­ables, ex­tract­ed from the __annotations__ prop­er­ty. If a vari­able type im­ple­ments __repr__(), a repr() of it is print­ed as the val­ue, oth­er­wise the val­ue is omit­ted.

from typing import Tuple, List

def foo(a: str, be_nice: bool = True) -> Tuple[int, str]:

SETTINGS: List[Tuple[str, bool]] = []

For bet­ter read­abil­i­ty, if the func­tion sig­na­ture con­tains type an­no­ta­tions or a de­fault val­ue, the ar­gu­ments are print­ed each on one line. Oth­er­wise, to avoid wast­ing ver­ti­cal space, the ar­gu­ments are list­ed on a sin­gle line.

Sim­i­lar­ly to how the builtin help() in Python 3.7 start­ed an­no­tat­ing bound­aries be­tween po­si­tion-on­ly, po­si­tion-or-key­word and key­word-on­ly ar­gu­ments with / and *, the same is done here — it’s es­pe­cial­ly help­ful for na­tive func­tions, where you can for ex­am­ple call math.sin(0.3) but not math.sin(x=0.3), be­cause the x ar­gu­ment is po­si­tion­al-on­ly. Cur­rent­ly, po­si­tion­al-on­ly ar­gu­ments are pos­si­ble on­ly with na­tive func­tions, PEP570 adds them for pure Python func­tions as well.

In some cas­es, es­pe­cial­ly when doc­u­ment­ing na­tive func­tions, the sig­na­ture can’t be ex­tract­ed and the func­tion sig­na­ture shows just an el­lip­sis () in­stead of the ac­tu­al ar­gu­ment list.

Class meth­ods, stat­ic meth­ods, dun­der meth­ods, prop­er­ties

Meth­ods dec­o­rat­ed with @classmethod are put in­to a “Class meth­ods” sec­tion, @staticmethods in­to a “Stat­ic meth­ods” sec­tion. Dou­ble-un­der­scored meth­ods ex­plic­it­ly im­ple­ment­ed in the class are put in­to a “Spe­cial meth­ods” sec­tion, oth­er­wise they’re ig­nored — by de­fault, Python adds a large col­lec­tion of dun­der meth­ods to each class and the on­ly way to know if the method is us­er-pro­vid­ed or im­plic­it is by check­ing the doc­string.

class MyClass:
    def a_classmethod(cls):
        """A class method"""

    def a_staticmethod():
        """A static method"""

    def __init__(self, foo, bar):
        """A constructor"""

Prop­er­ties added to class­es ei­ther us­ing the @property dec­o­ra­tor or cre­at­ed with the property() builtin are added to the “Prop­er­ties” sec­tion. Each prop­er­ty is an­no­tat­ed with get set del if it has a get­ter, a set­ter and a deleter or with get and oth­er vari­ants if it has just some. The doc­string and type an­no­ta­tion is ex­tract­ed from the prop­er­ty get­ter.

from typing import Tuple

class MyClass:
    def a_read_write_property(self) -> Tuple[int, int]:
        """A read-write tuple property"""

    def a_read_write_property(self, a):
        # Docstring and type annotation taken from the getter, no need to
        # have it repeated here too

Doc­u­ment­ing enum val­ues

Python sup­plies an im­plic­it doc­strings for enums de­rived from enum.Enum and enum val­ues im­plic­it­ly in­her­it the doc­string of the enum class. If ei­ther is de­tect­ed to be the case, doc­string of the enum or the val­ue is ig­nored. While it’s pos­si­ble to doc­u­ment enum class­es the usu­al way, there’s a non-ob­vi­ous way to doc­u­ment enum val­ues as well.

import enum

class MyEnum(enum.Enum):
    """My enum"""

    ZERO = 0
    TWO = 3

MyEnum.ZERO.__doc__ = "Zero value"
MyEnum.TWO.__doc__ = "Three, but named TWO for compatibility"

The doc­u­men­ta­tion out­put for enums in­cludes enum val­ue val­ues and the class it was de­rived from, so it’s pos­si­ble to know whether it’s an enum or a flag.

In­clud­ing un­der­scored names in the out­put

By de­fault, names start­ing with an un­der­score (ex­cept for __dunder__ meth­ods) are treat­ed as pri­vate and not list­ed in the out­put. One way to ex­pose them is to list them in __all__, how­ev­er that works for mod­ule con­tent on­ly. For ex­pos­ing gen­er­al un­der­scored names, you ei­ther need to pro­vide a doc­string or ex­ter­nal doc­u­men­ta­tion con­tent (and in case of plain da­ta, ex­ter­nal doc­u­men­ta­tion con­tent is the on­ly op­tion).

Note that at the point where mod­ules and class­es are crawled for mem­bers, doc­strings are not parsed yet — so e.g. a da­ta doc­u­men­ta­tion via a :data: op­tion of the .. py:class:: m.sphinx di­rec­tive won’t be vis­i­ble to the ini­tial crawl and thus the da­ta will stay hid­den.

Some­times, how­ev­er, you’ll want the in­verse — keep­ing an un­der­scored name hid­den, even though it has a doc­string. So­lu­tion is to re­move the doc­string while gen­er­at­ing the docs, di­rect­ly in the file dur­ing mod­ule im­port:

import mymodule
mymodule._private_thing.__doc__ = None

INPUT_MODULES = [mymodule]


In ad­di­tion to doc­u­men­ta­tion gen­er­at­ed by in­spect­ing par­tic­u­lar mod­ule, it’s pos­si­ble to add ded­i­cat­ed doc­u­men­ta­tion pages. Con­tent is writ­ten in reST (see Writ­ing reST con­tent for a short in­tro­duc­tion) and tak­en from files spec­i­fied in INPUT_PAGES. File­names are in­ter­pret­ed rel­a­tive to con­fig­u­ra­tion file path, out­put file­name is in­put base­name with ex­ten­sion re­placed to .html. In par­tic­u­lar, con­tent of a index.rst file is used for the doc­u­men­ta­tion main page. Ex­am­ple:

INPUT_PAGES = ['pages/index.rst']
My Python library

:summary: Welcome on the main page!

This is a documentation of the mypythonlib module. You can use it like

.. code:: py

    import mypythonlib

Apart from :summary:, the page can have any num­ber of meta­da­ta, with all of them ex­posed as prop­er­ties of page in the out­put tem­plates. Fields list­ed in FORMATTED_METADATA (the :summary: is among them) are ex­pect­ed to be for­mat­ted as reST and ex­posed as HTML, oth­er­wise as a plain text.

All ref­er­enced im­ages are ex­pect­ed to have ei­ther an ab­so­lute URL or be rel­a­tive to INPUT, the ones with rel­a­tive paths are then copied di­rect­ly to OUTPUT with the lead­ing dirs stripped from the path.


The reST con­tent is not lim­it­ed to just the builtin func­tion­al­i­ty and it’s pos­si­ble to ex­tend it via plug­ins eit­er from m.css it­self or 3rd par­ty ones. See doc­u­men­ta­tion of each plug­in to see its us­age; the m.html­san­i­ty plug­in is used un­con­di­tion­al­ly while all oth­ers are op­tion­al. For ex­am­ple, en­abling the com­mon m.css plug­ins might look like this:

PLUGINS = ['m.code', 'm.components', 'm.dox']

Ex­ter­nal doc­u­men­ta­tion con­tent

Be­cause it’s of­ten not fea­si­ble to have the whole doc­u­men­ta­tion stored in Python doc­strings, the gen­er­a­tor al­lows you to sup­ply doc­u­men­ta­tion from ex­ter­nal files. Sim­i­lar­ly to pages, the INPUT_DOCS set­ting is a list of reST files that con­tain doc­u­men­ta­tion for par­tic­u­lar names us­ing cus­tom di­rec­tives. A set of cus­tom di­rec­tives is pro­vid­ed by the m.sphinx plug­in — see its doc­u­men­ta­tion for de­tailed de­scrip­tion of all fea­tures. Be­low is a sim­ple ex­am­ple of us­ing it to doc­u­ment a class:

PLUGINS = ['m.sphinx']
INPUT_DOCS = ['docs.rst']
.. py:class:: mymodule.sub.Class
    :summary: A pretty class

    This class is *pretty*.

py­bind11 com­pat­i­bil­i­ty

C++ bind­ings gen­er­at­ed us­ing py­bind11 do not have all in­for­ma­tion ac­ces­si­ble through in­tro­spec­tion and thus the script has to do a few py­bind11-spe­cif­ic work­arounds to gen­er­ate ex­pect­ed out­put. This be­hav­ior is not en­abled by de­fault as it might have un­want­ed con­se­quences in pure Python code, en­able it us­ing the PYBIND11_COMPATIBILITY op­tion.

Func­tion sig­na­tures, prop­er­ty an­no­ta­tions

For rea­sons ex­plained in py­bind/py­bind11#990, py­bind11 is not able to pro­vide func­tion sig­na­tures through in­tro­spec­tion and thus the script falls back to pars­ing ar­gu­ment names, type an­no­ta­tions and de­fault val­ues from the doc­string in­stead. By de­fault, un­less py::arg() is used, func­tion ar­gu­ments are po­si­tion­al-on­ly (shown as arg0, arg1, …) and marked as such in the out­put.

Sim­i­lar­ly, prop­er­ty types are ex­tract­ed from get­ter doc­strings.

Un­like Python, py­bind11 has a builtin sup­port for over­load­ed func­tions — de­pend­ing on types passed to a func­tion, it dis­patch­es to a par­tic­u­lar C++ over­load. The over­loads are ex­pand­ed in the out­put as well, mean­ing you can see one func­tion men­tioned more than once with dif­fer­ent sig­na­tures.

Be­cause stat­ic meth­ods in py­bind11 are not dec­o­rat­ed with @staticmethod, they are de­tect­ed based on pres­ence of self as the first pa­ram­e­ter — if it’s there, it’s an in­stance method, oth­er­wise it’s a stat­ic method.


Enums in py­bind11 are not de­rived from enum.Enum, but rather are plain class­es. The on­ly re­li­able way to de­tect a py­bind11 enum is by look­ing for a __members__ mem­ber, which is a dict pro­vid­ing string names and their cor­re­spond­ing val­ues. With py­bind 2.2, it’s on­ly pos­si­ble to doc­u­ment the enum class it­self, not the val­ues.

at­trs com­pat­i­bil­i­ty

If a code­base is us­ing the at­trs pack­age and the ATTRS_COMPATIBILITY op­tion is en­abled, the script is able to ex­tract the (oth­er­wise in­ac­ces­si­ble by nor­mal means) in­for­ma­tion about at­tributes de­fined us­ing attr.ib() or via the @attr.s(auto_attribs=True) dec­o­ra­tor. Note that at­tributes of class­es us­ing @attr.s(slots=True) are vis­i­ble even with­out the com­pat­i­bil­i­ty en­abled.

In all cas­es, there’s no pos­si­bil­i­ty of adding in-source doc­strings for any of these and you need to sup­ply the doc­u­men­ta­tion with the .. py:property:: di­rec­tive as de­scribed in Ex­ter­nal doc­u­men­ta­tion con­tent.

Ad­di­tion­al­ly, var­i­ous dun­der meth­ods that say just “Au­to­mat­i­cal­ly cre­at­ed by at­trs.” in their doc­string are im­plic­it­ly hid­den from the out­put if this op­tion is en­abled. In or­der to show them again, over­ride the doc­string to some­thing mean­ing­ful.

Com­mand-line op­tions

./ [-h] [--templates TEMPLATES] [--debug] conf


  • conf — con­fig­u­ra­tion file


  • -h, --help — show this help mes­sage and ex­it
  • --templates TEMPLATES — tem­plate di­rec­to­ry. De­faults to the templates/python/ sub­di­rec­to­ry if not set.
  • --debug — ver­bose log­ging out­put. Use­ful for de­bug­ging.

Im­ple­ment­ing cus­tom plug­ins

Third-par­ty plug­ins can be load­ed from paths spec­i­fied in PLUGIN_PATHS. Cus­tom plug­ins need to im­ple­ment a reg­is­tra­tion func­tion named register_mcss(). It gets passed the fol­low­ing named ar­gu­ments and the plug­in might or might not use them.

Key­word ar­gu­ment Con­tent
mcss_settings Dict con­tain­ing all m.css set­tings
jinja_environment Jin­ja2 en­vi­ron­ment. Use­ful for adding new fil­ters etc.
module_doc_contents Mod­ule doc­u­men­ta­tion con­tents
class_doc_contents Class doc­u­men­ta­tion con­tents
enum_doc_contents Enum doc­u­men­ta­tion con­tents
enum_value_doc_contents Enum doc­u­men­ta­tion con­tents
function_doc_contents Func­tion doc­u­men­ta­tion con­tents
property_doc_contents Prop­er­ty doc­u­men­ta­tion con­tents
data_doc_contents Da­ta doc­u­men­ta­tion con­tents
hooks_post_crawl Hooks to call af­ter the ini­tial name crawl
hooks_pre_scope Hooks to call on scope en­ter
hooks_post_scope Hooks to call on scope ex­it
hooks_docstring Hooks to call when pars­ing a doc­string
hooks_pre_page Hooks to call be­fore each page gets ren­dered
hooks_post_run Hooks to call at the very end of the script run

The module_doc_contents, class_doc_contents, enum_doc_contents, enum_value_doc_contents, function_doc_contents, property_doc_contents and data_doc_contents vari­ables are Dict[str, Dict[str, str]], where the first lev­el is a name and sec­ond lev­el are key/val­ue pairs of the ac­tu­al HTML doc­u­men­ta­tion con­tent. Plug­ins that parse ex­tra doc­u­men­ta­tion in­puts (such as m.sphinx) are sup­posed to add to the dict, which is then used to fill the ac­tu­al doc­u­men­ta­tion con­tents. The fol­low­ing cor­re­sponds to the doc­u­men­ta­tion source shown in the Ex­ter­nal doc­u­men­ta­tion con­tent sec­tion be­low. Note that the dict can al­ready have ex­ist­ing en­tries added from else­where, so it’s im­por­tant to avoid ful­ly over­writ­ing it:

docs = class_doc_contents.setdefault('mymodule.sub.Class', {})
docs['summary'] = "A pretty class"
docs['details'] = "This class is *pretty*."

The hooks_post_crawl, hooks_docstring, hooks_pre_page and hooks_post_run vari­ables are lists of func­tions. Plug­ins that need to do some­thing at spe­cif­ic points of the ex­e­cu­tion are sup­posed to add func­tions to the list.

The hooks_post_crawl is called once gath­er­ing of all names is done. It gets passed the fol­low­ing ar­gu­ments:

Key­word ar­gu­ment Con­tent

Map with all gath­ered mod­ule, class, enum, func­tion, prop­er­ty, da­ta and page meta­da­ta. Plug­ins are al­lowed to read from it (for ex­am­ple to se­ri­al­ize them to a file for search­ing or link­ing from oth­er projects) as well as write to it (for ex­am­ple to al­low link­ing to names from ex­ter­nal projects). Key is a name, val­ue has at the fol­low­ing prop­er­ties:

Prop­er­ty De­scrip­tion
type En­try type. Same as the enum passed to cus­tom URL for­mat­ters.
object Ob­ject which the en­try doc­u­ments. This prop­er­ty be­ing None means this en­try is ex­ter­nal 1; if not present at all there’s filename in­stead.
filename File this en­try doc­u­ments. Present on­ly for EntryType.PAGE or EntryType.SPECIAL.
path Path. Equiv­a­lent to key.split('.').
url URL to the en­try doc­u­men­ta­tion, for­mat­ted with cus­tom URL for­mat­ters.
css_classes List of CSS class­es to add to the <a> tag. In­ter­nal en­tries usu­al­ly have ['m-doc'] while ex­ter­al have ['m-doc-external'].
^ As this dis­tin­guish­es be­tween in­ter­nal and ex­ter­nal en­tries, new en­tries added by the plug­in need to have object set to None so the script as well as oth­er plug­ins can cor­rect­ly dis­tin­guish them.

The hooks_pre_scope and hooks_post_scope get called be­fore en­ter­ing and af­ter leav­ing a name scope (page, mod­ule, class, enum, enum val­ue, func­tion, prop­er­ty or da­ta), and are meant main­ly to aid with con­text-sen­si­tive link­ing. Those scopes can be nest­ed and can be called suc­ces­sive­ly for the same scope — for ex­am­ple, when ren­der­ing mod­ule docs, hooks_pre_scope gets called first for the mod­ule scope, but then an­oth­er hooks_pre_scope gets called when ren­der­ing a sum­ma­ry for ref­er­ence to an in­ner class. Then, hooks_post_scope gets called in re­verse or­der. The plug­ins are ex­pect­ed to im­ple­ment a stack-like da­ta struc­ture for main­tain­ing in­for­ma­tion about cur­rent scope. Both of those func­tions get passed the fol­low­ing ar­gu­ments:

Key­word ar­gu­ment Con­tent
type Type of the scope that’s be­ing en­tered or ex­it­ed. Same as the enum passed to cus­tom URL for­mat­ters.
path Path of the mod­ule / class / func­tion / enum / enum val­ue / da­ta scope that’s be­ing en­tered or ex­it­ed. A list of names, '.'.join(path) is equiv­a­lent to the ful­ly qual­i­fied name.
param_names In case of func­tions, list of pa­ram­e­ter names. This ar­gu­ment is not present oth­er­wise.

Hooks list­ed in hooks_docstring are called when doc­strings are parsed, and al­ways pre­ced­ed by a cor­re­spond­ing hooks_pre_scope call. The first list­ed hook gets the raw doc­string on­ly pro­cessed by inspect.cleandoc() and each fol­low­ing gets the out­put of the pre­vi­ous. When a hook re­turns an emp­ty string, hooks lat­er in the list are not called. String re­turned by the last hook is pro­cessed, if any, the same way as if no hooks would be present — it gets par­ti­tioned in­to sum­ma­ry and con­tent and those put to the out­put as-is, each para­graph wrapped in <p> tags. The hooks are free to do any­thing with the doc­string — ex­tract­ing meta­da­ta from it and re­turn­ing it as-is, tran­spiling it from one markup lan­guage to an­oth­er, or ful­ly con­sum­ing it, pop­u­lat­ing the *_doc_contents vari­ables men­tioned above and re­turn­ing noth­ing back. Each hook gets passed the fol­low­ing ar­gu­ments:

Key­word ar­gu­ment Con­tent
type Name type. Same as the enum passed to cus­tom URL for­mat­ters.
path Path of the mod­ule / class / func­tion / enum / enum val­ue / da­ta con­tain­ing the doc­string. A list of names, '.'.join(path) is equiv­a­lent to the ful­ly qual­i­fied name.
signature Sig­na­ture of a func­tion, for dis­tin­guish­ing be­tween par­tic­u­lar over­loads. In a form of (param1: type1, param2: type2).
doc Doc­string con­tent. Al­ways non-emp­ty — once a hook re­turns noth­ing back, no fur­ther hooks are called.

The hooks_pre_page is called be­fore each page of out­put gets ren­dered. Can be used for ex­am­ple for re­set­ting some in­ter­nal counter for page-wide unique el­e­ment IDs. The hooks_post_run is called af­ter the whole run is done, use­ful for ex­am­ple to se­ri­al­ize cached in­ter­nal state. Cur­rent­ly, those two func­tions get no ar­gu­ments passed.

Reg­is­tra­tion func­tion for a plug­in that needs to query the OUTPUT set­ting might look like this — the re­main­ing key­word ar­gu­ments will col­lapse in­to the **kwargs pa­ram­e­ter. See code of var­i­ous m.css plug­ins for ac­tu­al ex­am­ples. The be­low ex­am­ple shows reg­is­tra­tion of a hy­po­thet­ic HTML val­ida­tor plug­in — it saves the out­put path from set­tings and reg­is­ters a post-run hook that val­i­dates ev­ery­thing in giv­en out­put di­rec­to­ry.

output_dir = None

def _validate_output():

def register_mcss(mcss_settings, hooks_post_run, **kwargs):
    global output_dir
    output_dir = mcss_settings['OUTPUT']
    hooks_post_run += [_validate_output]

Cus­tomiz­ing the tem­plate

The rest of the doc­u­men­ta­tion ex­plains how to cus­tom­ize the builtin tem­plate to bet­ter suit your needs. Each doc­u­men­ta­tion file is gen­er­at­ed from one of the tem­plate files that are bun­dled with the script. How­ev­er, it’s pos­si­ble to pro­vide your own Jin­ja2 tem­plate files for cus­tom­ized ex­pe­ri­ence as well as mod­i­fy the CSS styling.

CSS files

By de­fault, com­piled CSS files are used to re­duce amount of HTTP re­quests and band­width need­ed for view­ing the doc­u­men­ta­tion. How­ev­er, for eas­i­er cus­tomiza­tion and de­bug­ging it’s bet­ter to use the un­pro­cessed stylesheets. The STYLESHEETS op­tion lists all files that go to the <link rel="stylesheet" /> in the re­sult­ing HTML markup, while EXTRA_FILES list the in­di­rect­ly ref­er­enced files that need to be copied to the out­put as well. Be­low is an ex­am­ple con­fig­u­ra­tion cor­re­spond­ing to the dark theme:

THEME_COLOR = '#22272e'

Af­ter mak­ing de­sired changes to the source files, it’s pos­si­ble to post­pro­cess them back to the com­piled ver­sion us­ing the util­i­ty as ex­plained in the CSS themes doc­u­men­ta­tion. In case of the dark theme, the m-dark+documentation.compiled.css and m-dark.documentation.compiled.css files are pro­duced like this:

cd css
./ m-dark.css m-documentation.css -o m-dark+documentation.compiled.css
./ m-dark.css m-documentation.css --no-import -o m-dark.documentation.compiled.css

Out­put tem­plates

Each out­put file is ren­dered with one of these tem­plates:

File­name Use
module.html Mod­ule doc­u­men­ta­tion
class.html Class doc­u­men­ta­tion
page.html Ex­plic­it doc­u­men­ta­tion pages, in­clud­ing the main page

Each tem­plate gets passed all con­fig­u­ra­tion val­ues from the Con­fig­u­ra­tion ta­ble as-is, to­geth­er with a URL vari­able with URL of giv­en out­put file. In ad­di­tion to builtin Jin­ja2 fil­ters, the format_url fil­ter re­turns ei­ther a path for­mat­ted ac­cord­ing to cus­tom URL for­mat­ters, if the path is rel­a­tive; or a full URL, if the ar­gu­ment is an ab­so­lute URL. It’s use­ful in cas­es like this:

{% for css in HTML_EXTRA_STYLESHEET %}
<link rel="stylesheet" href="{{ css|format_url|e }}" />
{% endfor %}

The ac­tu­al page con­tents are pro­vid­ed in a page ob­ject, which has the fol­low­ing prop­er­ties. All ex­posed da­ta are meant to be passed di­rect­ly to the HTML markup with­out any ad­di­tion­al es­cap­ing.

Prop­er­ty De­scrip­tion
page.summary Doc sum­ma­ry
page.filename File name 4
page.url File URL 4
page.breadcrumb List of (title, URL) tu­ples for bread­crumb nav­i­ga­tion.
page.content De­tailed doc­u­men­ta­tion, if any

Each mod­ule page, ren­dered with module.html, has the fol­low­ing ad­di­tion­al prop­er­ties:

Prop­er­ty De­scrip­tion
page.prefix_wbr Ful­ly-qual­i­fied sym­bol pre­fix for giv­en com­pound with trail­ing . with <wbr/> tag af­ter ev­ery ..
page.modules List of in­ner mod­ules. See Mod­ule prop­er­ties for de­tails.
page.classes List of class­es. See Class prop­er­ties for de­tails.
page.enums List of enums. See Enum prop­er­ties for de­tails.
page.functions List of mod­ule-lev­el func­tions. See Func­tion prop­er­ties for de­tails. List of mod­ule-lev­el da­ta. See Da­ta prop­er­ties for de­tails.
page.has_enum_details If there is at least one enum with full de­scrip­tion block 3
page.has_function_details If there is at least one func­tion (or method, in case of class­es) with full de­scrip­tion block 3
page.has_data_details If there is at least one da­ta with full de­scrip­tion block 3

Each class page, ren­dered with class.html, has the fol­low­ing ad­di­tion­al prop­er­ties:

Prop­er­ty De­scrip­tion
page.classmethods List of class meth­ods (an­no­tat­ed with @classmethod). See Func­tion prop­er­ties for de­tails.
page.staticmethods List of stat­ic meth­ods (an­no­tat­ed with @staticmethod). See Func­tion prop­er­ties for de­tails.
page.methods List of meth­ods. See Func­tion prop­er­ties for de­tails.
page.dunder_methods List of dou­ble-un­der­scored spe­cial func­tions. See Func­tion prop­er­ties for de­tails. List of prop­er­ties. See Prop­er­ty prop­er­ties for de­tails.
page.has_property_details If there is at least one prop­er­ty with full de­scrip­tion block 3

Ex­plic­it doc­u­men­ta­tion pages ren­dered with class.html have ad­di­tion­al prop­er­ties tak­en from in­put meta­da­ta. If giv­en meta­da­ta is list­ed in FORMATTED_METADATA, it’s ren­dered in­to HTML, oth­er­wise it’s ex­posed as plain text.

Mod­ule prop­er­ties

Prop­er­ty De­scrip­tion
module.url URL of de­tailed mod­ule doc­u­men­ta­tion Mod­ule name
module.summary Doc sum­ma­ry

Class prop­er­ties

Prop­er­ty De­scrip­tion
class_.url URL of de­tailed class doc­u­men­ta­tion Class name
class_.summary Doc sum­ma­ry

Enum prop­er­ties

Prop­er­ty De­scrip­tion Enum name Enum ID 5
enum.summary Doc sum­ma­ry
enum.content De­tailed doc­u­men­ta­tion, if any
enum.base Base class from which the enum is de­rived. Set to None if no base class in­for­ma­tion is avail­able.
enum.base_link Like enum.base, but with cross-linked types
enum.values List of enum val­ues
enum.has_details If there is enough con­tent for the full de­scrip­tion block. 3
enum.has_value_details If the enum val­ues have de­scrip­tion. Imp­ies enum.has_details.

Ev­ery item of enum.values has the fol­low­ing prop­er­ties:

Prop­er­ty De­scrip­tion Val­ue name Val­ue ID 5
value.value Val­ue val­ue. Set to None if no val­ue is avail­able.
value.content Val­ue doc­u­men­ta­tion, if any

Func­tion prop­er­ties

Prop­er­ty De­scrip­tion Func­tion name Func­tion ID 5
function.summary Doc sum­ma­ry
function.content De­tailed doc­u­men­ta­tion, if any
function.type Func­tion re­turn type an­no­ta­tion 2
function.type_link Like function.type, but with cross-linked types
function.params List of func­tion pa­ram­e­ters. See be­low for de­tails.
function.has_complex_params Set to True if the pa­ram­e­ter list should be wrapped on sev­er­al lines for bet­ter read­abil­i­ty (for ex­am­ple when it con­tains type an­no­ta­tions or de­fault ar­gu­ments). Set to False when wrap­ping on mul­ti­ple lines would on­ly oc­cu­py too much ver­ti­cal space.
function.has_param_details If the func­tion pa­ram­e­ters are doc­u­ment­ed
function.return_value Re­turn val­ue doc­u­men­ta­tion. Can be emp­ty.
function.has_details If there is enough con­tent for the full de­scrip­tion block 3
function.is_classmethod Set to True if the func­tion is an­no­tat­ed with @classmethod, False oth­er­wise.
function.is_staticmethod Set to True if the func­tion is an­no­tat­ed with @staticmethod, False oth­er­wise.

The func.params is a list of func­tion pa­ram­e­ters and their de­scrip­tion. Each item has the fol­low­ing prop­er­ties:

Prop­er­ty De­scrip­tion Pa­ram­e­ter name
param.type Pa­ram­e­ter type an­no­ta­tion 2
param.type_link Like param.type, but with cross-linked types
param.default De­fault pa­ram­e­ter val­ue, if any
param.kind Pa­ram­e­ter kind, a string equiv­a­lent to one of the in­spect.Pa­ram­e­ter.kind val­ues
param.content De­tailed doc­u­men­ta­tion, if any

In some cas­es (for ex­am­ple in case of na­tive APIs), the pa­ram­e­ters can’t be in­tro­spect­ed. In that case, the pa­ram­e­ter list is a sin­gle en­try with name set to "..." and the rest be­ing emp­ty.

Prop­er­ty prop­er­ties

Prop­er­ty De­scrip­tion Prop­er­ty name Prop­er­ty ID 5
property.type Prop­er­ty get­ter re­turn type an­no­ta­tion 2
property.type_link Like property.type, but with cross-linked types
property.summary Doc sum­ma­ry
property.content De­tailed doc­u­men­ta­tion, if any
property.is_gettable If the prop­er­ty is get­table
property.is_settable If the prop­er­ty is set­table
property.is_deletable If the prop­er­ty is deletable with del
property.has_details If there is enough con­tent for the full de­scrip­tion block 3

Da­ta prop­er­ties

Prop­er­ty De­scrip­tion Da­ta name Da­ta ID 5
data.type Da­ta type
data.type_link Like data.type_link, but with cross-linked types
data.summary Doc sum­ma­ry
data.content De­tailed doc­u­men­ta­tion, if any
data.value Da­ta val­ue rep­re­sen­ta­tion
data.has_details If there is enough con­tent for the full de­scrip­tion block 3

In­dex page tem­plates

The fol­low­ing in­dex pages are pro­vid­ed, show­ing a ex­pand­able tree of the con­tents:

File­name Use
classes.html Class list­ing
modules.html Mod­ule list­ing
pages.html Page list­ing

Each tem­plate is passed all con­fig­u­ra­tion val­ues from the Con­fig­u­ra­tion ta­ble as-is, to­geth­er with an URL, as above. The nav­i­ga­tion tree is pro­vid­ed in an index ob­ject, which has the fol­low­ing prop­er­ties:

Prop­er­ty De­scrip­tion
index.classes List of all mod­ules + class­es
index.pages List of all pages

The form of each list en­try is the same:

Prop­er­ty De­scrip­tion
i.kind En­try kind (one of 'module', 'class' or 'page') Name
i.url URL of the file with de­tailed doc­u­men­ta­tion
i.summary Doc sum­ma­ry
i.has_nestable_children If the list has nestable chil­dren (i.e., dirs or names­paces)
i.children Re­cur­sive list of child en­tries

Mod­ule/class list is or­dered in a way that all mod­ules are be­fore all class­es.

^ a b c i.type is ex­tract­ed out of func­tion an­no­ta­tion. If the types aren’t an­no­tat­ed, the an­no­ta­tion is emp­ty.
^ a b c d e f g h page.has_*_details and i.has_details are True if there is de­tailed de­scrip­tion, func­tion pa­ram­e­ter doc­u­men­ta­tion or doc­u­ment­ed enum val­ue list­ing that makes it worth to ren­der the full de­scrip­tion block. If False, the mem­ber should be in­clud­ed on­ly in the sum­ma­ry list­ing on top of the page to avoid un­nec­es­sary rep­e­ti­tion.
^ a b page.filename and page.url is gen­er­at­ed by an URL for­mat­ter, see Cus­tom URL for­mat­ters for more in­for­ma­tion
^ a b c d e is an ID used for link­ing to giv­en en­try on a page. Gen­er­at­ed by an an­chor for­mat­ter, see Cus­tom URL for­mat­ters for more in­for­ma­tion.