Who needs settings in .py
files? Can’t we just use .ini
files or YAML for configuration? No, sometimes we can’t.
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.
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
.
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.
This blog is about things I encounter while doing web and non-web software development.