"""
cfg_loader.schema.base
~~~~~~~~~~~~~~~~~~~~~~
Implement base marshmallow schema to deserialize configuration data
:copyright: Copyright 2017 by ConsenSys France.
:license: BSD, see :ref:`license` for more details.
"""
import marshmallow
from marshmallow import Schema, post_load
from ..exceptions import ValidationError
from ..fields import UnwrapNested
from ..interpolator import SubstitutionTemplate, Interpolator
from ..utils import add_prefix
[docs]class InterpolatingSchema(Schema):
"""Schema class that interpolate environ variables from input data
It implements environment variable substitution following specification from docker-compose
(c.f. https://docs.docker.com/compose/compose-file/#variable-substitution)
:param substitution_mapping: Mapping containing values to substitute
:type substitution: dict
"""
_interpolator_class = Interpolator
_substitution_template = SubstitutionTemplate
def __init__(self, *args, substitution_mapping=None, **kwargs):
self.substitution_mapping = substitution_mapping or {}
self.interpolator = self._interpolator_class(substitution_mapping=self.substitution_mapping,
substitution_template=self._substitution_template)
super().__init__(*args, **kwargs)
[docs] def load(self, data, many=None, partial=None):
"""Deserialize a data structure to an object defined by this Schema’s fields
:param data: Data object to load from
:type data: dict
:param many: Whether to deserialize ``data`` as a collection
:type many: bool
:param partial: whether to ignore missing fields
:type partial: bool | tuple
:returns: Deserialized data
:type return: dict
"""
if self.substitution_mapping:
# substitute environment variables
data = self.interpolator.interpolate_recursive(data)
try:
return super().load(data, many, partial)
except marshmallow.exceptions.ValidationError as e:
raise ValidationError(e.normalized_messages())
[docs]class UnwrapNestedSchema(Schema):
"""Schema class that can unwrap nested fields"""
@post_load
def unwrap_nested_fields(self, data):
unwrap_nested = {}
for field, value in self.fields.items():
if isinstance(value, UnwrapNested):
unwrap_nested.update(add_prefix(data.pop(field), value.prefix))
data.update(unwrap_nested)
return data
[docs]class ConfigSchema(InterpolatingSchema, ExtraFieldsSchema, UnwrapNestedSchema):
"""Main schema class for declaring a configuration schema
It inherits every feature from
- :class:`InterpolatingSchema`
- :class:`ExtraFieldsSchema`
- :class:`UnwrapNestedSchema`
Example
>>> from cfg_loader import ConfigSchema, BaseConfigLoader
>>> from marshmallow import fields
>>> class MyConfigSchema(ConfigSchema):
... setting1 = fields.Str()
... setting2 = fields.Int(required=True)
... setting3 = fields.Float(missing=13.2)
>>> substitution_mapping = {'VARIABLE': 'substitution'}
>>> my_config_loader = BaseConfigLoader(MyConfigSchema,
... substitution_mapping)
>>> schema = MyConfigSchema(substitution_mapping=substitution_mapping)
>>> config = schema.load({
... 'setting1': '${VARIABLE}',
... 'setting2': '${UNSET_VARIABLE:-1}',
... })
>>> config == {
... 'setting1': 'substitution',
... 'setting2': 1,
... 'setting3': 13.2,
... }
True
"""