# nobodd.config

This module contains the classes and functions used to configure the main nobodd application. These are not likely to be of much use to other applications, but are documented here just in case.

## ConfigArgumentParser

class nobodd.config.ConfigArgumentParser(*args, template=None, **kwargs)

  A variant of `ArgumentParser` that links arguments to specified keys in a `ConfigParser` instance.

  Typical usage is to construct an instance of `ConfigArgumentParser`, define the parameters and parameter groups on it, associating them with configuration section and key names as appropriate, then call `read_configs()` to parse a set of configuration files. These will be checked against the (optional) template configuration passed to the initializer, which defines the set of valid sections and keys expected.

  The resulting `ConfigParser` forms the “base” configuration, prior to argument parsing. This can be optionally manipulated, before passing it to `set_defaults_from()` to set the argument defaults. At this point, `parse_args()` may be called to parse the command line arguments, knowing that defaults in the help will be drawn from the “base” configuration.

  The resulting `Namespace` object is the application’s runtime configuration. For example:

      >>> from pathlib import Path
      >>> from nobodd.config import *
      >>> parser = ConfigArgumentParser()
      >>> tftp = parser.add_argument_group('tftp', section='tftp')
      >>> tftp.add_argument('--listen', type=str, key='listen',
      ... help="the address on which to listen for connections "
      ... "(default: %(default)s)")
      >>> Path('defaults.conf').write_text('''
      ... [tftp]
      ... listen = 127.0.0.1
      ... ''')
      >>> defaults = parser.read_configs(['defaults.conf'])
      >>> parser.set_defaults_from(defaults)
      >>> parser.get_default('listen')
      '127.0.0.1'
      >>> config = parser.parse_args(['--listen', '0.0.0.0'])
      >>> config.listen
      '0.0.0.0'

  Note that, after the call to `set_defaults_from()`, the parser’s idea of the defaults has been drawn from the file-based configuration (and thus will be reflected in printed `--help`), but this is still overridden by the arguments passed to the command line.

  add_argument(*args, section=None, key=None, **kwargs)

    Adds section and key parameters. These link the new argument to the specified configuration entry.

    The default for the argument can be specified directly as usual, or can be read from the configuration (see `read_configs()` and `set_defaults_from()`). When arguments are parsed, the value assigned to this argument will be copied to the associated configuration entry.

  add_argument_group(title=None, description=None, section=None)

    Adds a new argument group object and returns it.

    The new argument group will likewise accept section and key parameters on its `add_argument()` method. The section parameter will default to the value of the section parameter passed to this method (but may be explicitly overridden).

  of_type(type)

    Return a set of (section, key) tuples listing all configuration items which were defined as being of the specified type (with the type keyword passed to `add_argument()`.

  read_configs(paths)

    Constructs a `ConfigParser` instance, and reads the configuration files specified by paths, a list of `Path`-like objects, into it.

    The method will check the configuration for valid section and key names, raising `ValueError` on invalid items. It will also resolve any configuration values that have the type `Path` relative to the path of the configuration file in which they were defined.

    The return value is the configuration parser instance.

  set_defaults_from(config)

    Sets defaults for all arguments from their associated configuration entries in config.

  update_config(config, namespace)

    Copy values from namespace (an `argparse.Namespace`, presumably the result of calling something like `parse_args()`) to config, a `ConfigParser`. Note that namespace values will be converted to `str` implicitly.

## Board

class nobodd.config.Board(serial, image, partition, ip)

  Represents a known board, recording its serial number, the image (filename) that the board should boot, the partition number within the image that contains the boot partition, and the IP address (if any) that the board should have.

  classmethod from_section(config, section)

    Construct a new `Board` from the specified section of the config (a mapping, e.g. a `ConfigParser` section).

  classmethod from_string(s)

    Construct a new `Board` from the string s which is expected to be a comma-separated list of serial number, filename, partition number, and IP address. The last two parts (partition number and IP address) are optional and default to 1 and `None` respectively.

## Conversion functions

nobodd.config.port(s)

  Convert the string s into a port number. The string may either contain an integer representation (in which case the conversion is trivial, or a port name, in which case `socket.getservbyname()` will be used to convert it to a port number (usually via NSS).

nobodd.config.boolean(s)

  Convert the string s to a `bool`. A typical set of case insensitive strings are accepted: “yes”, “y”, “true”, “t”, and “1” are converted to `True`, while “no”, “n”, “false”, “f”, and “0” convert to `False`. Other values will result in `ValueError`.

nobodd.config.size(s)

  Convert the string s, which must contain a number followed by an optional suffix (MB for mega-bytes, GB, for giga-bytes, etc.), and return the absolute integer value (scale the number in the string by the suffix given).

nobodd.config.serial(s)

  Convert the string s, which must contain a number in hexidecimal format to an `int`. If s begins with either “10000000” or “00000000” then these prefixes will be discarded. This is intended to provide a representation of a Raspberry Pi’s serial number that is consistent with that used by the TFTP client in the Pi’s bootloader.

nobodd.config.duration(s)

  Convert the string s to a `timedelta`. The string must consist of white-space and/or comma separated values which are a number followed by a suffix indicating duration. For example:

      >>> duration('1s')
      timedelta(seconds=1)
      >>> duration('5 minutes, 30 seconds')
      timedelta(seconds=330)

  The set of possible durations, and their recognized suffixes is as follows:

  - Microseconds: microseconds, microsecond, microsec, micros, micro, useconds, usecond, usecs, usec, us, µseconds, µsecond, µsecs, µsec, µs

  - Milliseconds: milliseconds, millisecond, millisec, millis, milli, mseconds, msecond, msecs, msec, ms

  - Seconds: seconds, second, secs, sec, s

  - Minutes: minutes, minute, mins, min, mi, m

  - Hours: hours, hour, hrs, hr, h

  If conversion fails, `ValueError` is raised.