1. b.93z.org
  2. Notes

settingslib: easy to use settings wrapper for Python

Who needs settings in .py files? Can’t we just use .ini files or YAML for configuration? No, sometimes we can’t.

Motivation

Once I was working on a project that used Werkzeug, Jinja2, SQLAlchemy and other libraries that, being put together, may “replace Django”. There were also numerous infrastructure-related scripts on top of that, and all these things used ad-hoc settings loading in style of Django (.py file with CONSTANTS).

I decided to DRYify code a bit, so I created settingslib.

Example

Let’s create simple command-line utility (example-util.py), that will take string references to config files as arguments, read them, merge, and then pretty-print result as JSON:

#!/usr/bin/env python

import sys
import json

import settingslib


def main(refs):
    conf = reduce(
        lambda acc, v: acc + settingslib.Settings(v),
        refs,
        settingslib.Settings()
    )
    print json.dumps(conf, indent=4)


if __name__ == '__main__':
    main(sys.argv[1:])

Now let’s create three config files. cfg1.py:

$ echo 'A = 2
> B = 3' > cfg1.py

$ cat cfg1.py
A = 2
B = 3

cfg2.py:

$ echo 'A = 1' > cfg2.py

$ cat cfg2.py
A = 1

cfg3.py:

$ echo 'B = 2' > cfg3.py

$ cat cfg3.py
B = 2

For us, ./example-util.py is more convenient than python example-util.py:

$ chmod +x example-util.py

What do we expect to get from ./example-util.py ./cfg1.py ./cfg2.py ./cfg3.py command (note dots and slashes, they are necessary)? Common sense tells: our util will initialize itself with empty Settings class, then read first file, execute initial + settings_from_cfg1, and assign result to initial. Same with cfg2.py and cfg3.py. Then it will use built-in json module to serialize result into JSON, and write it to stdout. Let’s see if it’s true:

$ ./example-util.py ./cfg1.py ./cfg2.py ./cfg3.py
{
    "A": 1,
    "B": 2
}

So, at first we have:

>>> initial
{}

Then, after we add first config:

>>> initial
{'A': 2, 'B': 3}

After we add second:

>>> initial
{'A': 1, 'B': 3}

Finally, we have:

>>> initial
{'A': 1, 'B': 2}

All these steps are hidden within single call to reduce. If we’ll omit each rightmost config file, we’ll get confirmation:

$ ./example-util.py ./cfg1.py ./cfg2.py ./cfg3.py
{
    "A": 1,
    "B": 2
}
$ ./example-util.py ./cfg1.py ./cfg2.py
{
    "A": 1,
    "B": 3
}
$ ./example-util.py ./cfg1.py
{
    "A": 2,
    "B": 3
}
$ ./example-util.py
{}

Now, let’s check if our utility can accept, say, dotted path:

$ ./example-util.py django
{
    "VERSION": [
        1,
        3,
        1,
        "final",
        0
    ]
}

For more usage examples, and Settings class reference, see README.rst.

Quality

settingslib has test suite (powered by pytest. To run it, you’ll need tox and all versions of Python supported by library (≥ 2.5).

Library is considered as stable. It is used in production in various places, in different companies. I do not plan to add or remove features.

© 2008–2017 93z.org