Search code examples
python-sphinxrestructuredtextautodoc

Human readable iterables in Sphinx documentation


Sphinx-autodoc flattens dicts, lists, and tuples - making long ones barely readable. Pretty-print format isn't always desired either, as some nested containers are better kept flattened than columned. Is there a way to display iterables as typed in source code?


Solution

  • Get it straight from source, and add an .rst command for it:

    # conf.py
    from importlib import import_module
    from docutils  import nodes
    from sphinx    import addnodes
    from inspect   import getsource
    from docutils.parsers.rst import Directive
    
    class PrettyPrintIterable(Directive):
        required_arguments = 1
    
        def run(self):
            def _get_iter_source(src, varname):
                # 1. identifies target iterable by variable name, (cannot be spaced)
                # 2. determines iter source code start & end by tracking brackets
                # 3. returns source code between found start & end
                start = end = None
                open_brackets = closed_brackets = 0
                for i, line in enumerate(src):
                    if line.startswith(varname):
                        if start is None:
                            start = i
                    if start is not None:
                        open_brackets   += sum(line.count(b) for b in "([{")
                        closed_brackets += sum(line.count(b) for b in ")]}")
    
                    if open_brackets > 0 and (open_brackets - closed_brackets == 0):
                        end = i + 1
                        break
                return '\n'.join(src[start:end])
    
            module_path, member_name = self.arguments[0].rsplit('.', 1)
            src = getsource(import_module(module_path)).split('\n')
            code = _get_iter_source(src, member_name)
    
            literal = nodes.literal_block(code, code)
            literal['language'] = 'python'
    
            return [addnodes.desc_name(text=member_name),
                    addnodes.desc_content('', literal)]
    
    def setup(app):
        app.add_directive('pprint', PrettyPrintIterable)
    

    Example .rst and result:

    enter image description here

    (:autodata: with empty :annotation: is to exclude the original flattened dictionary).

    Some code borrowed from this answer.