mirror of
https://github.com/jazzband/django-configurations.git
synced 2026-03-16 22:20:27 +00:00
353 lines
11 KiB
Python
353 lines
11 KiB
Python
import ast
|
|
import copy
|
|
import decimal
|
|
import os
|
|
|
|
from django.core import validators
|
|
from django.core.exceptions import ValidationError, ImproperlyConfigured
|
|
from django.utils import six
|
|
|
|
from .utils import import_by_path
|
|
|
|
|
|
def setup_value(target, name, value):
|
|
actual_value = value.setup(name)
|
|
if value.multiple:
|
|
# overwriting the original Value class with the result
|
|
setattr(target, name, actual_value)
|
|
for multiple_name, multiple_value in actual_value.items():
|
|
setattr(target, multiple_name, multiple_value)
|
|
else:
|
|
setattr(target, name, actual_value)
|
|
|
|
|
|
class Value(object):
|
|
"""
|
|
A single settings value that is able to interpret env variables
|
|
and implements a simple validation scheme.
|
|
"""
|
|
multiple = False
|
|
|
|
def __init__(self, default=None, environ=False, environ_name=None,
|
|
environ_prefix='DJANGO', *args, **kwargs):
|
|
if isinstance(default, Value):
|
|
self.default = copy.copy(default.default)
|
|
else:
|
|
self.default = default
|
|
self.environ = environ
|
|
if environ_prefix and environ_prefix.endswith('_'):
|
|
environ_prefix = environ_prefix[:-1]
|
|
self.environ_prefix = environ_prefix
|
|
self.environ_name = environ_name
|
|
|
|
def __repr__(self):
|
|
return "<Value default: {0}>".format(self.default)
|
|
|
|
def setup(self, name):
|
|
value = self.default
|
|
if self.environ:
|
|
if self.environ_name is None:
|
|
environ_name = name.upper()
|
|
else:
|
|
environ_name = self.environ_name
|
|
if self.environ_prefix:
|
|
full_environ_name = '{0}_{1}'.format(self.environ_prefix,
|
|
environ_name)
|
|
else:
|
|
full_environ_name = environ_name
|
|
if full_environ_name in os.environ:
|
|
value = self.to_python(os.environ[full_environ_name])
|
|
return value
|
|
|
|
def to_python(self, value):
|
|
"""
|
|
Convert the given value of a environment variable into an
|
|
appropriate Python representation of the value.
|
|
This should be overriden when subclassing.
|
|
"""
|
|
return value
|
|
|
|
|
|
class MultipleMixin(object):
|
|
multiple = True
|
|
|
|
|
|
class BooleanValue(Value):
|
|
true_values = ('yes', 'y', 'true', '1')
|
|
false_values = ('no', 'n', 'false', '0', '')
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(BooleanValue, self).__init__(*args, **kwargs)
|
|
if self.default not in (True, False):
|
|
raise ImproperlyConfigured('Default value {0!r} is not a '
|
|
'boolean value'.format(self.default))
|
|
|
|
def to_python(self, value):
|
|
normalized_value = value.strip().lower()
|
|
if normalized_value in self.true_values:
|
|
return True
|
|
elif normalized_value in self.false_values:
|
|
return False
|
|
else:
|
|
raise ImproperlyConfigured('Cannot interpret '
|
|
'boolean value {0!r}'.format(value))
|
|
|
|
|
|
class CastingMixin(object):
|
|
exception = (TypeError, ValueError)
|
|
message = 'Cannot interpret value {0!r}'
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(CastingMixin, self).__init__(*args, **kwargs)
|
|
if isinstance(self.caster, six.string_types):
|
|
self._caster = import_by_path(self.caster)
|
|
elif callable(self.caster):
|
|
self._caster = self.caster
|
|
else:
|
|
error = 'Cannot use caster of {0} ({1!r})'.format(self,
|
|
self.caster)
|
|
raise ImproperlyConfigured(error)
|
|
|
|
def to_python(self, value):
|
|
try:
|
|
return self._caster(value)
|
|
except self.exception:
|
|
raise ImproperlyConfigured(self.message.format(value))
|
|
|
|
|
|
class IntegerValue(CastingMixin, Value):
|
|
caster = int
|
|
|
|
|
|
class FloatValue(CastingMixin, Value):
|
|
caster = float
|
|
|
|
|
|
class DecimalValue(CastingMixin, Value):
|
|
caster = decimal.Decimal
|
|
exception = decimal.InvalidOperation
|
|
|
|
|
|
class ListValue(Value):
|
|
converter = None
|
|
message = 'Cannot interpret list item {0!r} in list {1!r}'
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.separator = kwargs.pop('separator', ',')
|
|
converter = kwargs.pop('converter', None)
|
|
if converter is not None:
|
|
self.converter = converter
|
|
super(ListValue, self).__init__(*args, **kwargs)
|
|
# make sure the default is a list
|
|
if self.default is None:
|
|
self.default = []
|
|
# initial conversion
|
|
if self.converter is not None:
|
|
self.default = [self.converter(value) for value in self.default]
|
|
|
|
def to_python(self, value):
|
|
split_value = [v.strip() for v in value.strip().split(self.separator)]
|
|
# removing empty items
|
|
value_list = filter(None, split_value)
|
|
if self.converter is None:
|
|
return list(value_list)
|
|
|
|
converted_values = []
|
|
for list_value in value_list:
|
|
try:
|
|
converted_values.append(self.converter(list_value))
|
|
except (TypeError, ValueError):
|
|
raise ImproperlyConfigured(self.message.format(list_value,
|
|
value))
|
|
return converted_values
|
|
|
|
|
|
class BackendsValue(ListValue):
|
|
|
|
def converter(self, value):
|
|
import_by_path(value)
|
|
return value
|
|
|
|
|
|
class TupleValue(ListValue):
|
|
message = 'Cannot interpret tuple item {0!r} in tuple {1!r}'
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(TupleValue, self).__init__(*args, **kwargs)
|
|
if self.default is None:
|
|
self.default = ()
|
|
else:
|
|
self.default = tuple(self.default)
|
|
|
|
def to_python(self, value):
|
|
return tuple(super(TupleValue, self).to_python(value))
|
|
|
|
|
|
class SetValue(ListValue):
|
|
message = 'Cannot interpret set item {0!r} in set {1!r}'
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(SetValue, self).__init__(*args, **kwargs)
|
|
if self.default is None:
|
|
self.default = set()
|
|
else:
|
|
self.default = set(self.default)
|
|
|
|
def to_python(self, value):
|
|
return set(super(SetValue, self).to_python(value))
|
|
|
|
|
|
class DictValue(Value):
|
|
message = 'Cannot interpret dict value {0!r}'
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(DictValue, self).__init__(*args, **kwargs)
|
|
if self.default is None:
|
|
self.default = {}
|
|
else:
|
|
self.default = dict(self.default)
|
|
|
|
def to_python(self, value):
|
|
value = super(DictValue, self).to_python(value)
|
|
if not value:
|
|
return {}
|
|
try:
|
|
evaled_value = ast.literal_eval(value)
|
|
except ValueError:
|
|
raise ImproperlyConfigured(self.message.format(value))
|
|
if not isinstance(evaled_value, dict):
|
|
raise ImproperlyConfigured(self.message.format(value))
|
|
return evaled_value
|
|
|
|
|
|
class ValidationMixin(object):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(ValidationMixin, self).__init__(*args, **kwargs)
|
|
if isinstance(self.validator, six.string_types):
|
|
self._validator = import_by_path(self.validator)
|
|
elif callable(self.validator):
|
|
self._validator = self.validator
|
|
else:
|
|
error = 'Cannot use validator of {0} ({1!r})'.format(self,
|
|
self.validator)
|
|
raise ImproperlyConfigured(error)
|
|
self.to_python(self.default)
|
|
|
|
def to_python(self, value):
|
|
try:
|
|
self._validator(value)
|
|
except ValidationError:
|
|
raise ImproperlyConfigured(self.message.format(value))
|
|
else:
|
|
return value
|
|
|
|
|
|
class EmailValue(ValidationMixin, Value):
|
|
message = 'Cannot interpret email value {0!r}'
|
|
validator = 'django.core.validators.validate_email'
|
|
|
|
|
|
class URLValue(ValidationMixin, Value):
|
|
message = 'Cannot interpret URL value {0!r}'
|
|
validator = validators.URLValidator()
|
|
|
|
|
|
class IPValue(ValidationMixin, Value):
|
|
message = 'Cannot interpret IP value {0!r}'
|
|
validator = 'django.core.validators.validate_ipv46_address'
|
|
|
|
|
|
class RegexValue(ValidationMixin, Value):
|
|
message = "Regex doesn't match value {0!r}"
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
regex = kwargs.pop('regex', None)
|
|
self.validator = validators.RegexValidator(regex=regex)
|
|
super(RegexValue, self).__init__(*args, **kwargs)
|
|
|
|
|
|
class PathValue(Value):
|
|
def __init__(self, *args, **kwargs):
|
|
self.check_exists = kwargs.pop('check_exists', True)
|
|
super(PathValue, self).__init__(*args, **kwargs)
|
|
|
|
def setup(self, name):
|
|
value = super(PathValue, self).setup(name)
|
|
value = os.path.expanduser(value)
|
|
if self.check_exists and not os.path.exists(value):
|
|
raise ImproperlyConfigured('Path {0!r} does '
|
|
'not exist.'.format(value))
|
|
return os.path.abspath(value)
|
|
|
|
|
|
class SecretValue(Value):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
kwargs['environ'] = True
|
|
super(SecretValue, self).__init__(*args, **kwargs)
|
|
if self.default is not None:
|
|
raise ImproperlyConfigured('Secret values are only allowed to '
|
|
'be set as environment variables')
|
|
|
|
def setup(self, name):
|
|
value = super(SecretValue, self).setup(name)
|
|
if not value:
|
|
raise ImproperlyConfigured('Secret value {0!r} '
|
|
'is not set'.format(name))
|
|
return value
|
|
|
|
|
|
class DatabaseURLValue(CastingMixin, Value):
|
|
caster = 'dj_database_url.parse'
|
|
message = 'Cannot interpret database URL value {0!r}'
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.alias = kwargs.pop('alias', 'default')
|
|
kwargs.setdefault('environ', True)
|
|
kwargs.setdefault('environ_prefix', None)
|
|
kwargs.setdefault('environ_name', 'DATABASE_URL')
|
|
super(DatabaseURLValue, self).__init__(*args, **kwargs)
|
|
if self.default is None:
|
|
self.default = {}
|
|
else:
|
|
self.default = self.to_python(self.default)
|
|
|
|
def to_python(self, value):
|
|
value = super(DatabaseURLValue, self).to_python(value)
|
|
return {self.alias: value}
|
|
|
|
|
|
class EmailURLValue(CastingMixin, MultipleMixin, Value):
|
|
caster = 'dj_email_url.parse'
|
|
message = 'Cannot interpret email URL value {0!r}'
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
kwargs.setdefault('environ', True)
|
|
kwargs.setdefault('environ_prefix', None)
|
|
kwargs.setdefault('environ_name', 'EMAIL_URL')
|
|
super(EmailURLValue, self).__init__(*args, **kwargs)
|
|
if self.default is None:
|
|
self.default = {}
|
|
else:
|
|
self.default = self.to_python(self.default)
|
|
|
|
|
|
class CacheURLValue(CastingMixin, Value):
|
|
caster = 'django_cache_url.parse'
|
|
message = 'Cannot interpret cache URL value {0!r}'
|
|
|
|
def __init__(self, name='default', *args, **kwargs):
|
|
self.alias = kwargs.pop('alias', 'default')
|
|
kwargs.setdefault('environ', True)
|
|
kwargs.setdefault('environ_prefix', None)
|
|
kwargs.setdefault('environ_name', 'CACHE_URL')
|
|
super(CacheURLValue, self).__init__(*args, **kwargs)
|
|
if self.default is None:
|
|
self.default = {}
|
|
else:
|
|
self.default = self.to_python(self.default)
|
|
|
|
def to_python(self, value):
|
|
value = super(CacheURLValue, self).to_python(value)
|
|
return {self.alias: value}
|