Source code for djangofloor.conf.fields

"""Convert values from config files to Python values
=================================================

Use these classes in your mapping provided in `yourproject.iniconf:INI_MAPPING`.
Check :mod:`djangofloor.conf.mapping` for examples.
"""

import os

from django.core.checks import Error

from djangofloor.checks import settings_check_results

__author__ = "Matthieu Gallet"

MISSING_VALUE = [[]]


[docs]def bool_setting(value): """return `True` if the provided (lower-cased) text is one of ('1', 'ok', 'yes', 'true', 'on')""" return str(value).lower() in {"1", "ok", "yes", "true", "on"}
[docs]def str_or_none(text): """return `None` if the text is empty, else returns the text""" return text or None
[docs]def str_or_blank(value): """return '' if the provided value is `None`, else return value""" return "" if value is None else str(value)
[docs]def guess_relative_path(value): """Replace an absolute path by its relative path if the abspath begins by the current dir""" if value is None: return "" elif os.path.exists(value): value = os.path.abspath(value) return value.replace(os.path.abspath(os.getcwd()), ".") return value
[docs]def strip_split(value): """Split the value on "," and strip spaces of the result. Remove empty values. >>> strip_split('keyword1, keyword2 ,,keyword3') ["keyword1", "keyword2", "keyword3"] >>> strip_split('') [] >>> strip_split(None) [] :param value: :type value: :return: a list of strings :rtype: :class:`list` """ if value: return [x.strip() for x in value.split(",") if x.strip()] return []
[docs]class ConfigField: """Class that maps an option in a .ini file to a setting. :param name: the section and the option in a .ini file (like "database.engine") :param setting_name: the name of the setting (like "DATABASE_ENGINE") :param from_str: any callable that takes a text value and returns an object. Default to `str_or_none` :type from_str: `callable` :param to_str: any callable that takes the Python value and that converts it to str. Default to `str` :type to_str: `callable` :param help_str: any text that can serve has help in documentation. :param default: the value that will be used in documentation. The current setting value is used if equal to `None`. """ def __init__( self, name: str, setting_name: str, from_str=str, to_str=str_or_blank, help_str: str = None, default: object = None, ): self.name = name self.setting_name = setting_name self.from_str = from_str self.to_str = to_str self.__doc__ = help_str self.value = default def __str__(self): return self.name
[docs]class CharConfigField(ConfigField): """Accepts str values. If `allow_none`, then `None` replaces any empty value.""" def __init__(self, name, setting_name, allow_none=True, **kwargs): from_str = str_or_none if allow_none else str super().__init__( name, setting_name, from_str=from_str, to_str=str_or_blank, **kwargs )
[docs]class IntegerConfigField(ConfigField): """Accept integer values. If `allow_none`, then `None` replaces any empty values (other `0` is used).""" def __init__(self, name, setting_name, allow_none=True, **kwargs): if allow_none: def from_str(value: str): return int(value) if value else None else: def from_str(value: str): return int(value) if value else 0 super().__init__( name, setting_name, from_str=from_str, to_str=str_or_blank, **kwargs )
[docs]class FloatConfigField(ConfigField): """Accept floating-point values. If `allow_none`, then `None` replaces any empty values (other `0.0` is used).""" def __init__(self, name, setting_name, allow_none=True, **kwargs): if allow_none: def from_str(value): return float(value) if value else None else: def from_str(value): return float(value) if value else 0.0 super().__init__( name, setting_name, from_str=from_str, to_str=str_or_blank, **kwargs )
[docs]class ListConfigField(ConfigField): """Convert a string to a list of values, splitted with the :meth:`djangofloor.conf.fields.strip_split` function.""" def __init__(self, name, setting_name, **kwargs): def to_str(value): if value: return ",".join([str(x) for x in value]) return "" super().__init__( name, setting_name, from_str=strip_split, to_str=to_str, **kwargs )
[docs]class BooleanConfigField(ConfigField): """Search for a boolean value in the ini file. If this value is empty and `allow_none` is `True`, then the value is `None`. Otherwise returns `True` if the provided (lower-cased) text is one of ('1', 'ok', 'yes', 'true', 'on') """ def __init__(self, name, setting_name, allow_none=False, **kwargs): if allow_none: def from_str(value): if not value: return None return bool_setting(value) def to_str(value): if value is None: return "" return str(bool(value)).lower() else: def from_str(value): return bool_setting(value) def to_str(value): return str(bool(value)).lower() super().__init__(name, setting_name, from_str=from_str, to_str=to_str, **kwargs)
[docs]class ChoiceConfigFile(ConfigField): """Only allow a limited set of values in the .ini file. The available values must be given as :class:`str`. Choices must be a :class:`dict`, mapping .ini (string) values to actual values. If an invalid value is provided by the user, then `None` is returned, but an error is displayed through the Django check system. """ def __init__(self, name, setting_name, choices, help_str="", **kwargs): def from_str(value): if value not in choices: valid = ", ".join(['"%s"' % x for x in choices]) settings_check_results.append( Error( 'Invalid value "%s". Valid choices: %s.' % (value, valid), obj="configuration", ) ) return choices.get(value) def to_str(value): for k, v in choices.items(): if v == value: return str(k) return "" valid_values = ", ".join(['"%s"' % x for x in choices]) if help_str: help_str += " Valid choices: %s" % valid_values else: help_str = "Valid choices: %s" % valid_values super().__init__( name, setting_name, from_str=from_str, to_str=to_str, help_str=help_str, **kwargs )