10. config — A configuration class with Python syntax.

Why

I wrote this simple class because I wanted to use Python expressions in my configuration files. This is much more fun than using .INI style config files. While there are some other Python config modules available on the web, I couldn’t find one that suited my needs and my taste: either they are intended for more complex configuration needs than mine, or they do not work with the simple Python syntax I expected.

What

Our Config is Python dictionary which can hold any values. The keys however should be strings and be valid Python identifiers not starting with an underscore. Values are usually simple types, but can also be containers likes lists and dicts. A special dict subclass Section can be used to define sections inside the Config. Again, all the keys in a Section must be valid Python identifiers not starting with a an underscore. Sections provide a shortcut key access: instead of cfg['section']['key'] one can simply use the combined section/key: cfg[``section/key]. This works for getting as well as for setting values. See Examples. (Actually, the shortkey lookup also works for items in a plain dict, except for directly setting values within non-existent dict: the created dict will always be a Section.

Config files

The most important feature of a configuration class is to be able to store the values in a file and load it back from there. To make the file easily readable and editable, Config files use a very Python-like syntax.

10.1. Classes defined in module config

class config.Section[source]

A class for storing a section in a Config.

This only exists to give the sections another class name than plain dict.

class config.Config(data={}, default=None)[source]

A Python-style configuration class.

The configuration can be initialized with a dictionary, or with a variable that can be passed to the read() function. The latter includes the name of a config file, or a multiline string holding the contents of a configuration file.

Parameters:
  • data (dict or multiline string, optional) – Data to initialize the Config. If a dict, all keys should follow the rules for valid config keys formulated above. If a multiline string, it should be an executable Python source text, with the limitations and exceptions outlined in the Notes below.

  • default (Config object, optional) – If provided, this object will be used as default lookup for missing keys.

Notes

The configuration object can be initialized from a dict or from a multiline string. Using a dict is obvious: one only has to obey the restriction that keys should be valid Python variable names.

The format of the multiline config text is described hereafter. This is also the format in which config files are written and can be loaded.

All config lines should have the format: key = value, where key is a string and value is a Python expression The first ‘=’ character on the line is the delimiter between key and value. Blanks around both the key and the value are stripped. The value is then evaluated as a Python expression and stored in a variable with name specified by the key. This variable is available for use in subsequent configuration lines. It is an error to use a variable before it is defined. The key,value pair is also stored in the Config dictionary, unless the key starts with an underscore (‘_’): this provides for local variables.

Lines starting with ‘#’ are comments and are ignored, as are empty and blank lines. Lines ending with ‘’ are continued on the next line. A line starting with ‘[’ starts a new section. A section is nothing more than a Python dictionary inside the Config dictionary. The section name is delimited by ‘[‘and ‘]’. All subsequent lines will be stored in the section dictionary instead of the toplevel dictionary.

All other lines are executed as Python statements. This allows e.g. for importing modules.

Whole dictionaries can be inserted at once in the Config with the update() function.

All defined variables while reading config files remain available for use in the config file statements, even over multiple calls to the read() function. Variables inserted with addSection() will not be available as individual variables though, but can be accessed as self['name'].

As far as the resulting Config contents is concerned, the following are equivalent:

C.update({'key':'value'})
C.read("key='value'\n")

There is an important difference though: the second line will make a variable key (with value ‘value’) available in subsequent Config read() method calls.

Examples

>>> C = Config('''# A simple config example
...     aa = 'bb'
...     bb = aa
...     [cc]
...     aa = 'aa'    # yes ! comments are allowed)
...     _n = 3       # local: will get stripped
...     rng = list(range(_n))
...     ''')
>>> C
Config({'aa': 'bb', 'bb': 'bb', 'cc': Section({'aa': 'aa', 'rng': [0, 1, 2]})})
>>> C['aa']
'bb'
>>> C['cc']
Section({'aa': 'aa', 'rng': [0, 1, 2]})
>>> C['cc/aa']
'aa'
>>> C.keys()
['aa', 'bb', 'cc', 'cc/aa', 'cc/rng']

Create a new Config with default lookup in C

>>> D = Config(default=C)
>>> D
Config({})
>>> D['aa']       # Get from C
'bb'
>>> D['cc']       # Get from C
Section({'aa': 'aa', 'rng': [0, 1, 2]})
>>> D['cc/aa']            # Get from C
'aa'
>>> D.get('cc/aa','zorro')      # but get method does not cascade!
'zorro'
>>> D.keys()
[]

Setting values in D will store them in D while C remains unchanged.

>>> D['aa'] = 'wel'
>>> D['dd'] = 'hoe'
>>> D['cc/aa'] = 'ziedewel'
>>> D
Config({'aa': 'wel', 'dd': 'hoe', 'cc': Section({'aa': 'ziedewel'})})
>>> C
Config({'aa': 'bb', 'bb': 'bb', 'cc': Section({'aa': 'aa', 'rng': [0, 1, 2]})})
>>> print(C)
aa = 'bb'
bb = 'bb'

[cc]
aa = 'aa'
rng = [0, 1, 2]

>>> D['cc/aa']
'ziedewel'
>>> D['cc']
Section({'aa': 'ziedewel'})
>>> D['cc/rng']
[0, 1, 2]
>>> 'bb' in D
False
>>> 'cc/aa' in D
True
>>> 'cc/ee' in D
False
>>> D['cc/bb'] = 'ok'
>>> D.keys()
['aa', 'dd', 'cc', 'cc/aa', 'cc/bb']
>>> del D['aa']
>>> del D['cc/aa']
>>> D.keys()
['dd', 'cc', 'cc/bb']
>>> D.sections()
['cc']
>>> del D['cc']
>>> D.keys()
['dd']
update(data={}, name=None)[source]

Add a dictionary to the Config object.

The data, if specified, should be a valid Python dict. If no name is specified, the data are added to the top dictionary and will become attributes. If a name is specified, the data are added to the named attribute, which should be a dictionary. If the name does not specify a dictionary, an empty one is created, deleting the existing attribute.

If a name is specified, but no data, the effect is to add a new empty dictionary (section) with that name.

load(filename, debug=False)[source]

Read a configuration from a file in Config format.

Parameters:

filename (path_like) – The path of a text file in Config format.

Returns:

Config – Returns the Config self, updated with the settings read from the specified file.

fromtext(txt, filename=None, debug=False)[source]

Update a Config from a text in Config file format.

Parameters:
  • txt (str|iterable(str)) – A multiline string or an iterable of strings. This could be any object that allows iteratation over its lines, such as an open text file. If it is a string, it will first be splitted on newlines.

  • filename (str) – The filename to be shown in read error messages.

write(filename, header='# Config written by pyFormex    -*- PYTHON -*-\n\n', trailer='\n# End of config\n')[source]

Write the config to the given file

The configuration data will be written to the file with the given name in a text format that is both readable by humans and by the Config.read() method.

The header and trailer arguments are strings that will be added at the start and end of the outputfile. Make sure they are valid Python statements (or comments) and that they contain the needed line separators, if you want to be able to read it back.

keys(descend=True)[source]

Return the keys in the config.

Parameters:

descend (bool, optional) – If True (default) also reports the keys in Section objects with the combined section/item keys. This is the proper use for a Config with optional sections. If False, no keys in sections are reported, only the section names.

sections()[source]

Return the sections