|
|
5 years ago | |
|---|---|---|
| configspec | 5 years ago | |
| tests | 5 years ago | |
| .gitignore | 6 years ago | |
| DEVELOPMENT.md | 6 years ago | |
| LICENSE.md | 6 years ago | |
| README.md | 6 years ago | |
| pytest.ini | 6 years ago | |
| setup.py | 6 years ago | |
| tox.ini | 6 years ago | |
README.md
Config-Spec
A Python library for specifying config requirements. Enables configuration to be validated before being loaded and used.
At it's simplest, create a spec and validate values against it:
>>> from configspec import *
>>> confspec=IntSpec(maxval=12, helptext="An int below 12")
>>> confspec.validate(3)
An invalid value will throw an InvalidConfigError exception:
>>> confspec.validate(42)
configspec.specification.InvalidConfigError: Config value must be <= 12
Config-Spec extends these basics to also include recursive nesting, optional values and defaults, portable/serialised specifications, and configuration overlays.
Why yet another config library?
The original need for Config-Spec was to allow portable plugins to define what sort of config they needed, but have another core program actually load, manage and verify the config before handing it off to them. The goal was to have something simple and easy to use, without getting bogged down in complexities and boilerplate.
While it includes direct support for TOML, Config-Spec is happy to use any generic Python data structure. The specification and validation features can also be put to good use by themselves - the loading, layering and management functionality is split apart and completely optional.
Installation
Install Config-Spec with Pip directly from this repo:
pip install git+https://git.distreon.net/novirium/config-spec.git
For development setup, see the the Development guide
Usage
Specifications and validations
Specifications can be defined in 3 basic Python primary types (BoolSpec, IntSpec and StringSpec), along with dictionaries to organise them (DictSpec). The ConfigSpecification is just a special type of DictSpec used to indicate the root of the config.
All of the specification types also have a list variant (BoolListSpec, IntListSpec, StringListSpec, DictListSpec).
The general idea is to define your config specification early on, then use it to validate whatever configuration is loaded before starting up your application - allowing it to bail out gracefully or fall back to another configuration.
For example, here we create a root config specification dict confspec and add 3 keys to it of different types:
from configspec import *
confspec = ConfigSpecification()
confspec.add_spec("my_num", IntSpec(maxval=12, helptext="An int below 12"))
confspec.add_spec("my_text_items", StringListSpec(helptext="A list of strings"))
confspec.add_spec("option_flag", BoolSpec(default=False, optional=True, helptext="An optional bool"))
We can see add_spec() adding the new specification keys, giving them a name and a type, along with specific requirements to validate against. Note that the second spec added ("my_text_items") is a single specification that will then validate against every item in the list given to it.
The config spec defined above should then validate a properly structured config:
loaded_conf = {"my_num":10, "my_text_items": ["string1","string2","string3"]}
confspec.validate(loaded_conf)
If all is valid, nothing should really happen - when validate() is called an exception will be thrown if something doesn't fit properly, so any code after this point can safely assume that the loaded_conf dict contains the keys and values it expects it to.
The above example also demonstrates the optional arg that can be given to a specification - if not supplied in the dict to be validated, it will be added, and given a the default value (yes, this does mean calling validate() on some data may modify it). For instance, we can then run the following, showing that the "option_flag" now exists:
>>> print(loaded_conf["option_flag"])
False
Portable specifications
One of the dependancies for Config-Spec is python-preserve, and all Specification classes are preservable. This means that config specifications can be easily preserved to simple python types, serialised (using whatever means you prefer) and restored elsewhere:
(using confspec from above example)
>>> import preserve
>>> preserved_spec=preserve.preserve(confspec)
>>> preserved_spec
{'default': None, 'optional': False, 'helptext': '', 'spec_dict': {'my_num': {'default': None, 'optional': False, 'helptext': 'An int below 12', 'minval': None, 'maxval': 12, '<_jam>': 'IntSpec'}, 'my_text': {'default': '', 'optional': False, 'helptext': 'A list of strings', 'minlength': None, 'maxlength': None, '<_jam>': 'StringListSpec'}, 'option_flag': {'default': False, 'optional': True, 'helptext': 'An optional bool', '<_jam>': 'BoolSpec'}}, '<_jam>': 'ConfigSpecification'}
>>> preserve.restore(preserved_spec)
<configspec.specification.ConfigSpecification object at 0x7fb973651d60>
Config Manager
Config-Spec includes a config manager to help load and organise different bundles and layers of configuration values, but this is still changing regularly. Use of this manager is completely optional, and is all self-contained in manager.py