Source code for appsettings

# -*- coding: utf-8 -*-

"""Django AppSettings package."""

import six
from django.core.exceptions import ImproperlyConfigured
from django.core.signals import setting_changed

from .settings import (
    BooleanSetting,
    BooleanTypeChecker,
    DictSetting,
    DictTypeChecker,
    FloatSetting,
    FloatTypeChecker,
    IntegerSetting,
    IntegerTypeChecker,
    IterableSetting,
    IterableTypeChecker,
    ListSetting,
    ListTypeChecker,
    NestedSetting,
    ObjectSetting,
    ObjectTypeChecker,
    PositiveFloatSetting,
    PositiveIntegerSetting,
    SetSetting,
    Setting,
    SetTypeChecker,
    StringSetting,
    StringTypeChecker,
    TupleSetting,
    TupleTypeChecker,
    TypeChecker,
)
from .validators import DictKeysTypeValidator, DictValuesTypeValidator, TypeValidator, ValuesTypeValidator

__all__ = (
    "BooleanSetting",
    "BooleanTypeChecker",
    "DictKeysTypeValidator",
    "DictSetting",
    "DictTypeChecker",
    "DictValuesTypeValidator",
    "FloatSetting",
    "FloatTypeChecker",
    "IntegerSetting",
    "IntegerTypeChecker",
    "IterableSetting",
    "IterableTypeChecker",
    "ListSetting",
    "ListTypeChecker",
    "NestedSetting",
    "ObjectSetting",
    "ObjectTypeChecker",
    "PositiveFloatSetting",
    "PositiveIntegerSetting",
    "SetSetting",
    "Setting",
    "SetTypeChecker",
    "StringSetting",
    "StringTypeChecker",
    "TupleSetting",
    "TupleTypeChecker",
    "TypeChecker",
    "TypeValidator",
    "ValuesTypeValidator",
)


class _Metaclass(type):
    """
    ``AppSettings``'s metaclass.

    Each setting object declared in the class will be populated (name, prefix)
    and moved into the _meta.settings dictionary. A reference to this
    dictionary will also be added in the class as ``settings``.
    """

    def __new__(mcs, cls, bases, dct):
        """
        New method.

        Args:
            cls (str): class name.
            bases (tuple): base classes to inherit from.
            dct (dict): class attributes.

        Returns:
            class: the new created class.
        """
        super_new = super(_Metaclass, mcs).__new__

        # Also ensure initialization is only performed for subclasses
        # of AppSettings (excluding AppSettings class itself).
        parents = [b for b in bases if isinstance(b, _Metaclass)]
        if not parents:
            return super_new(mcs, cls, bases, dct)

        new_attr = {}
        _meta = dct.pop("Meta", type("Meta", (), {"setting_prefix": ""}))()
        _meta.settings = {}

        for name, setting in dct.items():
            if isinstance(setting, Setting):
                _meta.settings[name] = setting
                # populate name
                if setting.name == "":
                    setting.name = name
                # populate prefix
                if setting.prefix == "":
                    setting.prefix = _meta.setting_prefix
            else:
                new_attr[name] = setting
        new_attr["_meta"] = _meta
        new_attr["settings"] = _meta.settings

        return super_new(mcs, cls, bases, new_attr)

    def __getattr__(cls, item):
        """
        Return a setting object if it is in the ``_meta.settings`` dictionary.

        Args:
            item (str):
                the name of the setting variable (not the setting's name).

        Returns:
            ``Setting``: the setting object.

        Raises:
            AttributeError if the setting does not exist.
        """
        if item in cls._meta.settings.keys():
            return cls._meta.settings[item]
        raise AttributeError("'%s' class has no attribute '%s'" % (cls.__name__, item))


[docs]class AppSettings(six.with_metaclass(_Metaclass)): """ Base class for application settings. Only use this class as a parent class for inheritance. If you try to access settings directly in ``AppSettings``, it will raise a RecursionError. Some protections have been added to prevent you from instantiating this very class, or to return immediately when running ``AppSettings.check()``, but trying to access attributes on the class is not yet prevented. """
[docs] def __init__(self): """ Initialization method. The ``invalidate_cache`` method will be connected to the Django ``setting_changed`` signal in this method, with the dispatch UID being the id of this very object (``id(self)``). """ if self.__class__ == AppSettings: raise RuntimeError("Do not use AppSettings class as itself, " "use it as a base for subclasses") setting_changed.connect(self.invalidate_cache, dispatch_uid=id(self)) self._cache = {}
[docs] def __getattr__(self, item): """ Return a setting value. The caching is done here. If the setting exists, and if it's variable name is in the cache dictionary, return the cached value. If there is no cached value, get the setting value with ``setting.get_value()``, cache it, and return it. Args: item (str): the name of the setting variable (not the setting's name). Returns: object: a setting value. Raises: AttributeError if the setting does not exist. """ if item in self.settings.keys(): if item in self._cache: return self._cache[item] value = self._cache[item] = self.settings[item].get_value() return value raise AttributeError("'%s' object has no attribute '%s'" % (repr(self), item))
@classmethod
[docs] def check(cls): """ Class method to check every settings. Will raise an ``ImproperlyConfigured`` exception with explanation. """ if cls == AppSettings: return None exceptions = [] for setting in cls.settings.values(): try: setting.check() # pylama:ignore=W0703 except Exception as e: exceptions.append(str(e)) if exceptions: raise ImproperlyConfigured("\n".join(exceptions))
[docs] def invalidate_cache(self, **kwargs): """Invalidate cache. Run when receive ``setting_changed`` signal.""" self._cache = {}