Skip to content

Documentation Style Guide

Peter Corke edited this page Oct 22, 2026 · 6 revisions

Documentation Style Guide

RTB uses reST-style docstrings rendered with Sphinx, not the NumPy docstring style. This convention is shared across Peter's Python toolboxes (RTB, MVTB, SMTB, bdsim); see the rvc-ecosystem repo's AGENTS.md (§5, Code Standards) for the canonical source.

Style Guide

1. Docstrings

  • reST style: :param name:, :returns: — not NumPy-style section headers (Parameters/Returns/Notes underlined with dashes)
  • Section headings inside a docstring are rubrics: .. rubric:: Examples (always plural, even for one example), .. rubric:: Notes, .. rubric:: References. Not an underlined title: that is NumPy style, and it only works because Sphinx's napoleon extension reads it
  • Every docstring has a one-line summary, followed by a :param:/:returns: block, followed by extended description and notes
  • Where appropriate, a docstring ends with .. seealso:: cross-links as its last line, the directive and not :seealso:. :seealso: looks right in the source, but Sphinx does not know it: it renders as one more labelled field after the parameters and returns ("Seealso: g()"), not as the "See also" box
  • Docstring references to classes and methods use the :meth: and :class: roles, not plain text or Markdown-style backticks
  • :type: / :rtype: are used only when shape information adds value beyond the signature's type hint (e.g. :type q: ndarray(6,n)) — the type hint itself covers everything else, so don't restate a plain type in :type:/:rtype: as a matter of habit
  • Math docstrings use r"""...""" (a raw string) so LaTeX in :math: renders correctly
  • All public methods and functions are type-hinted, using modern syntax: X | Y, X | None, list[X], dict[K, V] — not Union, Optional, List, Dict from typing

Docstring Section Ordering

r"""
One line description.

Longer paragraph description. Each section of the docstring should be
separated by a blank line.

:param name: description of the parameter
:type name: ndarray(6,n)
:param other: description of another parameter
:return: description of the return value

.. note::
    An optional emphasised important note.

.. warning::
    An optional warning about a gotcha or unsafe usage.

Extended discussion, background, or a worked mathematical explanation can go
here, after the parameter/return block.

.. rubric:: Examples

Prose describing what the example below shows.

.. runblock:: pycon

    >>> import roboticstoolbox as rtb
    >>> panda = rtb.models.Panda().ets()
    >>> solver = rtb.IK_QP()
    >>> Tep = panda.fkine([0, -0.3, 0, -2.2, 0, 2, 0.7854])
    >>> solver.solve(panda, Tep)

.. versionadded:: 1.5.0

.. seealso:: :meth:`related_method` :class:`RelatedClass`
"""

Real example, from roboticstoolbox/robot/IK.py's IK_NR class:

class IK_NR(IKSolver):
    r"""
    Newton-Raphson Numerical Inverse Kinematics Solver

    A class which provides functionality to perform numerical inverse
    kinematics (IK) using the Newton-Raphson method. See `step` method for
    mathematical description.

    .. note::

        When using this class with redundant robots (>6 DoF), `pinv` must
        be set to `True`

    :param name: The name of the IK algorithm
    :param ilimit: How many iterations are allowed within a search before
        a new search is started
    :param tol: Maximum allowed residual error E, where
        :math:`E = \tfrac{1}{2} \vec{e}^\top \mat{W}_e \vec{e}` ...
    """

Class Docstrings

The class docstring documents the constructor's parameters (via :param:), and lives immediately below the class declaration. __init__ itself does not get its own docstring — see IK_NR above: the docstring is on class IK_NR(IKSolver):, and def __init__(self, name=..., ilimit=..., ...): has none.

Code Examples

Code examples that should actually execute (and show real, current output) use .. runblock:: pycon, with a blank line and then the example indented, like any other directive body:

.. runblock:: pycon

    >>> import roboticstoolbox as rtb
    >>> panda = rtb.models.Panda().ets()
    >>> solver = rtb.IK_QP()
    >>> Tep = panda.fkine([0, -0.3, 0, -2.2, 0, 2, 0.7854])
    >>> solver.solve(panda, Tep)

Each runblock is a separate session: a second block needs its own imports, and does not see the variables of the first.

An older layout, with the example on the next line, no blank line and no indent, also works but Sphinx reports "Explicit markup ends without a blank line" for each one, and it is the layout of the minority of the examples. Use the indented one.

A figure is a .. plot:: directive, with :include-source: to show the code that makes it (this is the "pure code plus a figure" form):

.. plot::
    :include-source:

    import roboticstoolbox as rtb

    puma = rtb.models.DH.Puma560()
    puma.plot(puma.qr, block=False)

If the example would produce huge or non-deterministic output (hundreds of table rows, a randomly-seeded result, anything that opens a window or a network connection — e.g. Swift's browser-based visualizer), use a plain, static .. code-block:: pycon (or .. code-block:: text for output) instead, with hand-written representative output. Don't runblock something whose real captured output would be misleading or broken in the built HTML — see docs/source/intro.rst's catalog() example and its Swift-plot example for both cases in practice.

Version notices

A new feature has .. versionadded:: X.Y.Z, and a change in behaviour has .. versionchanged:: X.Y.Z with a sentence saying what changed, and a deprecation has .. deprecated:: X.Y.Z, where X.Y.Z is the release that the change ships in: the next release after the version in pyproject.toml. A notice that names a release that never exists stays wrong.

2. Restructured Text for Built Docs

Class/module reference pages use plain automodule/autoclass directives.

A page documenting a whole module (several related classes), e.g. arm_superclass.rst:

.. automodule:: roboticstoolbox.robot.Robot
   :members:
   :undoc-members:
   :show-inheritance:
   :inherited-members:
   :special-members: __getitem__

A page documenting a single class, e.g. mobile-vehicle-bicycle.rst:

.. autoclass:: roboticstoolbox.mobile.Bicycle
   :members:
   :undoc-members:
   :show-inheritance:
   :inherited-members:
   :special-members: __init__

:special-members: is only needed to pull in a dunder method (like __init__ or __getitem__) that Sphinx doesn't document by default.

Arguments and results

These decisions were first written down in 2020, when the toolbox was being moved from MATLAB, and are what the code does now.

  • Optional arguments are Python keyword arguments, Link(d=0.1, alpha=pi/2), not the MATLAB keyword-value pairs ('d', 0.1) or string options ('deg'). There is no compatibility layer for MATLAB-style arguments.
  • A function that returns several things returns one object with named attributes, not a tuple to be unpacked by position and not a count of requested outputs as in MATLAB's nargout. jtraj returns a Trajectory with .q, .qd and .qdd, and the inverse kinematics return an IKSolution with .q, .success and so on, so jtraj(q0, q1).q reads well. The older namedtuple form is still seen in places.
  • Robot models are one class per robot under roboticstoolbox/models/ (DH, ETS, URDF), each providing its named configurations such as qz and qr, see Models.
  • Examples are standalone scripts and notebooks in the examples and docs/notebooks folders, see Working with Jupyter.

Clone this wiki locally