Generic Configuration file reader/parser with a bunch of nifty extra features, most notably:
JSON & YAML Configuration file reader/parser with built-in fault tolerance and ENV variable injecting, structure inheritance, internal referencing and secret injection.
- Enabled automatic
fidelius
integration (see: https://gitlab.ccptools.cc/packages/fidelius)
- JSON & YAML config file reading
- Files can extend base file for easy overriding and customization
- Files can include multiple other files for better organization and structure.
- Includes/extends can be in a nested structure
- Simple yet powerful internal variable reference parsing
- Inject values from elsewhere in the config files anywhere
- Automatic injection of environment variables for values
- Nested fault tolerant fetching of non-existing values
- Quickly nest keys into one with dots
- Easy default value usage
- Password and secret masking
- One line config loading
Singleton config modelExtendable for multiple singleton configs- Removed from the basic class in 2.0. Singleton functionality can be
added by simply extending the
alviss.structs.BaseConfig
on the application level and adding thetypeutils.metas.Singleton
as ametaclass
- The
alviss.structs.SingletonConfig
sill behaves as the old singleton base guy did if people want.
- Removed from the basic class in 2.0. Singleton functionality can be
added by simply extending the
Reloading (or loading, setting or updating different values) anywhere in code will change them globally wherever the same model is referenced.
Update in one place...
# Somewhere in the program we decide that reloading the config is in order
from alviss.structs import SingletonConfig
cfg = SingletonConfig()
cfg.load(foo='Foo Too You!', bar='Salad bar vs. Tequila bar!')
# This will be the same config
cfg2 = SingletonConfig()
assert cfg == cfg2
Updated everywhere...
# Anywhere the config is needed...
from alviss.structs import SingletonConfig
cfg = SingletonConfig()
assert cfg.foo == 'What is a Foo anyway?'
Calling BaseConfig.load(**kwargs)
will clear the entire config data map and
load up the new values given.
from alviss.structs import BaseConfig
# Initial values...
cfg = BaseConfig()
cfg.load(foo='Foo Too You!', bar='Salad bar vs. Tequila bar!')
assert cfg.foo == 'Foo Too You!'
assert cfg.bar == 'Salad bar vs. Tequila bar!'
# ...now we reload...
cfg.load(foo='Foo is not a real word!')
# ...so now there is no `bar` in the config
assert cfg.foo == 'What is a Foo anyway?'
assert not cfg.bar
Giving the BaseConfig
constructor **kwargs
does the same as calling
BaseConfig.load(**kwargs)
and will therefor clear and reload the config values
from the supplied keyword arguments.
from alviss.structs import BaseConfig
# Initial values...
cfg = BaseConfig()
cfg.load(foo='Foo Too You!', bar='Salad bar vs. Tequila bar!')
# This does the same thing
cfg = BaseConfig(foo='Foo Too You!', bar='Salad bar vs. Tequila bar!')
from alviss.structs import BaseConfig
# Initial values...
cfg = BaseConfig()
cfg.load(foo='Foo Too You!', bar='Salad bar vs. Tequila bar!')
# Updated only the `foo` value, but on a singleton level so it changes everywhere
cfg.foo = 'Nyoo Foo!'
Calling update(**kwargs)
will update only the given keyword values of the
config (globally via singleton magic as before) but preserves the existing
values of the config that did not change (while load(**kwargs)
wiped the
config clean first).
from alviss.structs import BaseConfig
# Initial values...
cfg = BaseConfig()
cfg.load(foo='Foo Too You!', bar='Salad bar vs. Tequila bar!')
# Updated only the given values (leaving `bar` unchanged)
cfg.update(foo='Nyoo Foo!', moo='Shoo!')
You can use the alviss.structs.SingletonConfig
as a base for implementing
other
config classes in order to store different singleton instances of different
configs.
The singleton mechanism supports inheritance so the new Classes will have their
own singleton instances separate from each other and from SingletonConfig
.
from alviss.structs import SingletonConfig
class ClientConfig(SingletonConfig):
pass
class ServerConfig(SingletonConfig):
pass
client_cfg = ClientConfig(host='localhost')
server_cfg = ServerConfig(host='127.0.0.1')
assert client_cfg != server_cfg
User the alviss.quickloader
to load a BaseConfig
from the given JSON or
YAML file.
The config.json
file...
{
"foo": "Foo Too You!",
"bar": "Salad bar vs. Tequila bar!"
}
Loading from config.json
...
from alviss import quickloader
cfg = quickloader.autoload('config.json')
assert cfg.foo == 'Foo Too You!'
Alviss now supports extending base config files and including files via the
__extends__
and __include__
config keys respectively.
Using the __extends__
key you can embed one or more additional config files
wholesale into another. This works from within the nested key hierarchy so
extended files within nested keys/sections will inject the values there.
Values of keys duplicated in both the current file and the extended files
defined in __extends__
will retain the value from the current file (i.e. the
current values override those from the extended base files).
The __extends__
key can take either a single string file name or a list of
files.
The base.json
file...
{
"foo": "Foo Too You!",
"bar": {
"first": "Beer",
"second": "Wine"
}
}
The production.json
file...
{
"__extends__": "base.json",
"bar": {
"second": "Tequila"
}
}
Loading base.json
and overriding with production.json
...
from alviss import quickloader
# Assume this comes from an environment variable for example
CONFIG_FILE = 'production.json'
# Loading the base config...
cfg = quickloader.autoload(CONFIG_FILE)
assert cfg.foo == 'Foo Too You!'
assert cfg.bar.first == 'Beer'
assert cfg.bar.second == 'Tequila'
Using the __include__
key you can embed one or more additional config files
wholesale into another. This works from within the nested key hierarchy so
including files within nested keys/sections will inject the values there.
Values of keys duplicated in both the current file and the included files
defined in __include__
will retain the value from the last included file
(i.e. the included values override those from the current one).
The __include__
key can take either a single string file name or a list of
files.
The logging.yaml
file...
level: INFO
file: stuff.log
The db.yaml
file...
database:
driver: mysql
name: mydata
The extra.yaml
file...
bar: Foo
The app.yaml
file...
foo: Bar
logging:
# Default level is DEBUG
level: DEBUG
__include__: logging.yaml
bar: NotFoo
__include__:
- db.yaml
- extra.yaml
Loading app.yaml
...
from alviss import quickloader
# Loading the config...
cfg = quickloader.autoload('app.yaml')
assert cfg.foo == 'Bar'
assert cfg.logging.level == 'INFO'
assert cfg.database.driver == 'mysql'
assert cfg.bar == 'Foo'
You can reference a nested key (e.g. for overriding in an include/extension ) by using dots to separate keys in the path.
The config.json
file...
{
"database": {
"settings": {
"connection": {
"host": "1.2.3.4",
"port": 1234
}
}
}
}
The local.json
file...
{
"__extends__": "config.json",
"database.settings.connection.host": "localhost"
}
Loading local.json
...
from alviss import quickloader
# Loading the config...
cfg = quickloader.autoload('local.json')
assert cfg.database.settings.connection.host == 'localhost'
You can reference other config value keys from within any values by using
${some.other.key}
from any string value, using dots to navigate key nestings.
The config.json
file...
{
"basics": {
"env": "prod"
},
"db": {
"name": "my-data-${basics.env}"
}
}
Loading config.json
...
from alviss import quickloader
# Loading the config...
cfg = quickloader.autoload('config.json')
assert cfg.db.name == 'my-data-prod'
You can automatically inject any environment variables set on the system by
using ${__ENV__:SOME_ENV_VAR}
from within any string value.
The config.yaml
file...
user: Foo
password: ${__ENV__:MY_PASS}
Loading from config.yaml
...
import os
os.environ.update('MY_PASS', 'SuperSecret!')
from alviss import quickloader
# Loading the config...
cfg = quickloader.autoload('config.yaml')
assert cfg.password == 'SuperSecret!'
Alviss config models will return an Empty
and/or EmptyDict
) from
typeutils
if asked for a value that was not in the config file read.
This means that you will never get a KeyError
or other such exceptions when
looking up config values, even if search multiple nested levels.
from alviss.structs import *
# Load config...
cfg = BaseConfig(foo={'alpha': 42})
assert cfg.foo.alpha == 42
assert not cfg.foo.beta
assert not cfg.bar
assert not cfg.bar.alpha
assert not cfg.a.b.c.d.e.f
To check explicitly if a value has not been set (vs. it's value just being an
empty string for example) you can check if the value is typeutils.empty.Empty
from alviss.structs import * # Includes a reference to `typeutils.empty.Empty`
# Load config...
cfg = BaseConfig(foo={'alpha': 42})
assert cfg.foo.alpha == 42
assert cfg.foo.beta is Empty
assert cfg.bar is Empty
Empty
object evaluate as False
in boolean comparisons so you can ensure
default values with an or
bitwise operator.
from alviss.structs import *
# Load config...
cfg = BaseConfig(foo='Foo Too You!')
# Get value or default
my_bar = cfg.bar or 'There was no bar'
assert my_bar == 'There was no bar'