/configy

Simple Configuration manager, plays well with testing

Primary LanguagePythonMIT LicenseMIT

configy

Simple Configuration manager, plays well with testing.

https://travis-ci.com/grigi/configy.svg?branch=master https://coveralls.io/repos/grigi/configy/badge.svg?branch=master&service=github

Basic Usage

Install from pypi:

pip install configy

Specify the configuration directives as early in execution as possible:

import configy

try:
    # Every option is optional, fill in as makes sense.
    configy.load_config(
        conf='the_configuration.yaml',  # The default config file if not specified as an ENV var
        env='CONFIGY_FILE',             # The ENV var to look for a config file
        defaults='defaults.yaml',       # The defaults that is always loaded.
        data={'manual': 'defaults'},    # Manually provided defaults loaded
        case_sensitive=True             # Case Sensitive by default
    )
except configy.ConfigyError as e:
    # Report config load error to user

Given a sample YAML config file of:

Something:
  value: The Value
  number: 42
  bool1: 1
  bool2: FALSE
  bool3: y

You then use it so:

>>> from configy import config
>>> config.Something.value
'The Value'

If you try to access any configuration value that isn't defined you will get an exception:

>>> config.Something.other
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'other'

The config object is just a dictionary, so you can use it as a regular dictionary as well:

>>> config['Something']['value']
'The Value'
>>> config.Something.get('other', 'default value')
'default value'

Environment variable substitution

You can request Environment variable substitution by using ${ENVVAR} in the yaml document:

Something:
  full_value: ${ENV_FULL}
  part_value: http://${ENV_PART}/something

If the envvar isn't defined, it wills raise ConfigyError.

Helper functions

Since you can't guarantee the type of a value in the configuration files (YAML treats everything as text), you need to do type conversion manually.

For ints and floats it is easy:

>>> int(config.Something.number)
42
>> float(config.Something.number)
42.0

For booleans it is a bit more tricky, as a boolean can be represented by many different notations. You also don't have complete control over the notation used. For this we provide a to_bool() helper function.

It treats case-insensitively

True
'y', 'yes', '1', 't','true'
False
'n', 'no', '0', 'f', 'false'

Anything else will resort to the provided default (which defaults to False)

>>> from configy import config, to_bool
>>> to_bool(config.Something.bool1)
True
>>> to_bool(config.Something.bool2)
False
>>> to_bool(config.Something.bool1)
True
>>> to_bool(config.Something.number)
False
>>> to_bool(config.Something.number, True)
True
>>> to_bool(config.Something.number, None)
None

How to overload settings for testing

During testing, one often wants to override some configuration to test something specific. Configy supports this use case.

from configy import config, testconfig

@testconfig.override_config({
    'Something': {
        'other': 'I now exist',
    },
    'Extra': 'defined',
})
def test_override():
    # Existing values still work as per usual
    assert config.Something.value == 'The Value'
    # New values
    assert config.Something.other == 'I now exist'
    assert config.Extra == 'defined'

One can also define configuration to be used:

@testconfig.load_config(
    conf='test_config.yaml'
)
def test_load_config():
    assert config.testvalue == 'test result'

You can also define the WHOLE configuration that is loaded for that test:

@testconfig.load_config(data={
    'testvalue': 'test result',
})
def test_load_config_data():
    assert config.testvalue == 'test result'

All the testing decorators will work on method, class and function level.