Implementation of key-value pair based configuration for Python applications.
Project description
Python configuration utilities
Implementation of key-value pair based configuration for Python applications.
Features:
- support for most common sources of application settings
- support for overriding settings in sequence
- support for nested structures and lists, using attribute notation
- strategy to use environment specific settings
This library is freely inspired by .NET Core Microsoft.Extensions.Configuration
namespace and its pleasant design (ref. MSDN documentation, Microsoft Extensions Configuration Deep Dive).
The main class is influenced by Luciano Ramalho`s example of JSON structure explorer using attribute notation, in his book Fluent Python.
Overview
essentials-configuration
provides a way to handle configuration roots
composed of different layers, such as configuration files and environmental
variables. Layers are applied in order and can override each others' values,
enabling different scenarios like configuration by environment and system
instance.
Supported sources:
- yaml files
- json files
- ini files
- environmental variables
- dictionaries
- keys and values
- Azure Key Vault, using essentials-configuration-keyvault
- custom sources, implementing the
ConfigurationSource
interface
Installation
pip install essentials-configuration
To install with support for YAML
configuration files:
pip install essentials-configuration[yaml]
Extensions
- Azure Key Vault secrets configuration source: essentials-configuration-keyvault
Examples
JSON file and environmental variables
In this example, configuration values will include the structure inside the
file settings.json
and environmental variables whose name starts with "APP_".
Settings are applied in order, so environmental variables with matching name
override values from the json
file.
from configuration.common import ConfigurationBuilder
from configuration.json import JSONFile
from configuration.env import EnvironmentVariables
builder = ConfigurationBuilder(
JSONFile("settings.json"),
EnvironmentVariables(prefix="APP_")
)
config = builder.build()
For example, if the JSON file contains the following contents:
{
"logging": {
"level": "INFO"
},
"example": "Hello World",
"foo": "foo"
}
And the environment has a variable named APP_foo=AAA
:
>>> config
<Configuration {'logging': '...', 'example': '...', 'foo': '...'}>
>>> config.foo
'AAA'
>>> config.logging.level
'INFO'
YAML file and environmental variables
In this example, configuration will include anything inside a file
settings.yaml
and environmental variables. Settings are applied in order, so
environmental variables with matching name override values from the yaml
file
(using the yaml
source requires also PyYAML
package).
from configuration.common import ConfigurationBuilder
from configuration.env import EnvironmentVariables
from configuration.yaml import YAMLFile
builder = ConfigurationBuilder()
builder.add_source(YAMLFile("settings.yaml"))
builder.add_source(EnvironmentVariables())
config = builder.build()
YAML file, optional file by environment
In this example, if an environmental variable with name APP_ENVIRONMENT
and
value dev
exists, and a configuration file with name settings.dev.yaml
is
present, it is read to override values configured in settings.yaml
file.
import os
from configuration.common import ConfigurationBuilder
from configuration.env import EnvironmentVariables
from configuration.yaml import YAMLFile
environment_name = os.environ["APP_ENVIRONMENT"]
builder = ConfigurationBuilder()
builder.add_source(YAMLFile("settings.yaml"))
builder.add_source(YAMLFile(f"settings.{environment_name}.yaml", optional=True))
builder.add_source(EnvironmentVariables(prefix="APP_"))
config = builder.build()
Filtering environmental variables by prefix
from configuration.common import Configuration
config = Configuration()
# will read only environmental variables
# starting with "APP_", case insensitively, removing the "APP_" prefix by
# default
config.add_environmental_variables("APP_")
INI files
INI files are parsed using the built-in configparser
module, therefore
support [DEFAULT]
section; all values are kept as strings.
from configuration.common import ConfigurationBuilder
from configuration.ini import INIFile
builder = ConfigurationBuilder()
builder.add_source(INIFile("settings.ini"))
config = builder.build()
Dictionaries
from configuration.common import ConfigurationBuilder
builder = ConfigurationBuilder()
builder.add_map({"host": "localhost", "port": 8080})
builder.add_map({"hello": "world", "example": [{"id": 1}, {"id": 2}]})
config = builder.build()
assert config.host == "localhost"
assert config.port == 8080
assert config.hello == "world"
assert config.example[0].id == 1
assert config.example[1].id == 2
Keys and values
from configuration.common import ConfigurationBuilder
builder = ConfigurationBuilder()
builder.add_map({"host": "localhost", "port": 8080})
builder.add_value("port", 44555)
config = builder.build()
assert config.host == "localhost"
assert config.port == 44555
Overriding nested values
It is possible to override nested values by environmental variables or dictionary keys using the following notation for sub properties:
- keys separated by colon ":", such as
a:d:e
- keys separated by "__", such as
a__d__e
from configuration.common import ConfigurationBuilder, MapSource
builder = ConfigurationBuilder(
MapSource(
{
"a": {
"b": 1,
"c": 2,
"d": {
"e": 3,
"f": 4,
},
}
}
)
)
config = builder.build()
assert config.a.b == 1
assert config.a.d.e == 3
assert config.a.d.f == 4
builder.add_value("a:d:e", 5)
config = builder.build()
assert config.a.d.e == 5
assert config.a.d.f == 4
Overriding nested values using env variables
import os
builder = ConfigurationBuilder(
MapSource(
{
"a": {
"b": 1,
"c": 2,
"d": {
"e": 3,
"f": 4,
},
}
}
)
)
config = builder.build()
assert config.a.b == 1
assert config.a.d.e == 3
assert config.a.d.f == 4
# NB: if an env variable such as:
# a:d:e=5
# or...
# a__d__e=5
#
# is defined, it overrides the value from the dictionary
os.environ["a__d__e"] = "5"
builder.sources.append(EnvironmentVariables())
config = builder.build()
assert config.a.d.e == "5"
Overriding values in list items using env variables
builder = ConfigurationBuilder(
MapSource(
{
"b2c": [
{"tenant": "1"},
{"tenant": "2"},
{"tenant": "3"},
]
}
)
)
builder.add_value("b2c:1:tenant", "4")
config = builder.build()
assert config.b2c[0].tenant == "1"
assert config.b2c[1].tenant == "4"
assert config.b2c[2].tenant == "3"
Goal and non-goals
The goal of this package is to provide a way to handle configuration roots, fetching and composing settings from different sources, usually happening once at application's start.
The library implements only a synchronous API and fetching of application settings atomically (it doesn't support generators), like application settings fetched from INI, JSON, or YAML files that are read once in memory entirely. An asynchronous API is currently out of the scope of this library, since its primary use case is to fetch configuration values once at application's start.
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
Built Distribution
Hashes for essentials-configuration-0.0.2.tar.gz
Algorithm | Hash digest | |
---|---|---|
SHA256 | 5e2c81ea521d2c0bd41a956fd95c8041c2bc68d71a0d96784833adf317111170 |
|
MD5 | 555351affa381d251122d96c9fae8464 |
|
BLAKE2b-256 | 2f75b4f4813f9becf87cdb990c126f20c44f973d93244733dba372ed0e991ded |
Hashes for essentials_configuration-0.0.2-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 6e62a4822c5eecdfebb1e6e8fd6791cba9e66b50dc85b2fa6ff752f9ae021d62 |
|
MD5 | f1a454dfcc818c3dc16068af14b60357 |
|
BLAKE2b-256 | 8545f5cc79fa5225be1f28853a136676cb3fd460a2c145ff6382d5f02ba1e05c |