Configuration Management: dynaconf

Features

  • Inspired by the 12-factor application guide

  • Settings management (default values, validation, parsing, templating)

  • Protection of sensitive information (passwords/tokens)

  • Multiple file formats toml|yaml|json|ini|py and also customizable loaders.

  • Full support for environment variables to override existing settings (dotenv support included).

  • Optional layered system for multi environments [default, development, testing, production] (also called multi profiles)

  • Built-in support for Hashicorp Vault and Redis as settings and secrets storage.

  • Built-in extensions for Django and Flask web frameworks.

  • CLI for common operations such as init, list, write, validate, export.

For more information visit https://www.dynaconf.com/

Installation

$ poetry add dynaconf

Initialization

$ mkdir configs
$ dynaconf init -p configs -f yaml
Dynaconf initialization

This command creates the following files:

.
├── configs/
│   ├── .gitignore
│   ├── .secrets.toml # Sensitive data like passwords and tokens (optional)
│   └── settings.toml # Application setttings (optional)
└── config.py         # Where you import your settings object (required)

Fix the paths for your settings files in config.py

settings_files=["configs/settings.yaml", "configs/.secrets.yaml"]

Basic Usage

Let us create a broken calculator to see how dynaconf works.

# python_lifecycle_training/calculator/broken.py
import fire
from loguru import logger

from config import settings
from python_lifecycle_training.calculator.complex import Calculator


class BrokenCalculator(Calculator):
    offset = settings.offset
    logger.info(f"Offset: {offset}")

    @classmethod
    def add(cls, a, b):
        return super(BrokenCalculator, cls).add(a, b) - cls.offset

    @classmethod
    def sub(cls, a, b):
        return super(BrokenCalculator, cls).sub(a, b) - cls.offset

    @classmethod
    def mul(cls, a, b):
        return super(BrokenCalculator, cls).mul(a, b) - cls.offset

    @classmethod
    def div(cls, a, b):
        return super(BrokenCalculator, cls).div(a, b) - cls.offset


def main():
    fire.Fire(BrokenCalculator)

Add an offset in settings.yaml file:

offset: 2

Add an entry point in pyproject.toml

calc-broken = "python_lifecycle_training.calculator.broken:main"

Install your package

$ poetry install

Run the command

$ calc-broken add 2 2
Broken Calculator

Environment Variables

Dynaconf prioritizes environment variables over files as the best recommendation to keep your settings. You can override any setting key by exporting an environment variable prefixed by DYNACONF_ (or by the custom prefix)

$ export DYNACONF_OFFSET=1
$ calc-broken add 2 2
Broken Calculator with offset from environment

Note

To unset the variable run unset DYNACONF_OFFSET

You can customize the prefix for your env vars by changing it in config.py

Advanced Features

Switch Work Environment

Enable environment switching

settings = Dynaconf(
    envvar_prefix="DYNACONF",
    settings_files=["configs/settings.yaml", "configs/.secrets.yaml"],
    environments=True,
)

Edit settings.yaml

default:
  name: ""
  offset: 1

development:
  name: developer

production:
  offset: 0
  name: admin

Log the name of the user running the broken calculator

class BrokenCalculator(Calculator):
    offset = settings.offset
    logger.info(f"Offset: {offset}")
    logger.info(f"Set by: {settings.name}")

Run command

$ calc-broken add 2 2
Switching Environment

Dynaconf sets the default environment to development.

The subsequent workspace inherits the configurations of the upper workspace unless replaced. Thus, we could access the offset in development even though we didn’t specify it explicitly.

Switch Environment using Environment Variable

$ export ENV_FOR_DYNACONF=production
$ calc-broken add 2 2
Broken Calculator

Note

Unset ENV_FOR_DYNACONF using unset ENV_FOR_DYNACONF

Switch Environment using CLI

Set environment to development in your package __init__.py file

ENV = "development"

Update the broken calculator

import python_lifecycle_training


class BrokenCalculator(Calculator):
    with settings.using_env(python_lifecycle_training.ENV):
        offset = settings.offset
        logger.info(f"Offset: {offset}")
        logger.info(f"Set by: {settings.name}")

Create a common CLI

# python_lifecycle_training/calculator/cli.py
import fire
from loguru import logger

import python_lifecycle_training
from python_lifecycle_training.calculator.broken import BrokenCalculator
from python_lifecycle_training.calculator.complex import Calculator
from python_lifecycle_training.calculator.simple import add


class Main:
    def __init__(self, env: str = "development"):
        self.env = env
        logger.info(f"Environment: {self.env}")
        python_lifecycle_training.ENV = self.env

        self.simp = add
        self.complex = Calculator
        self.broken = BrokenCalculator


def main():
    fire.Fire(Main)

Update entry point scripts

[tool.poetry.scripts]
calc = "python_lifecycle_training.calculator.cli:main"

Install package

$ poetry install

Run command on production environment from CLI

$ calc broken add 2 2 --env=production
Broken Calculator

You can switch also between existing environments using:

  • from_env: Will create a new settings instance pointing to defined env. You can use this when you have to access a lesser number of config variables at once.

    settings.from_env("production").name
    
  • setenv: (not recommended) Will set the existing instance to defined env. This might seem convenient but may cause problems as the env is not set globally.

    setenv("production")
    settings.name
    

Other features

Next Step

To move on to the next step commit or stash your changes then checkout to the branch setup/test/pytest

$ git stash
$ git checkout setup/test/pytest

Uninstall

$ poetry remove dynaconf