Search code examples
pythonpython-sphinxautodoc

Sphinx autodoc and multi-line strings


I have a python module which defines a multi-line string constant. I want to have the multi-line string display nicely in the Sphinx-based documentation.

Below, is some example Python code, RST, and how it renders with sphinx-build. However, I would rather get something like the "desired sphinx docs".

Is this possible?

mymodule.py

#: Default configuration
DEFAULT_CONFIG = r"""
{ "foo": "bar",
  "baz": "rex" }
"""

mydocs.rst

...

--------------
Default Config
--------------

.. autodata:: mymodule.DEFAULT_CONFIG

Resulting Sphinx Docs

mymodule.DEFAULT_CONFIG
= "{ \"foo\": \"bar\",\n  \"bar\": \"rex\" }"

str(object=’‘) -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object.
If encoding or errors is specified, then the object
must expose a data buffer that will be decoded using
the given encoding and error handler. Otherwise, returns
the result of object.__str__() (if defined) or repr(object).
encoding defaults to sys.getdefaultencoding(). errors defaults to ‘strict’.

Desired Sphinx Docs

mymodule.DEFAULT_CONFIG = Default configuration
{ "foo": "bar",
  "baz": "rex" }

Solution

  • This cannot be supported directly, but since you're using Sphinx and Python the following hack is what I decided to go with:

    1. In this example, it is important that you can import the variable you desire. This should already be working, since autodoc is able to produce output.

    2. This hack will enable you to have a more human friendly output, but you will also still have the value of the variable (as far as sphinx is concerned) in the undesireable (with a bunch of \n characters).

    I will be reusing my own project for this, but using your variable / value. My package name is exhale and the file I'm putting this in is exhale/configs.py, so that's where that stuff is coming from. So this is the layout:

    File: exhale/configs.py

    This is your actual python code. It looks like this:

    __name__ = "configs"
    __docformat__ = "reStructuredText"
    
    DEFAULT_CONFIG = r"""
    { "foo": "bar",
      "baz": "rex" }
    """
    '''
    This is some description of the use of / intended purpose of the variable.
    
    The contents of this variable are a multi-line string with value:
    
    .. include:: DEFAULT_CONFIG_value.rst
    
    .. note::
    
       The above value is presented for readability, take special care in use of
       this variable that the real value has both a leading and trailing ``\\n``.
    '''
    

    In your sphinx documentation

    In whatever file you had the autodata above (I used automodule, it doesn't matter). The docs look like this (to be clear, you've already got this, and do not need to change it). The things you need to change are your actual python docstring, and the next section. This is here for completeness of the answer.

    Exhale Configs Module
    =====================
    
    .. automodule:: exhale.configs
       :members:
       :undoc-members:
    

    Modify your conf.py

    This is the fancy part, and a huge benefit to using Sphinx -- Python is so freaking convenient when it comes to writing files. In the above docstring, you'll see that I deliberately have a .. include directive. The crazy part about this is we can write this file dynamically. At the bottom of your conf.py, you can just add something like

    # Import the variable from wherever it lives
    from exhale.configs import DEFAULT_CONFIG
    default_parts = DEFAULT_CONFIG.strip().splitlines()
    # Because I am deliberately putting this underneath a '.. code-block:: py',
    # we need to indent by **THREE** spaces.
    #
    # Similarly, when writing to the file, the "\n\n   " before
    # {default_config_value} (the three spaces) is also important ;)
    default_config_value = "\n   ".join(p for p in default_parts)
    with open("DEFAULT_CONFIG_value.rst", "w") as dcv:
        dcv.write(".. code-block:: py\n\n   {default_config_value}\n\n".format(
            default_config_value=default_config_value
        ))
    

    If you are using Python 3, instead of splitting and joining, just use textwrap.indent. I did the above just to make sure Python 2 users can replicate.

    KABOOM

    When you run make html, it will regenerate the file DEFAULT_CONFIG_value.rst every time! So even if you change the value of the variable, it should be good to go. For reference, the generated file for me looks like this

    .. code-block:: py
    
       { "foo": "bar",
         "baz": "rex" }
    

    Note: this is not a standalone reStructuredText document, it should only every be used via .. include::!

    Last but not least, the rendered output looks like this:

    sphinx generated docs

    As stated in the preamble, Sphinx is going to include the \n version in the value. We are just putting this in the docstring. It's very useful to have both. The reason is because with the .. code-block:: py approach, Sphinx will strip leading / trailing newlines (hence the .. note:: in the docstring). So it is very helpful to have the human-readable version, but it's also useful to know the raw value.

    The only other thing worth mentioning here is that the sky is the limit! I chose to use .. code-block:: py for my purposes, but since you're literally writing the file yourself you can do whatever you want. You could write the file so that it instead produces

    .. code-block:: py
    
       DEFAULT_CONFIG = r"""
       { "foo": "bar",
         "baz": "rex" }
       """
    

    by changing the part in conf.py. It's up to you! When changing the output you may get bugs, open up the generated .rst document and make sure it's valid :)