Defining Options

Overview

New options are added to a block using the add_option method. All options have an optional help string which can then be used to automatically generate documentation for the configuration.

>>> cfg.add_option("ExampleOption", help="An example option")

Once an option has been added, it can be accessed using normal dictionary access semantics

>>> cfg["ExampleOption"] = "Hello World!"
>>> cfg["ExampleOption"]
"Hello World!"

Attempting to set a key that has not been defined will result in an error. Deleting the key will not remove the option, but only any value that has been set. Attempting to access a key whose value has not been set or has been removed in this way results in a KeyError.

>>> del cfg["ExampleOption"]
>>> cfg["ExampleOption"]
KeyError: 'ExampleOption'

Instead a default value can be provided for the option which will be returned if no value has been set.

>>> cfg.add_option("DefaultOption", default="Hello World!", help="An example option with a default")
>>> cfg["DefaultOption"]
"Hello World!"

Deleting the value will remove the set value, but not the default.

>>> cfg["DefaultOption"] = "Goodbye"
>>> cfg["DefaultOption"]
"Goodbye"
>>> del cfg["DefaultOption"]
>>> cfg["DefaultOption"]
"Hello World!"

A type parameter can also be provided which should be a function which will be used to convert any set values (including the default).

>>> cfg.add_option("IntegerOption", type=int, help="An integer option")
>>> cfg["IntegerOption"] = "55"
>>> cfg["IntegerOption"]
55

Note that the default value will not be passed through the type function.

Restricting the possible set values

It is possible to restrict the value that the user is allowed to set. The simplest way to do this is to provide a list of values to the choices parameter. Attempting to set a value not in this list results in a ValueError.

>>> cfg.add_option("RestrictedOption", type=int, choices=(2, 3, 5), help="An option with a restricted number of values")
>>> cfg["RestrictedOption"] = 12
ValueError: Set value '12' is not allowed (permitted options are (2, 3, 5))

Alternatively a custom validator function may be added which will be run on any value that is set. To signal that the set value is invalid the function may either return False or raise an exception.

>>> cfg.add_option("ValidatedOption", type=int, validator=lambda x: x < 10, help="An option with a custom validator")
>>> cfg["ValidatedOption"] = 12
ValueError: 12

Where possible setting choices should be preferred over a custom validator function as more information can be provided to the user. Default values will not be validated.

Deferred values

Sometimes a value may be determined from other values within the configuration. In these cases instead of providing a value directly a function can be provided. Then, whenever that value is accessed the function will be called instead. The argument to the function will be the block on which the option is declared.

>>> from operator import itemgetter
>>> cfg.add_option("Option", default="Default")
>>> cfg.add_option("Deferred")
>>> cfg["Deferred"] = itemgetter("Option")
>>> cfg["Deferred"]
"Default"
>>> cfg["Option"] = "SetValue"
>>> cfg["Deferred"]
"SetValue"

A default value can also be set to a function. The type function and validators (if any) will be applied to the result of the function call, even for the default case (unlike where a simple value is set).

>>> from operator import itemgetter
>>> cfg.add_option("Default", default=itemgetter("Other"), type=int, validator=lambda x: x < 20)
>>> cfg.add_option("Other")
>>> cfg["Other"] = "5"
>>> cfg["Default"]
5
>>> cfg["Other"] = 100
>>> cfg["Default"]
ValueError: 100

Given that referencing another option is a fairly common requirement there is a shortcut for this. If an option value is a string beginning with an ‘@’ then it will be used to construct an OptionReference. This works both for default and set values. A normal string beginning with an ‘@’ can be provided by escaping it.

>>> cfg.add_option("Reference", default="@Other")
>>> cfg.add_option("Other")
>>> cfg["Other"] = 10
>>> cfg["Reference"]
10
>>> cfg["Reference"] = "\@Other"
>>> cfg["Reference"]
"\@Other"

Required options

By default options must be set unless they have a default value different to None. If a default value of None should be allowed the the required parameter should be set to False.

>>> cfg.add_option("Optional", required=False, help="An optional option")

Enum Options

Often an option should be picking from a set of allowed options which are defined by an enum. A shortcut for this is the add_enum_option method. This sets the type and choices fields for the option so it accepts any of the string names.

import enum

class TestEnum(enum.Enum):
    FirstValue = enum.auto()
    SecondValue = enum.auto()
    ThirdValue = enum.auto()

cfg.add_enum_option("Option", TestEnum, default=TestEnum.FirstValue, help="An option taking one of the enum values")