This chapter contains information useful for developers of PyExpLabSys. The documentation has the form of little sections that each describe a small task.
Table of Contents
- Developer Notes
- Setting up logging for a component of PyExpLabSys
- Editing/Updating Documentation
- Writing Documentation
This section describes how to set up logging for a component in PyExpLabSys with the
logging module (i.e. the meaning of the word “logging” that refers to text
output of debug and status information to e.g. terminal and text files NOT sending data to
This section specifically deals with setting up logging for a component in PyExpLabSys, not of a program merely using PyExpLabSys. For information about how to set up logging for a program merely using PyExpLabSys, see the standard library documentation and Setting up logging of your program for how to use some convenience functions in PyExpLabSys.
Setting up a logger for a component of PyExpLabSys should be done in the manner recommended by the standard library documentation for logging from libraries. I.e. in the beginning of the file to the following:
import logging LOG = logging.getLogger(__name__) LOG.addHandler(logging.NullHandler())
__name__ as the name, will ensure that it gets a name that is the full
qualified name of the module e.g.
If more fine grained logging is required, e.g. if a module consist of several large
classes and it would preferable with a logger per class, they can be set up in the same
manner. Such class loggers should keep the
__name__ as a prefix followed by a “.” and
the name of the class, i.e:
# Assuming logging is already imported for the module logger MYCLASS_LOG = logging.getLogger(__name__ + '.MyClass') MYCLASS_LOG.addHandler(logging.NullHandler()) class MyClass(object): """My fancy class""" pass
After adding a new driver run the script:
will generate driver documentation stubs for all the drivers that did
not previously have one. The stubs are placed in
PyExpLabSys/doc/source/drivers-autogen-only. After generating the
stubs add and commit the new stubs with git.
cd PYEXPLABSYSPATH/doc/source python update-driver-only-autogen-stubs.py git add drivers-autogen-only/*.rst git cm "doc: Added new driver documentation stubs for <name of your driver>"
To add additional documentation for a driver, e.g. usage examples, that is not well suited to be placed directly in the source file, follow this procedure.
In the PyExpLabSys documentation the driver documentation files are located in two different folders depending on whether it is a stub or has extra documentation. To add extra documentation, first git move the file and then start to edit and commit it as usual:
cd PYEXPLABSYSPATH/doc/source git mv drivers-autogen-only/<name_of_your_driver_module>.rst drivers/ # Edit and commit as usual
It is useful to disable caching in your browser temporarily, when it is being used to preview local Sphinx pages. The easiest way to disable browser cache temporarily, is to disable caching when the developer tools are open. For Firefox, the procedure is:
- Open developer view (F12).
- Open the settings for developer view (there is a little gear in the headline of developer view, third icon from the right)
- Under “Advanced Settings” click “Disable Cache (when tool is open)”
In Chromium, the procedure is similar, except the check box is under “General”.
General restructured text primer is located here: http://sphinx-doc.org/rest.html. Most of the examples are from there. Super short summary of that follows:
The way to mark something as a section title is:
##### parts ##### ******** chapters ******** sections ======== subsections ----------- subsubsections ^^^^^^^^^^^^^^ paragraphs """"""""""
The following is the convention for how to use those in PyExpLabSys and the overall structure.
- Uses parts
- includes the main table of contents that links to chapter files for common, drivers, apps etc.
common.rst(or any other chapter file)
- Starts sections at chapter level
- May include an additions table of contents tree for sub files e.g. common_contionuous_logger
- Once again starts at chapter level
How these sections level work, I (Kenneth) must admit I have not investigated in detail. It seems, that you can re-use section levels at a lower level in the document hierarchy, if they are included in a table of contents tree, so we do. At some point it would probably be good to try and understand that better
.. _my-reference-label: Section to Cross-Reference -------------------------- References to its own section: :ref:`my-reference-label` or :ref:`Link title <my-reference-label>``
.. code-block:: python import time t0 = time.time() # Stuff that takes time print(time.time() - t0)
Bullet lists * Item over two lines. Item over two lines. Item over two lines. Item over two lines. Item over two lines. Item over two lines. * Lists can be nested, but must be separated by a blank line * Also when going back in level Numbered lists 1. This is a numbered list. 2. It has two items too. #. This is a numbered list. #. It has two items too.
:py:class:`PyExpLabSys.common.sockets.DateDataPullSocket`will create a link to the documentation like this:
:py:class:`~PyExpLabSys.common.sockets.DateDataPullSocket`will shorten the link text to only the class name:
:py:meth:`.close`will make a link to the
closemethod of the current class.
:py:meth:`~.close`as above using only ‘close’ as the link text
:py:meth:`the close method <.close>`will create a reference to the close method of the current class with the link text ‘the close method’
In general cross references are:
In this form, the role would usually be prefixed with a domain, so it could be
:py:func: to refer to a Python function. However, the
py domain is the
default, so it can be dropped from the role (shortened form).
For Python the relevant roles (in shortened form) are :
:data:for module level variables
:const:a “constant”, a variable that is not supposed to be changed
:obj:for objects of unspecified type
Whatever is written as the target is searched in the order:
- Without any further qualification (directly importable I think)
- Then with the current module preprended
- Then with the current module and class (if any) preprended
If you prefix the target with a
., then this search order is reversed.
Prefixing the target with a
~ will shorten the link text to only show the last
The standard way of writing docstrings, with arguments definitions, in Sphinx is quite ugly and almost unreadable as pure text (which is annoying if you use an editor or IDE which will show you the standard help-invoked documentation.
def old_data(self, codename, timeout=900, unixtime=None): """Checks if the data for codename has timed out Args: codename (str): The codename whose data should be checked for timeout Kwargs: timeout (float): The timeout to use in seconds, defaults to 900s. timestamp (float): Unix timestamp to compare to. Defaults to now. Raises: ValueError: If codename is unknown TypeError: If timeout or unixtime are not floats (or ints where appropriate) Returns: bool: Whether the data is too old or not """
A few things to note:
- Positional arguments, keyword arguments, exceptions and return values (Args, Kwargs, Raises, Returns) are written into sections. There are several aliases for each of them, but these are the recommended ones for PyExpLabSys (all possibly sections).
- All are optional! Do not feel obligated to fill in Raises if it is not relevant.
- Args and kwargs are on the form:
name (type): description
- Raises and Returns (which has no name) are on the form:
- If the description needs to continue on the next line, it will need to be indented another level
The call signature for instantiation should be documented in
In classes, attributes that are not defined explicitly with decorators, are documented in
the class docstring under the
class MyClass(object): """Class that describes me Attributes: name (str): The name of me birthdate (float): Unix timestamp for my birthdate and time """ def __init__(self, name, birthdate): """Initialize parameters""" self.name = name self.birthdate = birthdate @property def age(self): """The approximate age of me in years""" return (time.time() - self.birthdate) / (math.pi * 10**7)
A few things to notice:
- The attributes are listed in the same manner as arguments
- The age attribute, which is explicitely declared, will automatically be documented by its docstring