Welcome to Ubimaior
ubimaior
is a package to manage hierarchical configurations
split over multiple files and scopes. It's strongly inspired by
the way Spack manages its configuration and
aims at replicating the same user-level mechanism with an API
that mimics Python built-in objects.
Getting started
Many applications or libraries are customizable using configuration files, environment variables or command line arguments. In general a software can define different places, or scopes, where its configuration is stored or from where it is retrieved from.
Usually the semantics is such that configuration set in a higher
scope overrides the one set in a lower scope. ubimaior
, instead,
gives the ability to either override or merge options when they are
defined in a dictionary or list.
Consider for instance having rules in your configuration to enable or disable certain features of your application:
config:
# "enable" is a list of objects defining a list
# of plugins to enable
enable:
- name: plugin1
options: "foo,fee"
and assume that a config.yaml
file can be stored either at the system
level in /etc/app
, with higher precedence, or at the user level in
~/.app
, with lower precedence. In that case, if we have in /etc/app/config.yaml
:
config:
enable:
- name: plugin1
options: 'foo'
and in ~/.app/config.yaml
:
config:
enable:
- name: plugin2
options: 'bar,baz'
what the application will receive as input configuration will be:
config:
enable:
- name: plugin1
options: 'foo'
- name: plugin2
options: 'bar,baz'
i.e. a merge of the two files with a list / dictionary order that respects priority.
Use ubimaior
in your project
Using ubimaior
in a project is rather simple. The basic functionality
is available through the ubimaior.load
function.
Let's go back to our previous example and assume we want to load the
configuration split across the two config.yaml
files in the "system" and "user" scope.
A single call to ubimaior.load
can do that:
import ubimaior
config = ubimaior.load(
config_name='config', config_format='yaml',
scopes=[('system', '/etc/app'), ('user', '~/.app')]
)
The name of the config file is determined by the config_name
and
the config_format
arguments. The scopes
argument instead defines
the configuration scopes as a (name, path)
tuple.
The config
object is an instance of ubimaior.mapping.OverridableMapping
and behaves effectively as a built-in dictionary:
assert len(config) == 1
assert len(config['config']['enable']) == 2
while the config['config']['enable']
item is an instance of
ubimaior.sequence.MergedSequence
and behaves like a tuple.
For instance, we can add a new key:
config['config']['data_dir'] = './data'
To serialize our changes back to disk we need to call:
import ubimaior
ubimaior.dump(
config.flattened(), config_name='config', config_format='yaml',
scopes=[('system', '/etc/app'), ('user', '~/.app')]
)
Commands
ubimaior show
- Display the merged configuration file.ubimaior -h
- Print help message and exit.
Project layout
mkdocs.yml # Configuration file for docs
.github/ # Github configuration files
docs/
index.md # The documentation homepage
... # Other markdown pages, images and other files
tests/ # Unit tests
ubimaior/ # Code for the package