Source code for nengo.rc

r"""Certain features of Nengo can be configured globally through RC settings.

RC settings can be manipulated either through the ``nengo.rc`` object,
or through RC configuration files.

The ``nengo.rc`` object
=======================

The ``nengo.rc`` object gives programmatic access to
globally configured features of Nengo.

.. autodata:: nengo.rc

Configuration files
===================

``nengo.rc`` is initialized with configuration settings read
from the following files with precedence to those listed first:

1. ``nengorc`` in the current directory. This is intended to allow for project
   specific settings without hard coding them in the model script.

2. An operating system specific file in the user's home directory.

   * Windows: ``%userprofile%\.nengo\nengorc``

   * Other (OS X, Linux): ``~/.config/nengo/nengorc``

3. ``INSTALL/nengo-data/nengorc`` (where ``INSTALL`` is the
   installation directory of the Nengo package).

The RC file is divided into sections by lines containing the section name
in brackets, i.e. ``[section]``. A setting is set by giving the name followed
by a ``:`` or ``=`` and the value. All lines starting with ``#`` or ``;`` are
comments.

For example, to set the size of the decoder cache to 512 MB,
add the following to a configuration file:

.. code-block:: ini

   [decoder_cache]
   size = 512 MB

Configuration options
=====================

All of the configuration options are listed in the example
configuration file, which is included with Nengo
and copied below.

Commented lines show the default values for each setting.

.. _nengorc:

.. include:: ../nengo-data/nengorc
   :literal:
   :start-line: 29

"""

import configparser
import logging

import numpy as np

import nengo.utils.paths

logger = logging.getLogger(__name__)

# The default core Nengo RC settings. Access with
#   nengo.RC_DEFAULTS[section_name][option_name]
RC_DEFAULTS = {
    "precision": {"bits": 64},
    "decoder_cache": {
        "enabled": True,
        "readonly": False,
        "size": "512 MB",
        "path": nengo.utils.paths.decoder_cache_dir,
    },
    "progress": {"progress_bar": "auto"},
    "exceptions": {"simplified": True},
    "nengo.Simulator": {"fail_fast": False},
}

# The RC files in the order in which they will be read.
RC_FILES = [
    nengo.utils.paths.nengorc["system"],
    nengo.utils.paths.nengorc["user"],
    nengo.utils.paths.nengorc["project"],
]


class _RC(configparser.SafeConfigParser):
    """Allows reading from and writing to Nengo RC settings.

    This object is a :class:`configparser.ConfigParser`, which means that
    values can be accessed and manipulated with ``get`` and ``set``:

    .. testcode::

       oldsize = nengo.rc.get("decoder_cache", "size")
       nengo.rc.set("decoder_cache", "size", "2 GB")

    ``get`` and ``set`` return and expect strings. There are also special
    getter methods for booleans, ints, and floats:

    .. testcode::

       simple = nengo.rc.getboolean("exceptions", "simplified")

    In addition to the normal :class:`configparser.ConfigParser` methods,
    this object also has a ``reload_rc`` method to reset ``nengo.rc``
    to default settings:

    .. testcode::

       nengo.rc.reload_rc()  # Reads defaults from configuration files
       nengo.rc.reload_rc(filenames=[])  # Ignores configuration files

    """

    def __init__(self):
        # configparser uses old-style classes without 'super' support
        configparser.SafeConfigParser.__init__(self)
        self.reload_rc()

    @property
    def float_dtype(self):
        bits = self.get("precision", "bits")
        return np.dtype("float%s" % bits)

    @property
    def int_dtype(self):
        bits = self.get("precision", "bits")
        return np.dtype("int%s" % bits)

    def _clear(self):
        self.remove_section(configparser.DEFAULTSECT)
        for s in self.sections():
            self.remove_section(s)

    def _init_defaults(self):
        for section, settings in RC_DEFAULTS.items():
            self.add_section(section)
            for k, v in settings.items():
                self.set(section, k, str(v))

    def read_file(self, fp, filename=None):
        if filename is None:
            if hasattr(fp, "name"):
                filename = fp.name
            else:
                filename = "<???>"
        logger.debug("Reading configuration from {}".format(filename))
        try:
            return configparser.SafeConfigParser.read_file(self, fp, filename)
        except AttributeError:
            # pylint: disable=deprecated-method
            return configparser.SafeConfigParser.readfp(self, fp, filename)

    def read(self, filenames):
        logger.debug("Reading configuration files {}".format(filenames))
        return configparser.SafeConfigParser.read(self, filenames)

    def reload_rc(self, filenames=None):
        """Resets the currently loaded RC settings and loads new RC files.

        Parameters
        ----------
        filenames: iterable object
            Filenames of RC files to load.
        """
        if filenames is None:
            filenames = RC_FILES

        self._clear()
        self._init_defaults()
        self.read(filenames)


rc = _RC()