Configuration schema validation¶
As noted in index, your configuration is mostly supposed to be a dict. To validate your schema, you should instantiate a Descriptor. Descriptor reflects how your config is nested.
-
class
satella.configuration.schema.
Boolean
¶ This value must be a boolean, or be converted to one
-
class
satella.configuration.schema.
Float
¶ This value must be a float, or be converted to one
-
class
satella.configuration.schema.
Integer
¶ This value must be an integer, or be converted to one
-
class
satella.configuration.schema.
String
¶ This value must be a string, or be converted to one
-
class
satella.configuration.schema.
File
¶ This value must be a valid path to a file. The value in your schema will be an instance of
FileObject
-
class
satella.configuration.schema.
FileObject
(path: str)¶ What you get for values in schema of
File
.This object is comparable and hashable, and is equal to the string of it’s path
-
get_value
(encoding: Optional[str] = None) → Union[str, bytes]¶ Read in the entire file into memory
Parameters: encoding – optional encoding to apply. If None given, bytes will be returned Returns: file contents
-
open
(mode: str)¶ Open the file in specified mode
Parameters: mode – mode to open the file in Returns: file handle
-
-
class
satella.configuration.schema.
Directory
¶ This value must be a valid path to a file. The value in your schema will be an instance of
FileObject
-
class
satella.configuration.schema.
DirectoryObject
(path: str)¶ What you get for values in schema of
Directory
.This object is comparable and hashable, and is equal to the string of it’s path
-
get_files
() → Iterable[str]¶ Return a list of files inside this directory :return:
-
-
class
satella.configuration.schema.basic.
FileObject
(path: str)¶ What you get for values in schema of
File
.This object is comparable and hashable, and is equal to the string of it’s path
-
class
satella.configuration.schema.
IPv4
¶ This must be a valid IPv4 address (no hostnames allowed)
-
class
satella.configuration.schema.
List
(type_descriptor: Optional[satella.configuration.schema.base.Descriptor] = None)¶ This must be a list, made of entries of a descriptor (this is optional)
-
class
satella.configuration.schema.
Dict
(keys: List[NewType.<locals>.new_type], unknown_key_mapper: Callable[[str, Union[int, float, str, dict, list, bool, None]], Any] = <function Dict.<lambda>>)¶ This entry must be a dict, having at least specified keys.
Use like:
>>> Dict([ >>> create_key(String(), 'key_s'), >>> create_key(Integer(), 'key_i'), >>> create_key(Float(), 'key_f'), >>> create_key(String(), 'key_not_present', optional=True, >>> default='hello world'), >>> create_key(IPv4(), 'ip_addr') >>>])
-
class
satella.configuration.schema.
Caster
(to_cast: Callable[[Any], Any])¶ A value must be ran through a function.
Use like:
>>> class Environment(enum.IntEnum): >>> PRODUCTION = 0 >>> assert Caster(Environment)(0) == Environment.PRODUCTION
Then there is a descriptor that makes it possible for a value to have one of two types:
-
class
satella.configuration.schema.
Union
(*descriptors)¶ The type of one of the child descriptors. If posed as such:
Union(List(), Dict())
then value can be either a list or a dict
You can use the following to declare your own descriptors:
-
class
satella.configuration.schema.
Descriptor
¶ Base class for a descriptor
Just remember to decorate them with
-
satella.configuration.schema.
register_custom_descriptor
(name: str, is_plain: bool = True)¶ A decorator used for registering custom descriptors in order to be loadable via descriptor_from_dict
Use like:
>>> @register_custom_descriptor('ipv6') >>> class IPv6(Regexp): >>> REGEXP = '(([0-9a-f]{1,4}:)' ...
Parameters: - name – under which it is supposed to be invokable
- is_plain – is this a nested structure?
If you want them loadable by the JSON-schema loader.
You use the descriptors by calling them on respective values, eg.
>>> List(Integer())(['1', '2', 3.0])
[1, 2, 3]
JSON schema¶
The JSON schema is pretty straightforward. Assuming the top-level is a dict, it contains keys. A key name is the name of the corresponding key, and value can have two types. Either it is a string, which is a short-hand for a descriptor, or a dict containing following values:
{
"type": "string_type",
"optional": True/False,
"default": "default_value" - providing this implies optional=True
}
Note that providing a short-hand, string type is impossible for descriptors that take required arguments.
Available string types are:
- int -
Integer
- str -
String
- list -
List
- dict -
Dict
- ipv4 -
IPv4
- any -
Descriptor
- bool -
Boolean
- union -
Union
- caster -
Caster
- file -
File
- file_contents -
FileContents
- dir -
Directory
You can use file contents as follows:
{
"contents": {
"type": "file_contents",
"encoding": "utf-8
}
}
Or just
{
"contents": "file_contents"
}
But in this case, bytes will be read in.
Lists you define as following
{
"type": "list",
"of": {
".. descriptor type that this list has to have .."
}
}
Unions you define the following
{
"type": "union",
"of": [
".. descriptor type 1 ..",
".. descriptor type 2 .."
]
}
Dicts are more simple. Each key contains the key that should be present in the dict, and value is it’s descriptor
- again, either in a short form (if applicable) or a long one (dict with type
key).
You load it using the following function:
-
satella.configuration.schema.
descriptor_from_dict
(dct: dict) → satella.configuration.schema.base.Descriptor¶ Giving a Python dictionary-defined schema of the configuration, return a Descriptor-based one
Parameters: dct – something like - {
“a”: “int”, “b”: “str”, “c”: {
“type”: “int” “optional”: True, “default”: 5}, “d”: {
“a”: “int”, “b”: “str”}
}
although you can pass “int”, “float” and “str” without enclosing quotes, that will work too
Returns: a Descriptor-based schema
Casters you define as
{
"type": "caster"
"cast_to": "name of a built-in or a fully qualified class ID"
}
If cast_to is not a builtin, it specifies a full path to a class,
which will be loaded using
satella.imports.import_class()
.
Additionally, an extra argument can be specified:
{
"type": "caster",
"cast_to": "name of a built-in or a FQ class ID",
"expr": "y(int(x))"
}
In which case cast_to will be displayed as a y in expression, which will be eval()ed, and this value will be output. The input value will be called x.
You can also provide a commentary for your entries:
{
"contents": {
"type": "file_contents",
"encoding": "utf-8,
"description": "Encryption key (private key)",
"strip_afterwards": True
},
"max_workers": {
"type": "int",
"description": "Maximum parallel instances of service"
}
}
strip_afterwards
(default is False) strips the content of loaded file of trailing and
leading whitespace.