You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
86 lines
5.1 KiB
86 lines
5.1 KiB
# 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:
|
|
```python
|
|
>>> from configspec import *
|
|
>>> confspec=IntSpec(maxval=12, helptext="An int below 12")
|
|
>>> confspec.validate(3)
|
|
```
|
|
An invalid value will throw an `InvalidConfigError` exception:
|
|
```python
|
|
>>> 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:
|
|
|
|
```bash
|
|
pip install git+https://git.distreon.net/novirium/config-spec.git
|
|
```
|
|
|
|
For development setup, see the [the Development guide](./DEVELOPMENT.md)
|
|
|
|
# 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:
|
|
```python
|
|
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:
|
|
```python
|
|
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:
|
|
```python
|
|
>>> print(loaded_conf["option_flag"])
|
|
False
|
|
```
|
|
|
|
## Portable specifications
|
|
One of the dependancies for Config-Spec is [python-preserve](https://git.distreon.net/novirium/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:
|
|
|
|
```python
|
|
(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](configspec/manager.py) |