Skip to main content

Common config loading for Python and the command line

Project description

confight

PyPI PyPI - Python Version Build Status

One simple way of parsing configs

  • Extensible "Unix-like" conf.d directory
  • Allow for multiple formats (toml, json, yaml, ini)
  • Full unicode support
  • User settings ~/.config support
  • Nice out-of-the-box defaults
  • See examples

confight focuses on making application configuration easy to load, change, extend, generate and audit. It does so by allowing to use separated files for different topics, simplifying changes and new additions, without messing with already existing defaults or deleting or moving protected files.

This is achieved by using at least one config file (/etc/app/config.toml) and an extra directory (/etc/app/conf.d) for extra files.

Those extra files are called droplets which consist in is a small config file that is "dropped" into a conf.d directory where they will be parsed and merged nicely into a single final configuration.

The idea is to "map reduce" configurations, by parsing all files in order, giving more relevance to the latest appearance and then merge them into a single config that holds all the data:

 C₀ -- parse -----|
    C₁ -- parse --|
    C₂ -- parse --|-- merge --> C
       ⋮          |
    Cₙ -- parse --|

The name of those files will determine the order in which they're parsed and the priority their values will have when merging. The last one wins.

This approach is very common in Unix and used in cron (/etc/cron.d), bash profiles (/etc/profile.d), apt (/etc/apt/sources.list.d), systemd and many others. Is specially good for externally managed configs or debian-packaged applications, avoiding clashes between installed files and generated configs, avoiding changes that would stay forever unless manually merged (Yes, I've said 💩MANUALLY💩💩Placing new files in conf.d, application configuration can change be extended and overriden without getting dirty.

Usage

>>> import confight
>>> confight.load_app('myapp')
{
    "section": {
        "key": "value"
    }
}

The previous fragment got all the config files at /etc/myapp/config.toml and within the /etc/myapp/conf.d directory and merged them into a single config.

# /etc/myapp/config.toml    /etc/myapp/conf.d/00_first.json    /etc/myapp/conf.d/99_second.ini
[section]                   {                                  [section]
key = "base config"           "section": {                     key = value
                                 "key": "not this"
                              }
                            }

Default file locations for an application named myapp would be at:

  • /etc/myapp/config.toml
  • /etc/myapp/conf.d/*

User custom configurations would be read (if any) from:

  • ~/.config/myapp/config.toml
  • ~/.config/myapp/conf.d/*

See the examples section for more information on how to use these functions.

Loading

The load family of functions take a list of names, files or directories to easily parse and merge a related set of configurations:

confight.load_app('myapp')
confight.load_user_app('myapp')
confight.load_paths(['/path/to/config', '/path/to/dir'])
confight.load(['/path/to/config.toml', '/path/to/dir/droplet.toml'])

Each function offers different parameters to improve the ease of use.

The extension of the configuration file can be given with the extension parameter. For instance, load_app('myapp', extension='json') would look for the /etc/myapp/config.json file.

Parsing

Given a path to an existing configuration file, it will be loaded in memory using basic types (string, int, float, list, dict).

The given file can be in one of the allowed formats. For a complete list see the confight.FORMATS list.

confight.parse('/path/to/config', format='toml')

When no format is given, it tries to guess by looking at file extensions:

confight.parse('/path/to/config.json')  # will gess json format

You can see the list of all available extensions at confight.FORMAT_EXTENSIONS.

A custom parsing can be provided by passing a parser function to the load family of functions, matching the signature:

def parser(path, format=None)

The function takes a filesystem path and a format and the result should be a single dictionary with all the loaded data. When format is None the parser is expected to guess it.

Merging

Given a list of parsed configs in order, merge them into a single one. For values that appears several times, the last one wins.

Sections and subsections are recursively merged, keeping all keys along the way and overriding the ones in more than one file with the latest appearance.

A custom merging can be provided by passing a merger function to the load family of functions, matching the signature:

def merger(configs)

The function takes a list of dictionaries containing the parsed configuration in ascending order of priority. It should return a single dictionary with all the configuration.

Finding configs

The default behaviour is that all files at the conf.d directory will be opened, in lexicographical order, and parsed.

A custom config locator can be provided by passing a finder function to the load family of functions, matching the signature:

def finder(path)

The function takes a filesystem path (a conf.d directory supposedly) and returns a list of paths to config files in the desired order of parsing and merging, this is from less to more priority for their values.

Examples

Load application config from the default locations by using the load_app function which will look by default at the /etc/myapp/config.toml and configuration directory at /etc/myapp/conf.d:

# /etc/myapp/config.toml    # /etc/myapp/conf.d/production.toml
user = myapp                password = aX80@klj
password = guest
>>> confight.load_app('myapp')
{
  "user": "myapp",
  "password": "aX80@klj"
}

Allow the user to override the default value when wanting to use a different configuration. When None is given, the default is used:

import argparse
import confight

parser = argparse.ArgumentParser()
parser.add_argument('--config', default=None)
parser.add_argument('--config-dir', default=None)
args = parser.parse_args()

config = confight.load_app('myapp',
                           file_path=args.config,
                           dir_path=args.config_dir)

If the application supports user configuration the function load_user_app might come handy as it will first load the regular app config and then the one defined in the user directory ~/.config/myapp/config.toml and ~/.config/myapp/conf.d/*:

# /etc/myapp/config.toml      # ~/.config/myapp/conf.d/mysettings.toml
url = http://teg.avature.net  password = Avature123!
>>> confight.load_user_app('myapp')
{
  "url": "http://teg.avature.net",
  "password": "Avature123!"
}

To ignore config file extensions, set a format and all files will be parsed using such:

# /etc/myapp/config.toml      # /etc/myapp/config.d/extra
name = test                   name = erebus
>>> confight.load_app('myapp', format='toml')
{
    "name": "erebus"
}

To load configs from a dev or debug location use the prefix option. This will change the base to calculate default paths.

# Loads from ./configs/config.toml and ./configs/config.d/*
>>> confight.load_app('myapp', prefix='./configs')

The user_prefix option can be used altogether for user config files:

# Loads from regular places and ./user/config.toml and ./user/config.d/*
>>> confight.load_user_app('myapp', user_prefix='./user')

Added in version 1.0

Command line

confight allows to inspect configuration from the command line.

By using the confight command it would load the myapp configuration from it's default places and display the output in toml format:

confight show myapp

This allows to preview the resulting config for an application after all merges have been resolved. It can come handy when figuring out what the application has loaded or to debug complex config scenarios.

By passing the --verbose INFO interesting data such as all visited files is listed.

Added in version 0.3

Command line options

usage: confight [-h] [--version] [-v {DEBUG,INFO,WARNING,ERROR,CRITICAL}]
                {show} ...

One simple way of parsing configs

positional arguments:
{show}

optional arguments:
-h, --help            show this help message and exit
--version             show program's version number and exit
-v {DEBUG,INFO,WARNING,ERROR,CRITICAL}, --verbose {DEBUG,INFO,WARNING,ERROR,CRITICAL}
                        Logging level default: ERROR ['DEBUG', 'INFO',
                        'WARNING', 'ERROR', 'CRITICAL']

Installation

Install it via pip using:

pip install confight

Also with yaml support:

pip install confight[yaml]

Development

Run application tests

tox

Install the application and run tests in development:

pip install -e .
python -m pytest

Changelog

  • 1.2 (2019-02-14)

    • [3c266c8d] Force all loaded files to have the same extension
  • 1.1.1 (2019-01-31)

    [ javier.lasheras ]

    • [a1646871] OrderedDict for yaml too
  • 1.1 (2019-01-29)

    • [4a5920af] Adds pypi version badge to README
    • [59c47a5e] Drops support for Python 3.3 and Python 3.4
    • [dfa9c436] Adds support for Python 3.7
    • [6979074d] Fix manpage generation
    • [8f6b58f5] Create a parser with ExtendedInterpolation
    • [7d74246d] Avoid DeprecationWarnings
    • [633b1571] Ordered dicts everywhere
  • 1.0 (2018-06-26)

    • [736a6493] Adds prefix and user_prefix options
    • [023158e5] Adds --prefix and --user-prefix cli options
    • [f395fc44] Adapt tests to run in python 3.3 and 3.4
    • [a144dab1] Update package metadata
  • 0.3 (2018-06-14)

    • [a7b46ef1] Adds travis config file
    • [5f625da9] Add tox-travis integration
    • [1b678173] Adds confight command line tool
    • [691e042a] Adds cli unit tests
  • 0.2.2 (2018-04-13)

    • [3322a7a4] Allow custom file extensions when format is defined
  • 0.2.1 (2018-04-09)

    • [93cd8a1c] Update README
  • 0.2 (2018-04-04)

    • [63d55fa8] Add Yaml support
  • 0.1.1 (2018-04-03)

    • [80087037] Allows to pass extra paths in load functions
  • 0.1.0 (2018-03-27)

    • [23927421] Reorganize pretty functions and find behaviour
    • [fade6dd0] Adds debian packaging
    • [c818857a] Update README
  • 0.0.1 (2018-03-27)

    • Initial release.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

confight-1.2.tar.gz (9.7 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

confight-1.2-py3-none-any.whl (13.9 kB view details)

Uploaded Python 3

confight-1.2-py2-none-any.whl (8.8 kB view details)

Uploaded Python 2

File details

Details for the file confight-1.2.tar.gz.

File metadata

  • Download URL: confight-1.2.tar.gz
  • Upload date:
  • Size: 9.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.11.0 pkginfo/1.4.2 requests/2.19.1 setuptools/40.2.0 requests-toolbelt/0.8.0 tqdm/4.23.4 CPython/2.7.12

File hashes

Hashes for confight-1.2.tar.gz
Algorithm Hash digest
SHA256 d2a1370f995e0de6ca8326171f0d4188e4733a24cd2c29caec8ade66ed0af31f
MD5 19e9b90e26e0731c008934a739039882
BLAKE2b-256 d0845c0f0ce71d7000de76ad7ffb56e30a7552bd152914518360cca09dd6709a

See more details on using hashes here.

File details

Details for the file confight-1.2-py3-none-any.whl.

File metadata

  • Download URL: confight-1.2-py3-none-any.whl
  • Upload date:
  • Size: 13.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.11.0 pkginfo/1.4.2 requests/2.19.1 setuptools/40.2.0 requests-toolbelt/0.8.0 tqdm/4.23.4 CPython/2.7.12

File hashes

Hashes for confight-1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 55614507641c5987c43acabaeb322bae3438dd22d668e5e6f3da9cfdbb846b91
MD5 0df8d292ea2c3c792b0cea4d9adda4f8
BLAKE2b-256 09f7bb8f735e36e7537478b7fb16e92acd17cf6dcc62f8579f30d6adce986855

See more details on using hashes here.

File details

Details for the file confight-1.2-py2-none-any.whl.

File metadata

  • Download URL: confight-1.2-py2-none-any.whl
  • Upload date:
  • Size: 8.8 kB
  • Tags: Python 2
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.11.0 pkginfo/1.4.2 requests/2.19.1 setuptools/40.2.0 requests-toolbelt/0.8.0 tqdm/4.23.4 CPython/2.7.12

File hashes

Hashes for confight-1.2-py2-none-any.whl
Algorithm Hash digest
SHA256 bca3d73e48d9744c16d323c68fc6dae5e8d11b63d95cdb7c02c9a710665d835e
MD5 e427b770c8307d71e73af7da25c663b9
BLAKE2b-256 28f239ffc751e121d1c26cce5acab86b1bd12d273a72f454c68fc43555e75727

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page