Configuration sources¶
At the core of your config files, there are Sources. A Source is a single source of configuration - it could be an environment variable, or a particular file, or a directory full of these files.
-
class
satella.configuration.sources.
StaticSource
(config: dict)¶ A static piece of configuration. Returns exactly what is passed
-
provide
() → dict¶ Return your configuration, as a dict
Raises: ConfigurationError – on invalid configuration
-
-
class
satella.configuration.sources.
StaticSource
(config: dict) A static piece of configuration. Returns exactly what is passed
-
provide
() → dict Return your configuration, as a dict
Raises: ConfigurationError – on invalid configuration
-
-
class
satella.configuration.sources.
EnvironmentSource
(env_name: str, config_name: Optional[str] = None, cast_to=None)¶ This just returns a dictionary of { env_name => that env’s value }
Parameters: - env_name – name of the environment variable to check for
- config_name – name of the env_name in the dictionary to return
- cast_to – callable that converts a string to whatever form is desired. Note that this is deprecated. Use schema instead to cast to desired typing.
-
class
satella.configuration.sources.
EnvVarsSource
(env_name: str)¶ Return a dictionary that is the JSON encoded within a particular environment variable
-
class
satella.configuration.sources.
FileSource
(path: str, encoding: str = 'utf-8', interpret_as: List[Union[Type[satella.configuration.sources.format.FormatSource], str]] = ['JSONSource', 'YAMLSource', 'TOMLSource'])¶ Try to read a file and parse it with a known format.
-
class
satella.configuration.sources.
DirectorySource
(path, encoding: str = 'utf-8', interpret_as=['JSONSource', 'YAMLSource', 'TOMLSource'], fname_filter: Callable[[str], bool] = <function DirectorySource.<lambda>>, scan_subdirectories: bool = True, on_fail: int = 0)¶ Load all files from given directory and merge them
Parameters: - filter – callable that tells whether to use this file (or subdirectory if scan_subdirectories is enabled)
- on_fail – what to do in case a resource fails
-
provide
() → dict¶ Return your configuration, as a dict
Raises: ConfigurationError – on invalid configuration
Then there are abstract sources of configuration.
-
class
satella.configuration.sources.
AlternativeSource
(*sources)¶ If first source of configuration fails with ConfigurationError, use the next one instead, ad nauseam.
-
provide
() → dict¶ Raises: ConfigurationError – when backup fails too
-
-
class
satella.configuration.sources.
OptionalSource
(source: satella.configuration.sources.base.BaseSource)¶ This will substitute for empty dict if underlying config would fail.
Apply this to your sources if you expect that they will fail.
Use as
>>> OptionalSource(SomeOtherSource1)
-
class
satella.configuration.sources.
MergingSource
(*sources, on_fail: int = 0, fail_if_no_sources_are_correct: bool = True)¶ Source that merges configuration from a bunch of sources. The configuration has to be a dictionary!!
Parameters: - sources – Sources to examine. Source later in queue will override earlier’s entries, so take care.
- on_fail – how to behave when a source fails
- fail_if_no_sources_are_correct – even if on_fail == MergingSource.SILENT, if all sources fail, this will fail as well. Of course this makes sense only if on_fail == MergingSource.SILENT
-
provide
() → dict¶ Return your configuration, as a dict
Raises: ConfigurationError – on invalid configuration
-
class
satella.configuration.sources.
BuildObjectFrom
(key: str, child: satella.configuration.sources.base.BaseSource)¶ A source that outputs a single key with given name, and as it’s contents the contents of it’s child.
-
provide
() → dict¶ Return your configuration, as a dict
Raises: ConfigurationError – on invalid configuration
-
In order to actually load the configuration, use the method provide()
.
Note that FileSource will try parsing the file with any modules, available, so if you want parsing for yaml and toml, you better install pyyaml and toml respectively.
Note that JSON will be parsed using ujson if the module is available.
JSON schema¶
The JSON schema consists of defining particular sources, embedded in one another.
{
"type": "ClassNameOfTheSource",
"args": [
],
"kwarg_1": ...,
"kwarg_2": ...,
}
If an argument consists of a dict with type
key, it will be also loaded and passed internally as a source.
One of three reserved types is lambda
, which expects to have a key of operation
. This will be appended to
lambda x: `` and ``eval()
-uated.
Always you can provide a key called optional
with a value of True, this will wrap given Source in OptionalSource.
The second reserved type if binary
. This will encode the value
key with encoding
encoding (default is ascii).
The third reserved type is import
. It imports an expression and calls it with
discovered value, returning the output.
It accepts the following variables:
- module - module to import the expression from
- attribute - name of the attribute inside the module
- cast_before - a type to convert the value to before applying it to.
Eg:
class MyEnum(enum.IntEnum):
A = 0
os.environ['TEST_ENV'] = '2'
dct = {
"type": "EnvironmentSource",
"args": ["TEST_ENV", "TEST_ENV"],
"cast_to": {
"module": "my_module",
"attribute": "MyEnum",
"cast_before": {
"type": "lambda",
"operation": "int"
}
}
}
config = load_source_from_dict(dct)
assert config.provide()['TEST_ENV'] == MyEnum(0)
To instantiate the schema, use the following functions:
-
satella.configuration.sources.
load_source_from_dict
(dct: dict) → satella.configuration.sources.base.BaseSource¶ obj has a form of
- {
- “type”: “BaseSource”, “args”: [] # optional … kwargs
}
Raises: ConfigurationError – upon failure to instantiate
-
satella.configuration.sources.
load_source_from_list
(obj: list) → satella.configuration.sources.derivative.MergingSource¶ Builds a MergingSource from dict-ed objects
Please note that if your attacker has control over these files, he might provoke the application into executing arbitrary Python, so remember to sanitize your inputs!
You use BuildFromObject
in such a way:
{
"type": "BuildObjectFrom",
"key": "child",
"child": {
"type": "StaticSource",
"args": [
{"a": 5}
]
}
}
The result of this execution will be a dictionary:
{
"test": {
"a": 5
}
}
If you have only a single argument, you can also do:
{
"type": "DirectorySource",
"arg": "/app/config"
}
You can put any objects you like as the arguments, note however that if you pass a dictionary, that has a key of “type” and it’s value is one of recognized sources, an attempt will be made to parse it as a child.
Note that in case you pass a dict with a type that is not recognized, a warning will be emitted.