Source code for confyg

"""

Welcome to Confyg's documentation. Confyg is a class based
configuration module. By defining configuration classes, Confyg
will load the values from their sources.

A source can be anything like a file or environment variables.
The advantage to using Confyg is that you will have a unifying
syntax to load configurations, independent of the data source.

Tutorial
========

Here is a simple example that uses environment variables for a
web server.

`server_config.py`

.. code-block:: python

    from confyg import OsConfyg


    class Config(OsConfyg):
        SECRET_KEY = 'key'
        LOGGER_NAME = 'logger_name'
        DEBUG = 'debug'

    Config.load()
    assert Config.SECRET_KEY == '5b6ba13f79129a74a3e819b78e36b922'
    assert Config.LOGGER_NAME == 'web_server'
    assert Config.DEBUG == 'True'

.. code-block:: bash

    export SECRET_KEY='5b6ba13f79129a74a3e819b78e36b922'
    export LOGGER_NAME='web_server'
    export DEBUG='True'
    python server_config.py

We changed our mind and want to use a configuration file. The
file needs to be in some format. For simplicity we choose JSON.
The code doesn't change much:

.. code-block:: python

    from confyg import JsonConfyg


    class Config(JsonConfyg):

        __source__ = 'config.json'

        SECRET_KEY = 'key'
        LOGGER_NAME = 'logger-name'
        DEBUG = 'debug'

    Config.load()
    assert Config.SECRET_KEY == '5b6ba13f79129a74a3e819b78e36b922'
    assert Config.LOGGER_NAME == 'web_server'
    assert Config.DEBUG == True

`config.json`

.. code-block:: javascript

    {
        "key": "5b6ba13f79129a74a3e819b78e36b922",
        "logger-name": "web_server",
        "debug": true
    }

There is one thing to notice here. With JSON, `DEBUG`
has a different type. This is because the operating system
environment only supports strings. Different sources support
different types. We think this can lead to bugs, and hope
to fix it.

Transformations
===============

Transformations change the keys used to get values from the `__source__`.
They are specified in the `__transformation__` class attribute. They must
be wrapped around a `transformation` function call.

.. code-block:: python

    from confyg import JsonConfyg
    from confyg.transformations import upper_case, composite, transformation, \
        hyphens_to_underscore


    class Config(JsonConfyg):

        __source__ = 'config.json'

        SECRET_KEY = 'key'
        LOGGER_NAME = 'logger-name'
        DEBUG = 'debug'

    Config.load()

This would work as before but imagine we changed our mind from JSON configuration
file to a OS environment one. We could easily replace the inheriting `JsonConfyg`
class to a `OSConfyg` one but things like 'logger-name' are not valid valid
environment variable identifiers. All we have to do is tweak the keys that we use.

.. code-block:: python

    from confyg import OSConfyg
    from confyg.transformations import upper_case, composite, transformation, \
        hyphens_to_underscore


    class Config(OSConfyg):

        __source__ = 'config.json'

        __transformation__ = transformation(
            composite(
                upper_case,
                hyphens_to_underscore
            )
        )

        SECRET_KEY = 'key'
        LOGGER_NAME = 'logger-name'
        DEBUG = 'debug'

    Config.load()

In here we are taking two transformations and mixing them together.
The first `upper_case` will take a key and turn it into a all upper
cased string.

.. code-block:: python

    assert upper_case('key') == 'KEY'
    assert upper_case('logger-name') == 'LOGGER-NAME'
    assert upper_case('debug') == 'DEBUG'

The second replaces all hyphens to underscores:

.. code-block:: python

    assert hyphens_to_underscore('key') == 'key'
    assert hyphens_to_underscore('logger-name') == 'logger_name'
    assert hyphens_to_underscore('debug') == 'debug'

When mashed together we get:

.. code-block:: python

    assert composite(upper_case, hyphens_to_underscore)('key') == 'KEY'
    assert composite(upper_case, hyphens_to_underscore)('logger-name') == 'LOGGER_NAME'
    assert composite(upper_case, hyphens_to_underscore)('debug') == 'DEBUG'

Just like that we keep our nice looking keys in our source code while we keep
them in whatever style the `__source__` prefers.

Interface
=========

Every Confyg has a `__source__`. The source is used
to find the configuration values.

The `__config_store__` holds the serialized key values
after being loaded from the `__source__`.

New Confyg subclasses should only have to override the `load_store` method.
This method should return a dictionary like object. If so
the default `get` and `set` methods should work fine.

We encourage you to read the source code for the project. It is
tested, small, simple and documented.

You might wonder why we use names like `__source__`,
`__config_store__`, and `__transformation__` instead of regular names.
This is so that they do not clash with the values that we fill in the
classes.

"""
import json
import os

VERSION = '0.2.0.0'


def attributes_values(obj):
    """
    Returns a list of (attribute, value) tuples for obj.

    """
    return list(filter(
        lambda kv: not kv[0].startswith('_'), obj.__dict__.items()
    ))


def confyg_attributes_values(obj):
    """
    Returns a list of (attribute, value) tuples that we
    consider to be keys for a Confyg class. For now
    these are the ones that upper cased.

    """
    return list(filter(lambda av: av[0].isupper(), attributes_values(obj)))


[docs]class Confyg(object): """ The base configuration class implementing the common functionality. Subclassing this class should make it easy to add new configuration sources. """ __source__ = None __config_store__ = None __transformation__ = None @classmethod def load(cls): cls.__config_store__ = cls.load_store() for k, vk in confyg_attributes_values(cls): v = cls.get(vk) cls.set(k, v) @classmethod def load_store(cls): return {} @classmethod def get(cls, key): if cls.__transformation__ is not None: tf = cls.__transformation__ key = tf(key) return cls.__config_store__[ key ] @classmethod def set(cls, key, val): setattr(cls, key, val)
[docs]class JsonConfyg(Confyg): """ JsonConfyg let's you load configuration from a JSON file. The path to the file should be specified in `__source__`. """ @classmethod def load_store(cls): with open(cls.__source__, 'r') as f: return json.load(f)
[docs]class DictConfyg(Confyg): """ The DictConfyg loads configuration from the dictionary set in `__source__`. """ @classmethod def load_store(cls): return cls.__source__
[docs]class OsConfyg(DictConfyg): """ The OSConfyg class loads configuration from environment variables. """ __source__ = os.environ