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: str | None = None) 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.FileContents(encoding: str | None = None, strip_afterwards: bool = False)¶
This value must be a valid path to a file. The value in your schema will be the contents of this file, applied with encoding (if given). By default, bytes will be read in
- 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: Descriptor | None = None)¶
This must be a list, made of entries of a descriptor (this is optional)
- class satella.configuration.schema.Dict(keys: ~typing.List[DictDescriptorKey], unknown_key_mapper: ~typing.Callable[[str, int | float | str | dict | list | bool | None], ~typing.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: List[Descriptor])¶
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
- class satella.configuration.schema.Regexp¶
Base class for declaring regexp-based descriptors. Overload it’s attribute REGEXP. Use as following:
>>> class IPv6(Regexp): >>> REGEXP = '(([0-9a-f]{1,4}:)' ...
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) 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.