# -*- coding: utf-8 -*-
"""
Settings backend post processing
================================
Post processing methods are used to modify or validate given settings items.
Base backend inherit from ``SettingsPostProcessor`` to be able to use it in
its ``clean()`` method.
"""
import os
import datetime
from hashlib import blake2b
from uuid import uuid4
from ..exceptions import SettingsInvalidError
from . import SETTINGS_MANIFEST
[docs]class SettingsPostProcessor(object):
"""
Mixin object for all available post processing methods to use in
settings manifest (default manifest comes from ``SETTINGS_MANIFEST``).
"""
settings_manifesto = SETTINGS_MANIFEST
projectdir = ""
[docs] def post_process(self, settings):
"""
Perform post processing methods on settings according to their
definition in manifest.
Post process methods are implemented in their own method that have the
same signature:
* Get arguments: Current settings, item name and item value;
* Return item value possibly patched;
Args:
settings (dict): Loaded settings.
Returns:
dict: Settings object possibly modified (depending from applied
post processing).
"""
for k in settings:
# Search for post process rules for setting in manifest
if (
k in self.settings_manifesto and
self.settings_manifesto[k].get("postprocess", None) is not None
):
rules = self.settings_manifesto[k]["postprocess"]
# Chain post process rules from each setting
for method_name in rules:
settings[k] = getattr(self, method_name)(
settings,
k,
settings[k]
)
return settings
[docs] def _patch_expand_path(self, settings, name, value):
"""
Patch a path to expand home directory and make absolute path.
Args:
settings (dict): Current settings.
name (str): Setting name.
value (str): Path to patch.
Returns:
str: Patched path to an absolute path.
"""
if os.path.isabs(value):
return os.path.normpath(value)
# Expand home directory if any
value = os.path.expanduser(value)
# If the path is not yet an absolute directory, make it so from base
# directory if not empty
if not os.path.isabs(value) and self.projectdir:
value = os.path.join(self.projectdir, value)
return os.path.normpath(value)
[docs] def _patch_expand_paths(self, settings, name, value):
"""
Apply ``SettingsPostProcessor._patch_expand_path`` to each element in
list.
Args:
settings (dict): Current settings.
name (str): Setting name.
value (list): List of paths to patch.
Returns:
list: Patched path list to an absolute path.
"""
return [self._patch_expand_path(settings, name, item)
for item in value]
[docs] def _validate_path(self, settings, name, value):
"""
Validate path exists
Args:
settings (dict): Current settings.
name (str): Setting name.
value (str): Path to validate.
Raises:
boussole.exceptions.SettingsInvalidError: If path does not exists.
Returns:
str: Validated path.
"""
if not os.path.exists(value):
msg = "Path from setting '{name}' does not exists: {value}"
raise SettingsInvalidError(msg.format(name=name, value=value))
return value
[docs] def _validate_paths(self, settings, name, value):
"""
Apply ``SettingsPostProcessor._validate_path`` to each element in
list.
Args:
settings (dict): Current settings.
name (str): Setting name.
value (list): List of paths to patch.
Raises:
boussole.exceptions.SettingsInvalidError: Once a path does not
exists.
Returns:
list: Validated paths.
"""
return [self._validate_path(settings, name, item)
for item in value]
[docs] def _validate_required(self, settings, name, value):
"""
Validate a required setting (value can not be empty)
Args:
settings (dict): Current settings.
name (str): Setting name.
value (str): Required value to validate.
Raises:
boussole.exceptions.SettingsInvalidError: If value is empty.
Returns:
str: Validated value.
"""
if not value:
msg = "Required value from setting '{name}' must not be empty."
raise SettingsInvalidError(msg.format(name=name))
return value
[docs] def _patch_hash_suffix(self, settings, name, value):
"""
Coerce setting ``HASH_SUFFIX`` to the right value.
Args:
settings (dict): Current settings.
name (str): Setting name.
value (str): Required value to patch.
Returns:
object: It may be:
* None if value is an empty string, None or False;
* If value is ``:blake2``, this will return a hash from black2b with a
length of 20 characters (almost guaranteed to be unique);
* If value is ``:blake2``, this will return a hash from uuid4 with a
length of 32 characters (guaranteed to be unique);
* If True, assume it enables the default hash engine which is ``:blake2``;
* A string if value is a string that do not match any hash engine name;
"""
if not value:
return None
if value is True or value == ":blake2":
# Use the current datetime with microseconds to build the hash
value = blake2b(
datetime.datetime.now().isoformat().encode('utf-8'),
digest_size=10
).hexdigest()
elif value == ":uuid":
# UUID is unique ID that does not need any content, we just remove dashes
# for shorter string
value = str(uuid4()).replace("-", "")
return value