Documentation Guide

This documentation is written using Sphinx.

Installing Sphinx

You will need the sphinx documentation tool and its numpydoc extension, napoleon extension, and mermaid extension, along with the readthedocs theme.

Assuming you have the pip python package installer, you can easily install the required tools from PyPI:

>>> pip install Sphinx, numpydoc, sphinxcontrib-napoleon, sphinxcontrib-mermaid, sphinx_rtd_theme

Building the docs

Navigate to the documentation directory (see Directory Layout). If Sphinx is propery installed, you should be able to just run make html:

>>> make html
[huge printout of compiling status informational messages]

Build finished. The HTML pages are in _build/html

The documentation will be built to HTML and you can view it locally by opening _build/html/index.html

You can also similarly build the documentation to PDF, LaTeX, Windows Help, or several other formats as desired. Use make help to see the available output formats.

Rebuilding from Scratch

If there are structural changes made to the EXOSIMS directory (i.e., file/folder additions or deletions) you will need to run sphinx-apidoc. From the documentation directory run:

>>> sphinx-apidoc -f -o . ../EXOSIMS/

This will rebuild all of the EXOSIMS.* rst files, after which you can run make html to rebuild the html documentation. Note that this command also generates a modules.rst file that we do not use, and which can be safely deleted (but should not be added to the repository).

The prototype input table (EXOSIMS Prototype Inputs) is auto-generated by utility buildargdoc.py in the documentation directory. It can be called as:

>>> python buildargdoc.py

This entire rebuild procedure is also packaged into a bash script for POSIX systems (builddocs.sh) and a batch file for Windows systems ( builddocs.bat). Both are intended to be run from the documentation directory.

Docstrings

Valid and well-formatted docstrings are required for all EXOSIMS code. The file docstring_example.py in the documentation directory (see Directory Layout) provides some examples of the prefered code documentation style. Here we list the most important requirements:

Classes

All Class docstrings (occurring immediately after the class declaration) must include a description of the purpose of the class followed by a full list of arguments to the class __init__, in the exact order of method declaration, and then a full list of class attributes, preferably in alphabetical order. Each entry of both lists must include a well-defined type (see Intersphinx) and a detailed description. For arguments, keyword inputs must list their defaults, and optional inputs must be tagged as such in the type definition. An example of the preferred format is:

class ExampleClass():
    """An explanation of what this is for

    Args:
        input1 (type):
            Description of argument input1
        input2 (type):
            Description of argument input2
        input3 (type):
            Description of keyword input3. Defaults to defaultval
        input4 (type, optional):
            Description of optional keyword input4. Defaults to None

    Attributes:
        attribute1 (type):
            Description of attribute 1
        attribute2 (type):
            Description of attribute 2
    """

    def __init__(
        self,
        input1,
        input2,
        input3=defaultval
        input4=None
    ):

        self.attribute1 = input1 + input2
        if input4 is not None:
            self.attribute2 = input3
        else:
            self.attribute2 = None

Note that the __init__ (unlike all other methods) does not require its own docstring.

Methods

Every method (other than a class __init__) must have a docstring identifying the purpose of the method, its inputs and outputs. Input arguments should be formatted exactly as in the class example, above. The return is described first by its type (not a name). The type is followed by a detailed description of the returns. If a method is returning multiple objects, then the return type is a tuple. Method docstring for class methods should not list self as an argument, and should also note any attributes that are updated by the method. An example of a docstring for a method returning a single value is:

def example_method1(input1):
    """Does some computation on input1

    Args:
        input1 (type):
            Description of input1

    Returns:
        type:
            A description of the return
    """

    out = some_operation_on(input1)

    return out

For multiple outputs:

def example_method2(input1):
    """Does some computation on input1

    Args:
        input1 (type):
            Description of input1

    Returns:
        tuple:
            output1 (type):
                A description of the first output
            output2 (type):
                A description of the second output
    """

    out1 = some_operation_on(input1)
    out2 = some_other_operation_on(input2)

    return out1, out2

Comments

Commenting your code is great! You should do as much of that as possible. However, unless your code and comment fit within 88 characters, your comment should always precede the code. Comments should follow the same line length limits (88 characters) as the code. Comments should use a single # symbol followed by a single space. For example:

# This is an inline comment about the next line:
ouptut = operation(input)

Intersphinx

EXOSIMS docs make use of intersphinx to connect to documentation for both python and other projects (in particular numpy, scipy, and astropy). In order for this to work, it is necessary to make sure that types used in docstrings are well defined. Python native types should be written as:

  • str

  • float

  • int

  • dict

  • list

  • bool

For third party modules, types should be written as full class strings. For example, numpy arrays should be typed as numpy.ndarray. Adding a leading tilde (~numpy.ndarray) will suppress all of the leading packages/modules (so in this case, only ndarray) will be written in the compiled documentation. Try to be as specific as possible about the expected contents of a variable. For example, a list of dictionaries should be typed as list(dict) and a numpy array of booleans should be typed as ~numpy.ndarray(bool). Astropy quantities should be typed as ~astropy.units.Quantity. An astropy quantity array has type ~astropy.units.Quantity(~numpy.ndarray(float)). The associated description of the variable should explicitly state the physical unit type unless it is obvious from the description itself (i.e., a mass is expected to have mass units, etc.). For EXOSIMS conventions, see the prototype docstrings.

EXOSIMS modules should be typed by referencing the module-specific page in this documentation. For example, if a method has a TimeKeeping object as an input, the docstring should look like:

def example_method2(TK):
    """Does some computation requiring TimeKeeping

    Args:
        TK (:ref:`TimeKeeping`):
            TimeKeeping object

    """

Similarly, **specs inputs should reference the Input Specification (by writing :ref:`sec:inputespec` in the docstring).