rjgtoys.config by Example

A simple application

Imagine a simple ‘word translator’ application: it accepts words on the command line, looks up each word in a dictionary and prints the translation, if it has one.

It reads the lookup dictionary from its configuration file.

Here is the code:

"""
examples/translate.py: translate words using a dictionary
"""

import argparse
import os
from typing import Dict

from rjgtoys.config import Config, getConfig

class TranslateConfig(Config):

    words: Dict[str, str]

cfg = getConfig(TranslateConfig)

def main(argv=None):

    p = argparse.ArgumentParser()

    cfg.add_arguments(p, default='translate.yaml', adjacent_to=__file__)
    args, tail = p.parse_known_args(argv)

    for word in tail:
        result = cfg.words.get(word, "I don't know that word")
        print(f"{word}: {result}")


if __name__ == "__main__":
    main()

The class TranslateConfig defines the configuration parameters needed by the application. The base class, Config is based on pydantic.BaseModel, and should use type annotations to make the desired types clear, and to facilitate validation of the input.

The getConfig() call returns an object that retrieves and provides access to an instance of TranslateConfig once a configuration file has been chosen and read.

The cfg.add_arguments() call adds a --config command line option to the application argument parser, which will set the location of the configuration file. The same call also sets the default location for the configuration file, which in this case is translate.yaml, in the same directory as the translate.py script itself.

To retrieve configuration parameters, simply access attributes of the configuration object cfg, as if it were an instance of the specified configuration model, TranslateConfig.

Configuration file

Here is a suitable configuration file:

# examples/english-french.yaml

---
words:
    dog: chien
    cat: chat
    bird: oiseau

If this file were stored in a file called english-french.yaml it would not be found by default, and so to use it, the script should invoked as follows:

$ python translate.py --config english-french.yaml cat dog budgie
cat: chat
dog: chien
budgie: I don't know that word

Including defaults

One obvious way to make this dictionary available as the default would be to copy or rename the file to translate.yaml, but another way would be to make a new translate.yaml that uses the first as defaults:

# examples/translate.yaml: Default configuration file, using specific dictionary
defaults: !include english-french.yaml

The !include tag (a YAML tag provided by rjgtoys.yaml) causes the content of the named file to be inserted in place of the tag, so the above is equivalent to:

defaults:
  words:
     dog: chien
     cat: chat
     bird: oiseau

The configuration parser uses the defaults element of any mapping as a set of initial values into which to merge any other values provided for that mapping.

In this case, there are no more keys, and so the above is equivalent to:

words:
   dog: chien
   cat: chat
   bird: oiseau

Building on defaults

However, the dictionary can also be extended:

defaults: !include english-french.yaml

words:
  snail: escargot

The above is equivalent to:

words:
   dog: chien
   cat: chat
   bird: oiseau
   snail: escargot