Added a configuration values system.

This also adds some advanced features like a setup classmethod to the Configuration class.
Reorganized and extended the documentation.
This commit is contained in:
Jannis Leidel 2013-07-27 12:37:28 +02:00
parent 6db9fdbf30
commit 1a54847375
15 changed files with 1464 additions and 234 deletions

View file

@ -58,6 +58,7 @@ of the appropriate starter functions, e.g. a typical **manage.py** using
django-configurations would look like this:
.. code-block:: python
:emphasize-lines: 10
#!/usr/bin/env python
@ -72,13 +73,14 @@ django-configurations would look like this:
execute_from_command_line(sys.argv)
Notice in line 9 we don't use the common tool
Notice in line 10 we don't use the common tool
``django.core.management.execute_from_command_line`` but instead
``configurations.management.execute_from_command_line``.
The same applies to your **wsgi.py** file, e.g.:
.. code-block:: python
:emphasize-lines: 6
import os
@ -95,16 +97,26 @@ function but instead ``configurations.wsgi.get_wsgi_application``.
That's it! You can now use your project with ``manage.py`` and your favorite
WSGI enabled server.
**Alternatively** you can use a special Django project template that is a copy
of the one included in Django 1.5.x. The following example assumes you're using
pip_ to install dependencies.::
Project templates
-----------------
You can use a special Django project template that is a copy of the one
included in Django 1.5.x. The following examples assumes you're using pip_
to install packages.
First install Django and django-configurations::
# first install Django and django-configurations
pip install -r https://raw.github.com/jezdez/django-configurations/templates/1.5.x/requirements.txt
# then create your new Django project with the provided template
Then create your new Django project with the provided template::
django-admin.py startproject mysite -v2 --template https://github.com/jezdez/django-configurations/archive/templates/1.5.x.zip
Now you have a default Django 1.5.x project in the ``mysite`` directory that uses
django-configurations.
See the repository of the template for more information:
https://github.com/jezdez/django-configurations/tree/templates/1.5.x
.. _pip: http://pip-installer.org/

View file

@ -5,6 +5,7 @@ from django.conf import global_settings
from django.core.exceptions import ImproperlyConfigured
from .utils import uppercase_attributes
from .values import Value, setup_value
__all__ = ['Configuration']
@ -70,6 +71,12 @@ class Configuration(six.with_metaclass(ConfigurationBase)):
def post_setup(cls):
pass
@classmethod
def setup(cls):
for name, value in uppercase_attributes(cls).items():
if isinstance(value, Value):
setup_value(cls, name, value)
class Settings(Configuration):

View file

@ -10,8 +10,8 @@ from django.conf import ENVIRONMENT_VARIABLE as SETTINGS_ENVIRONMENT_VARIABLE
from django.utils.decorators import available_attrs
from django.utils.importlib import import_module
from .utils import uppercase_attributes
from .utils import uppercase_attributes, reraise
from .values import Value, setup_value
installed = False
@ -133,22 +133,6 @@ class ConfigurationImporter(object):
return None
def reraise(exc, prefix=None, suffix=None):
args = exc.args
if not args:
args = ('',)
if prefix is None:
prefix = ''
elif not prefix.endswith((':', ': ')):
prefix = prefix + ': '
if suffix is None:
suffix = ''
elif not (suffix.startswith('(') and suffix.endswith(')')):
suffix = '(' + suffix + ')'
exc.args = ('%s %s %s' % (prefix, exc.args[0], suffix),) + args[1:]
raise
class ConfigurationLoader(object):
def __init__(self, name, location):
@ -173,6 +157,11 @@ class ConfigurationLoader(object):
except Exception as err:
reraise(err, "While calling '{0}.pre_setup()'".format(cls_path))
try:
cls.setup()
except Exception as err:
reraise(err, "While calling the '{0}.setup()'".format(cls_path))
try:
obj = cls()
except Exception as err:
@ -192,6 +181,11 @@ class ConfigurationLoader(object):
except Exception as err:
reraise(err, "While calling '{0}.{1}'".format(cls_path,
value))
# in case a method returns a Value instance we have
# to do the same as the Configuration.setup method
if isinstance(value, Value):
setup_value(mod, name, value)
continue
setattr(mod, name, value)
setattr(mod, 'CONFIGURATION', '{0}.{1}'.format(fullname, self.name))

View file

@ -0,0 +1,292 @@
import decimal
import os
from contextlib import contextmanager
from django.test import TestCase
from django.core.exceptions import ImproperlyConfigured
from mock import patch
from configurations.values import (Value, BooleanValue, IntegerValue,
FloatValue, DecimalValue, ListValue,
TupleValue, SetValue, DictValue,
URLValue, EmailValue, IPValue,
RegexValue, PathValue, SecretValue,
DatabaseURLValue, EmailURLValue,
CacheURLValue, BackendsValue,
CastingMixin)
@contextmanager
def env(**kwargs):
with patch.dict(os.environ, clear=True, **kwargs):
yield
class FailingCasterValue(CastingMixin, Value):
caster = 'non.existing.caster'
class ValueTests(TestCase):
def test_value(self):
value = Value('default')
self.assertEqual(value.setup('TEST'), 'default')
with env(DJANGO_TEST='override'):
self.assertEqual(value.setup('TEST'), 'default')
@patch.dict(os.environ, clear=True, DJANGO_TEST='override')
def test_env_var(self):
value = Value('default', environ=True)
self.assertEqual(value.setup('TEST'), 'override')
self.assertNotEqual(value.setup('TEST'), value.default)
self.assertEqual(value.to_python(os.environ['DJANGO_TEST']),
value.setup('TEST'))
def test_value_reuse(self):
value1 = Value('default', environ=True)
value2 = Value(value1, environ=True)
self.assertEqual(value1.setup('TEST1'), 'default')
self.assertEqual(value2.setup('TEST2'), 'default')
with env(DJANGO_TEST1='override1', DJANGO_TEST2='override2'):
self.assertEqual(value1.setup('TEST1'), 'override1')
self.assertEqual(value2.setup('TEST2'), 'override2')
def test_env_var_prefix(self):
with patch.dict(os.environ, clear=True, ACME_TEST='override'):
value = Value('default', environ=True, environ_prefix='ACME')
self.assertEqual(value.setup('TEST'), 'override')
with patch.dict(os.environ, clear=True, TEST='override'):
value = Value('default', environ=True, environ_prefix='')
self.assertEqual(value.setup('TEST'), 'override')
def test_boolean_values_true(self):
value = BooleanValue(False, environ=True)
for truthy in value.true_values:
with env(DJANGO_TEST=truthy):
self.assertTrue(value.setup('TEST'))
def test_boolean_values_faulty(self):
self.assertRaises(ValueError, BooleanValue, 'false')
def test_boolean_values_false(self):
value = BooleanValue(True, environ=True)
for falsy in value.false_values:
with env(DJANGO_TEST=falsy):
self.assertFalse(value.setup('TEST'))
def test_boolean_values_nonboolean(self):
value = BooleanValue(True, environ=True)
with env(DJANGO_TEST='nonboolean'):
self.assertRaises(ValueError, value.setup, 'TEST')
def test_integer_values(self):
value = IntegerValue(1, environ=True)
with env(DJANGO_TEST='2'):
self.assertEqual(value.setup('TEST'), 2)
with env(DJANGO_TEST='noninteger'):
self.assertRaises(ValueError, value.setup, 'TEST')
def test_float_values(self):
value = FloatValue(1.0, environ=True)
with env(DJANGO_TEST='2.0'):
self.assertEqual(value.setup('TEST'), 2.0)
with env(DJANGO_TEST='noninteger'):
self.assertRaises(ValueError, value.setup, 'TEST')
def test_decimal_values(self):
value = DecimalValue(decimal.Decimal(1), environ=True)
with env(DJANGO_TEST='2'):
self.assertEqual(value.setup('TEST'), decimal.Decimal(2))
with env(DJANGO_TEST='nondecimal'):
self.assertRaises(ValueError, value.setup, 'TEST')
def test_failing_caster(self):
self.assertRaises(ImproperlyConfigured, FailingCasterValue)
def test_list_values_default(self):
value = ListValue(environ=True)
with env(DJANGO_TEST='2,2'):
self.assertEqual(value.setup('TEST'), ['2', '2'])
with env(DJANGO_TEST='2, 2 ,'):
self.assertEqual(value.setup('TEST'), ['2', '2'])
with env(DJANGO_TEST=''):
self.assertEqual(value.setup('TEST'), [])
def test_list_values_separator(self):
value = ListValue(environ=True, separator=':')
with env(DJANGO_TEST='/usr/bin:/usr/sbin:/usr/local/bin'):
self.assertEqual(value.setup('TEST'),
['/usr/bin', '/usr/sbin', '/usr/local/bin'])
def test_List_values_converter(self):
value = ListValue(environ=True, converter=int)
with env(DJANGO_TEST='2,2'):
self.assertEqual(value.setup('TEST'), [2, 2])
value = ListValue(environ=True, converter=float)
with env(DJANGO_TEST='2,2'):
self.assertEqual(value.setup('TEST'), [2.0, 2.0])
def test_list_values_custom_converter(self):
value = ListValue(environ=True, converter=lambda x: x * 2)
with env(DJANGO_TEST='2,2'):
self.assertEqual(value.setup('TEST'), ['22', '22'])
def test_list_values_converter_exception(self):
value = ListValue(environ=True, converter=int)
with env(DJANGO_TEST='2,b'):
self.assertRaises(ValueError, value.setup, 'TEST')
def test_tuple_values_default(self):
value = TupleValue(environ=True)
with env(DJANGO_TEST='2,2'):
self.assertEqual(value.setup('TEST'), ('2', '2'))
with env(DJANGO_TEST='2, 2 ,'):
self.assertEqual(value.setup('TEST'), ('2', '2'))
with env(DJANGO_TEST=''):
self.assertEqual(value.setup('TEST'), ())
def test_set_values_default(self):
value = SetValue(environ=True)
with env(DJANGO_TEST='2,2'):
self.assertEqual(value.setup('TEST'), set(['2', '2']))
with env(DJANGO_TEST='2, 2 ,'):
self.assertEqual(value.setup('TEST'), set(['2', '2']))
with env(DJANGO_TEST=''):
self.assertEqual(value.setup('TEST'), set())
def test_dict_values_default(self):
value = DictValue(environ=True)
with env(DJANGO_TEST='{2: 2}'):
self.assertEqual(value.setup('TEST'), {2: 2})
expected = {2: 2, '3': '3', '4': [1, 2, 3]}
with env(DJANGO_TEST="{2: 2, '3': '3', '4': [1, 2, 3]}"):
self.assertEqual(value.setup('TEST'), expected)
with env(DJANGO_TEST="""{
2: 2,
'3': '3',
'4': [1, 2, 3],
}"""):
self.assertEqual(value.setup('TEST'), expected)
with env(DJANGO_TEST=''):
self.assertEqual(value.setup('TEST'), {})
with env(DJANGO_TEST='spam'):
self.assertRaises(ValueError, value.setup, 'TEST')
def test_email_values(self):
value = EmailValue('spam@eg.gs', environ=True)
with env(DJANGO_TEST='spam@sp.am'):
self.assertEqual(value.setup('TEST'), 'spam@sp.am')
with env(DJANGO_TEST='spam'):
self.assertRaises(ValueError, value.setup, 'TEST')
def test_url_values(self):
value = URLValue('http://eggs.spam', environ=True)
with env(DJANGO_TEST='http://spam.eggs'):
self.assertEqual(value.setup('TEST'), 'http://spam.eggs')
with env(DJANGO_TEST='httb://spam.eggs'):
self.assertRaises(ValueError, value.setup, 'TEST')
def test_ip_values(self):
value = IPValue('0.0.0.0', environ=True)
with env(DJANGO_TEST='127.0.0.1'):
self.assertEqual(value.setup('TEST'), '127.0.0.1')
with env(DJANGO_TEST='::1'):
self.assertEqual(value.setup('TEST'), '::1')
with env(DJANGO_TEST='spam.eggs'):
self.assertRaises(ValueError, value.setup, 'TEST')
def test_regex_values(self):
value = RegexValue('000--000', environ=True, regex=r'\d+--\d+')
with env(DJANGO_TEST='123--456'):
self.assertEqual(value.setup('TEST'), '123--456')
with env(DJANGO_TEST='123456'):
self.assertRaises(ValueError, value.setup, 'TEST')
def test_path_values_with_check(self):
value = PathValue(environ=True)
with env(DJANGO_TEST='/'):
self.assertEqual(value.setup('TEST'), '/')
with env(DJANGO_TEST='~/'):
self.assertEqual(value.setup('TEST'), os.path.expanduser('~'))
with env(DJANGO_TEST='/does/not/exist'):
self.assertRaises(ValueError, value.setup, 'TEST')
def test_path_values_no_check(self):
value = PathValue(environ=True, check_exists=False)
with env(DJANGO_TEST='/'):
self.assertEqual(value.setup('TEST'), '/')
with env(DJANGO_TEST='~/spam/eggs'):
self.assertEqual(value.setup('TEST'),
os.path.join(os.path.expanduser('~'),
'spam', 'eggs'))
with env(DJANGO_TEST='/does/not/exist'):
self.assertEqual(value.setup('TEST'), '/does/not/exist')
def test_secret_value(self):
self.assertRaises(ValueError, SecretValue, 'default')
value = SecretValue()
self.assertRaises(ValueError, value.setup, 'TEST')
with env(DJANGO_SECRET_KEY='123'):
self.assertEqual(value.setup('SECRET_KEY'), '123')
def test_database_url_value(self):
value = DatabaseURLValue(environ=True)
self.assertEqual(value.default, {})
with env(DATABASE_URL='sqlite://'):
self.assertEqual(value.setup('DATABASE_URL'), {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'HOST': None,
'NAME': ':memory:',
'PASSWORD': None,
'PORT': None,
'USER': None,
}})
def test_email_url_value(self):
value = EmailURLValue(environ=True)
self.assertEqual(value.default, {})
with env(EMAIL_URL='smtps://user@domain.com:password@smtp.example.com:587'):
self.assertEqual(value.setup('EMAIL_URL'), {
'EMAIL_BACKEND': 'django.core.mail.backends.smtp.EmailBackend',
'EMAIL_FILE_PATH': '',
'EMAIL_HOST': 'smtp.example.com',
'EMAIL_HOST_PASSWORD': 'password',
'EMAIL_HOST_USER': 'user@domain.com',
'EMAIL_PORT': 587,
'EMAIL_USE_TLS': True})
with env(EMAIL_URL='console://'):
self.assertEqual(value.setup('EMAIL_URL'), {
'EMAIL_BACKEND': 'django.core.mail.backends.console.EmailBackend',
'EMAIL_FILE_PATH': '',
'EMAIL_HOST': None,
'EMAIL_HOST_PASSWORD': None,
'EMAIL_HOST_USER': None,
'EMAIL_PORT': None,
'EMAIL_USE_TLS': False})
with env(EMAIL_URL='smtps://user@domain.com:password@smtp.example.com:wrong'):
self.assertRaises(ValueError, value.setup, 'TEST')
def test_cache_url_value(self):
value = CacheURLValue(environ=True)
self.assertEqual(value.default, {})
with env(CACHE_URL='redis://user@host:port/1'):
self.assertEqual(value.setup('CACHE_URL'), {
'default': {
'BACKEND': 'redis_cache.cache.RedisCache',
'KEY_PREFIX': '',
'LOCATION': 'user@host:port:1'
}})
with env(CACHE_URL='wrong://user@host:port/1'):
self.assertRaises(KeyError, value.setup, 'TEST')
def test_backend_list_value(self):
backends = ['django.middleware.common.CommonMiddleware']
value = BackendsValue(backends)
self.assertEqual(value.setup('TEST'), backends)
backends = ['non.existing.Backend']
self.assertRaises(ImproperlyConfigured, BackendsValue, backends)

View file

@ -1,3 +1,10 @@
import sys
from django.core.exceptions import ImproperlyConfigured
from django.utils import six
from django.utils.importlib import import_module
def isuppercase(name):
return name == name.upper() and not name.startswith('_')
@ -5,3 +12,50 @@ def isuppercase(name):
def uppercase_attributes(obj):
return dict((name, getattr(obj, name))
for name in filter(isuppercase, dir(obj)))
def import_by_path(dotted_path, error_prefix=''):
"""
Import a dotted module path and return the attribute/class designated by the
last name in the path. Raise ImproperlyConfigured if something goes wrong.
Backported from Django 1.6.
"""
try:
module_path, class_name = dotted_path.rsplit('.', 1)
except ValueError:
raise ImproperlyConfigured("{0}{1} doesn't look like "
"a module path".format(error_prefix,
dotted_path))
try:
module = import_module(module_path)
except ImportError as err:
msg = '{0}Error importing module {1}: "{2}"'.format(error_prefix,
module_path,
err)
six.reraise(ImproperlyConfigured, ImproperlyConfigured(msg),
sys.exc_info()[2])
try:
attr = getattr(module, class_name)
except AttributeError:
raise ImproperlyConfigured('{0}Module "{1}" does not define a '
'"{2}" attribute/class'.format(error_prefix,
module_path,
class_name))
return attr
def reraise(exc, prefix=None, suffix=None):
args = exc.args
if not args:
args = ('',)
if prefix is None:
prefix = ''
elif not prefix.endswith((':', ': ')):
prefix = prefix + ': '
if suffix is None:
suffix = ''
elif not (suffix.startswith('(') and suffix.endswith(')')):
suffix = '(' + suffix + ')'
exc.args = ('{0} {1} {2}'.format(prefix, exc.args[0], suffix),) + args[1:]
raise

346
configurations/values.py Normal file
View file

@ -0,0 +1,346 @@
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 ValueError('Default value {!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 ValueError('Cannot interpret '
'boolean value {!r}'.format(value))
class CastingMixin(object):
exception = (TypeError, ValueError)
message = 'Cannot interpret value {!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 ValueError(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 {!r} in list {!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 value_list
converted_values = []
for list_value in value_list:
try:
converted_values.append(self.converter(list_value))
except (TypeError, ValueError):
raise ValueError(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 {!r} in tuple {!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 {!r} in set {!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):
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 {}
evaled_value = ast.literal_eval(value)
if not isinstance(evaled_value, dict):
raise ValueError('Cannot interpret dict value {!s}'.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 ValueError(self.message.format(value))
else:
return value
class EmailValue(ValidationMixin, Value):
message = 'Cannot interpret email value {!r}'
validator = 'django.core.validators.validate_email'
class URLValue(ValidationMixin, Value):
message = 'Cannot interpret URL value {!r}'
validator = validators.URLValidator()
class IPValue(ValidationMixin, Value):
message = 'Cannot interpret IP value {!r}'
validator = 'django.core.validators.validate_ipv46_address'
class RegexValue(ValidationMixin, Value):
message = "Regex doesn't match value {!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 ValueError('Path {!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 ValueError('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 ValueError('Secret value {!r} is not set'.format(name))
return value
class DatabaseURLValue(CastingMixin, Value):
caster = 'dj_database_url.parse'
message = 'Cannot interpret database URL value {!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 {!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 {!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}

1
docs/changes.rst Normal file
View file

@ -0,0 +1 @@
.. include:: ../CHANGES.rst

View file

@ -48,14 +48,9 @@ copyright = u'2012-2013, Jannis Leidel and other contributors'
# |version| and |release|, also used in various other places throughout the
# built documents.
#
try:
from configurations import __version__
# The short X.Y version.
version = '.'.join(__version__.split('.')[:2])
# The full version, including alpha/beta/rc tags.
release = __version__
except ImportError:
version = release = 'dev'
version = '0.4'
# The full version, including alpha/beta/rc tags.
release = version
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@ -291,4 +286,11 @@ epub_copyright = u'2012, Jannis Leidel'
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'http://docs.python.org/': None}
intersphinx_mapping = {
'python': ('http://docs.python.org/2.7', None),
'sphinx': ('http://sphinx.pocoo.org/', None),
'django': ('http://docs.djangoproject.com/en/dev/',
'http://docs.djangoproject.com/en/dev/_objects/'),
}
add_function_parentheses = add_module_names = False

77
docs/cookbook.rst Normal file
View file

@ -0,0 +1,77 @@
Cookbook
========
Celery
------
Given Celery's way to load Django settings in worker processes you should
probably just add the following to the **begin** of your settings module::
from configurations import importer
importer.install()
That has the same effect as using the ``manage.py`` or ``wsgi.py`` utilities
mentioned above.
FastCGI
-------
In case you use FastCGI for deploying Django (you really shouldn't) and aren't
allowed to us Django's runfcgi_ management command (that would automatically
handle the setup for your if you've followed the quickstart guide above), make
sure to use something like the following script::
#!/usr/bin/env python
import os
import sys
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
os.environ.setdefault('DJANGO_CONFIGURATION', 'MySiteConfiguration')
from configurations.fastcgi import runfastcgi
runfastcgi(method='threaded', daemonize='true')
As you can see django-configurations provides a helper module
``configurations.fastcgi`` that handles the setup of your configurations.
.. _runfcgi: https://docs.djangoproject.com/en/1.5/howto/deployment/fastcgi/
Envdir
------
envdir_ is an effective way to set a large number of environment variables
at once during startup of a command. This is great in combination with
django-configuration's :class:`~configurations.values.Value` subclasses
when enabling their ability to check environment variables for override
values.
Imagine for example you want to set a few environment variables, all you
have to do is to create a directory with files that have capitalized names
and contain the values you want to set.
Example::
$ tree mysite_env/
mysite_env/
├── DJANGO_SETTINGS_MODULE
├── DJANGO_DEBUG
├── DJANGO_DATABASE_URL
├── DJANGO_CACHE_URL
└── PYTHONSTARTUP
0 directories, 3 files
$ cat mysite_env/DJANGO_CACHE_URL
redis://user@host:port/1
$
Then, to enable the ``mysite_env`` environment variables, simply use the
``envdir`` command line tool as a prefix for your program, e.g.::
$ envdir mysite_env python manage.py runserver
See envdir_ documentation for more information, e.g. using envdir_ from
Python instead of from the command line.
.. _envdir: https://pypi.python.org/pypi/envdir

View file

@ -53,161 +53,16 @@ behind the scenes.
.. _`PEP 302`: http://www.python.org/dev/peps/pep-0302/
Usage patterns
--------------
Further documentation
---------------------
There are various configuration patterns that can be implemented with
django-configurations. The most common pattern is to have a base class
and various subclasses based on the enviroment they are supposed to be
used in, e.g. in production, staging and development.
.. toctree::
:maxdepth: 3
Server specific settings
^^^^^^^^^^^^^^^^^^^^^^^^
For example, imagine you have a base setting class in your **settings.py**
file::
from configurations import Settings
class Base(Settings):
TIME_ZONE = 'Europe/Berlin'
class Dev(Base):
DEBUG = True
TEMPLATE_DEBUG = DEBUG
class Prod(Base):
TIME_ZONE = 'America/New_York'
You can now set the ``DJANGO_CONFIGURATION`` environment variable to one
of the class names you've defined, e.g. on your production server it
should be ``Prod``. In bash that would be::
export DJANGO_SETTINGS_MODULE=mysite.settings
export DJANGO_CONFIGURATION=Prod
python manage.py runserver
Alternatively you can use the ``--configuration`` option when using Django
management commands along the lines of Django's default ``--settings``
command line option, e.g.::
python manage.py runserver --settings=mysite.settings --configuration=Prod
Global settings defaults
^^^^^^^^^^^^^^^^^^^^^^^^
Every ``configurations.Settings`` subclass will automatically contain
Django's global settings as class attributes, so you can refer to them when
setting other values, e.g.::
from configurations import Settings
class Prod(Settings):
TEMPLATE_CONTEXT_PROCESSORS = Settings.TEMPLATE_CONTEXT_PROCESSORS + (
'django.core.context_processors.request',
)
@property
def LANGUAGES(self):
return Settings.LANGUAGES + (('tlh', 'Klingon'),)
Mixins
^^^^^^
You might want to apply some configuration values for each and every
project you're working on without having to repeat yourself. Just define
a few mixin you re-use multiple times::
class FullPageCaching(object):
USE_ETAGS = True
Then import that mixin class in your site settings module and use it with
a Settings class::
from configurations import Settings
class Prod(Settings, FullPageCaching):
DEBUG = False
# ...
Pristine methods
^^^^^^^^^^^^^^^^
.. versionadded:: 0.3
In case one of your settings itself need to be a callable, you need to
tell that django-configurations by using the ``pristinemethod`` decorator,
e.g.::
from configurations import Settings, pristinemethod
class Prod(Settings):
@pristinemethod
def ACCESS_FUNCTION(user):
return user.is_staff
Lambdas work, too::
from configurations import Settings, pristinemethod
class Prod(Settings):
ACCESS_FUNCTION = pristinemethod(lambda user: user.is_staff)
Setup methods
^^^^^^^^^^^^^
.. versionadded:: 0.3
If there is something required to be set up before or after the settings
loading happens, please override the ``pre_setup`` or ``post_setup``
class methods like so (don't forget to apply the Python ``@classmethod``
decorator::
from configurations import Settings
class Prod(Settings):
# ...
@classmethod
def pre_setup(cls):
if something.completely.different():
cls.DEBUG = True
@classmethod
def post_setup(cls):
print("done setting up! \o/")
As you can see above the ``pre_setup`` method can also be used to
programmatically change a class attribute of the settings class and it
will be taken into account when doing the rest of the settings setup.
Of course that won't work for ``post_setup`` since that's when the
settings setup is already done.
In fact you can easily do something unrelated to settings, like
connecting to a database::
from configurations import Settings
class Prod(Settings):
# ...
@classmethod
def post_setup(cls):
import mango
mango.connect('enterprise')
.. warning::
You could do the same by overriding the ``__init__`` method of your
settings class but this may cause hard to debug errors because
at the time the ``__init__`` method is called (during Django startup)
the Django setting system isn't fully loaded yet.
So anything you do in ``__init__`` that may require
``django.conf.settings`` or Django models there is a good chance it
won't work. Use the ``post_setup`` method for that instead.
patterns
values
cookbook
changes
Alternatives
------------
@ -224,54 +79,15 @@ Many thanks to those project that have previously solved these problems:
.. _Pinax: http://pinaxproject.com
.. _`django-classbasedsettings`: https://github.com/matthewwithanm/django-classbasedsettings
Cookbook
--------
Celery
^^^^^^
Given Celery's way to load Django settings in worker processes you should
probably just add the following to the **begin** of your settings module::
from configurations import importer
importer.install()
That has the same effect as using the ``manage.py`` or ``wsgi.py`` utilities
mentioned above.
FastCGI
^^^^^^^
In case you use FastCGI for deploying Django (you really shouldn't) and aren't
allowed to us Django's runfcgi_ management command (that would automatically
handle the setup for your if you've followed the quickstart guide above), make
sure to use something like the following script::
#!/usr/bin/env python
import os
import sys
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
os.environ.setdefault('DJANGO_CONFIGURATION', 'MySiteSettings')
from configurations.fastcgi import runfastcgi
runfastcgi(method='threaded', daemonize='true')
As you can see django-configurations provides a helper module
``configurations.fastcgi`` that handles the setup of your configurations.
.. _runfcgi: https://docs.djangoproject.com/en/1.5/howto/deployment/fastcgi/
Bugs and feature requests
-------------------------
As always you mileage may vary, so please don't hesitate to send in feature
requests and bug reports at the usual place:
As always your mileage may vary, so please don't hesitate to send feature
requests and bug reports:
https://github.com/jezdez/django-configurations/issues
Thanks!
Thanks! Feel free to leave a tip, too:
.. include:: ../CHANGES.rst
https://www.gittip.com/jezdez/

167
docs/patterns.rst Normal file
View file

@ -0,0 +1,167 @@
Usage patterns
==============
There are various configuration patterns that can be implemented with
django-configurations. The most common pattern is to have a base class
and various subclasses based on the enviroment they are supposed to be
used in, e.g. in production, staging and development.
Server specific settings
------------------------
For example, imagine you have a base setting class in your **settings.py**
file::
from configurations import Configuration
class Base(Configuration):
TIME_ZONE = 'Europe/Berlin'
class Dev(Base):
DEBUG = True
TEMPLATE_DEBUG = DEBUG
class Prod(Base):
TIME_ZONE = 'America/New_York'
You can now set the ``DJANGO_CONFIGURATION`` environment variable to one
of the class names you've defined, e.g. on your production server it
should be ``Prod``. In bash that would be::
export DJANGO_SETTINGS_MODULE=mysite.settings
export DJANGO_CONFIGURATION=Prod
python manage.py runserver
Alternatively you can use the ``--configuration`` option when using Django
management commands along the lines of Django's default ``--settings``
command line option, e.g.::
python manage.py runserver --settings=mysite.settings --configuration=Prod
Global settings defaults
------------------------
Every ``configurations.Configuration`` subclass will automatically contain
Django's global settings as class attributes, so you can refer to them when
setting other values, e.g.::
from configurations import Configuration
class Prod(Configuration):
TEMPLATE_CONTEXT_PROCESSORS = Configuration.TEMPLATE_CONTEXT_PROCESSORS + (
'django.core.context_processors.request',
)
@property
def LANGUAGES(self):
return Configuration.LANGUAGES + (('tlh', 'Klingon'),)
Configuration mixins
--------------------
You might want to apply some configuration values for each and every
project you're working on without having to repeat yourself. Just define
a few mixin you re-use multiple times::
class FullPageCaching(object):
USE_ETAGS = True
Then import that mixin class in your site settings module and use it with
a ``Configuration`` class::
from configurations import Configuration
class Prod(Configuration, FullPageCaching):
DEBUG = False
# ...
Pristine methods
----------------
.. versionadded:: 0.3
In case one of your settings itself need to be a callable, you need to
tell that django-configurations by using the ``pristinemethod`` decorator,
e.g.::
from configurations import Configuration, pristinemethod
class Prod(Configuration):
@pristinemethod
def ACCESS_FUNCTION(user):
return user.is_staff
Lambdas work, too::
from configurations import Configuration, pristinemethod
class Prod(Configuration):
ACCESS_FUNCTION = pristinemethod(lambda user: user.is_staff)
Setup methods
-------------
.. versionadded:: 0.3
If there is something required to be set up before, during or after the
settings loading happens, please override the ``pre_setup``, ``setup`` or
``post_setup`` class methods like so (don't forget to apply the Python
``@classmethod`` decorator)::
import logging
from configurations import Configuration
class Prod(Configuration):
# ...
@classmethod
def pre_setup(cls):
if something.completely.different():
cls.DEBUG = True
@classmethod
def setup(cls):
super(Prod, cls).setup()
logging.info('production settings loaded: %s', cls)
@classmethod
def post_setup(cls):
logging.debug("done setting up! \o/")
As you can see above the ``pre_setup`` method can also be used to
programmatically change a class attribute of the settings class and it
will be taken into account when doing the rest of the settings setup.
Of course that won't work for ``post_setup`` since that's when the
settings setup is already done.
In fact you can easily do something unrelated to settings, like
connecting to a database::
from configurations import Configuration
class Prod(Configuration):
# ...
@classmethod
def post_setup(cls):
import mango
mango.connect('enterprise')
.. warning::
You could do the same by overriding the ``__init__`` method of your
settings class but this may cause hard to debug errors because
at the time the ``__init__`` method is called (during Django startup)
the Django setting system isn't fully loaded yet.
So anything you do in ``__init__`` that may require
``django.conf.settings`` or Django models there is a good chance it
won't work. Use the ``post_setup`` method for that instead.
.. versionchanged:: 0.4
A new ``setup`` method was added to be able to handle the new
:class:`~configurations.values.Value` classes and allow a in-between
modification of the configuration values.

458
docs/values.rst Normal file
View file

@ -0,0 +1,458 @@
Values
======
.. module:: configurations.values
:synopsis: Optional value classes for high-level validation and behavior.
.. versionadded:: 0.4
django-configurations allows you to optionally reduce the amount of validation
and setup code in your **settings.py** by using ``Value`` classes. They have
the ability to handle values from the process environment of your software
(:data:`os.environ`) and work well in projects that follow the
`Twelve-Factor methodology`_.
Overview
--------
Here is an example (from a **settings.py**)::
from configurations import values
DEBUG = values.BooleanValue(True)
As you can see all you have to do is to wrap your settings value in a call
to one of the included settings classes. When Django's process starts up
it will automatically make sure the passed in value validates correctly --
in the above case checks if the value is really a boolean.
You can safely use other :class:`~Value` instances as the default setting
value::
from configurations import values
DEBUG = values.BooleanValue(True)
TEMPLATE_DEBUG = values.BooleanValue(DEBUG)
See the list of built-in value classes for more information.
Environment variables
---------------------
To separate configuration from your application you should use environment
variables to override settings values if needed. Unfortunately environment
variables are string based so they are not easily mapped to the Python based
settings system Django uses.
Luckily django-configurations' :class:`~Value` subclasses have the ability
to handle environment variables for the most common use cases.
For example, imagine you'd like to override the ``TEMPLATE_DEBUG`` setting
on your staging server to be able to debug a problem with your in-development
code. You're using a web server that passes the environment variables from
the shell it was started in to your Django WSGI process.
First make sure you set the ``environ`` option of the :class:`~Value` instance
to ``True``::
from configurations import values
# ..
TEMPLATE_DEBUG = values.BooleanValue(True, environ=True)
That will tell django-configurations to look for a environment variable
named ``DJANGO_TEMPLATE_DEBUG`` when deciding which value of the ``DEBUG``
setting to actually enable.
When you run your web server simply specify that environment variable
(e.g. in your init script)::
DJANGO_TEMPLATE_DEBUG=true gunicorn mysite.wsgi:application
Since environment variables are string based the ``BooleanValue`` supports
a series of possible formats for a boolean value (``true``, ``yes``,
``y`` and ``1``, all in capital and lower case, and ``false``, ``no``,
``n`` and ``0`` of course). So for example this will work, too::
DJANGO_TEMPLATE_DEBUG=no ./manage.py runserver
``Value`` class
---------------
.. class:: Value(default, [environ=False, environ_name=None, environ_prefix='DJANGO'])
The ``Value`` class takes one required and several optional parameters.
:param default: the default value of the setting
:param environ: toggle for environment use
:param environ_name: name of environment variable to look for
:param environ_prefix: prefix to use when looking for environment variable
:type environ: bool
:type environ_name: capitalized string or None
:type environ_prefix: capitalized string
The ``default`` parameter is effectively the value the setting has
right now in your ``settings.py``.
.. method:: setup(name)
:param name: the name of the setting
:return: setting value
The ``setup`` method is called during startup of the Django process and
implements the ability to check the environment variable. Its purpose is
to return a value django-configrations is supposed to use when loading
the settings. It'll be passed one parameter, the name of the
:class:`~Value` instance as defined in the ``settings.py``. This is used
for building the name of the environment variable.
.. method:: to_python(value)
:param value: the value of the setting as found in the process
environment (:data:`os.environ`)
:return: validated and "ready" setting value if found in process
environment
The ``to_python`` method is only used when the ``environ`` parameter
of the :class:`~Value` class is set to ``True`` and an environment
variable with the appropriate name was found. It will be used to handle
the string based environment variable values and returns the "ready"
value to be returned by the ``setup`` method.
Built-ins
---------
Type values
^^^^^^^^^^^
.. class:: BooleanValue
A :class:`~Value` subclass that checks and returns boolean values. Possible
values for environment variables are:
- ``True`` values: ``'yes'``, ``'y'``, ``'true'``, ``'1'``
- ``False`` values: ``'no'``, ``'n'``, ``'false'``, ``'0'``,
``''`` (empty string)
::
DEBUG = values.BooleanValue(True)
.. class:: IntegerValue
A :class:`~Value` subclass that handles integer values.
::
MYSITE_CACHE_TIMEOUT = values.BooleanValue(3600)
.. class:: FloatValue
A :class:`~Value` subclass that handles float values.
::
MYSITE_TAX_RATE = values.FloatValue(11.9)
.. class:: DecimalValue
A :class:`~Value` subclass that handles Decimal values.
::
MYSITE_CONVERSION_RATE = values.DecimalValue(decimal.Decimal('4.56214'))
.. class:: ListValue(default, [separator=',', converter=None])
A :class:`~Value` subclass that handles list values.
:param separator: the separator to split environment variables with
:param converter: the optional converter callable to apply for each list
item
Simple example::
ALLOWED_HOSTS = ListValue(['mysite.com', 'mysite.biz'])
Use a custom converter to check for the given variables::
def check_monty_python(person):
if not is_completely_different(person):
raise ValueError('{0} is not a Monty Python member'.format(person))
return person
MONTY_PYTHONS = ListValue(['John Cleese', 'Eric Idle'],
converter=check_monty_python,
environ=True)
You can override this list with an environment variable like this::
DJANGO_MONTY_PYTHONS="Terry Jones,Graham Chapman" gunicorn mysite.wsgi:application
Use a custom separator::
EMERGENCY_EMAILS = ListValue(['admin@mysite.net'],
separator=';', environ=True)
And override it::
DJANGO_EMERGENCY_EMAILS="admin@mysite.net;manager@mysite.org;support@mysite.com" gunicorn mysite.wsgi:application
.. class:: TupleValue
A :class:`~Value` subclass that handles tuple values.
:param separator: the separator to split environment variables with
:param converter: the optional converter callable to apply for each tuple
item
See the :class:`~ListValue` examples above.
.. class:: SetValue
A :class:`~Value` subclass that handles set values.
:param separator: the separator to split environment variables with
:param converter: the optional converter callable to apply for each set
item
See the :class:`~ListValue` examples above.
.. class:: DictValue
Validator values
^^^^^^^^^^^^^^^^
.. class:: EmailValue
A :class:`~Value` subclass that validates the value using the
:data:`django:django.core.validators.validate_email` validator.
::
SUPPORT_EMAIL = values.EmailValue('support@mysite.com')
.. class:: URLValue
A :class:`~Value` subclass that validates the value using the
:class:`django:django.core.validators.URLValidator` validator.
::
SUPPORT_URL = values.URLValue('https://support.mysite.com/')
.. class:: IPValue
A :class:`~Value` subclass that validates the value using the
:data:`django:django.core.validators.validate_ipv46_address` validator.
::
LOADBALANCER_IP = values.IPValue('127.0.0.1')
.. class:: RegexValue(default, regex, [environ=False, environ_name=None, environ_prefix='DJANGO'])
A :class:`~Value` subclass that validates according a regular expression
and uses the :class:`django:django.core.validators.RegexValidator`.
:param regex: the regular expression
::
DEFAULT_SKU = values.RegexValue('000-000-00', regex=r'\d{3}-\d{3}-\d{2}')
.. class:: PathValue(default, [check_exists=True, environ=False, environ_name=None, environ_prefix='DJANGO'])
A :class:`~Value` subclass that normalizes the given path using
:func:`os.path.expanduser` and validates if it exists on the file system.
Takes an optional ``check_exists`` parameter to disable the check with
:func:`os.path.exists`.
:param check_exists: toggle the file system check
::
BASE_DIR = values.PathValue('/opt/mysite/')
URL-based values
^^^^^^^^^^^^^^^^
.. note::
The following URL-based :class:`~Value` subclasses behave slightly different
to the rest of the classes here. They are inspired by the
`Twelve-Factor methodology`_ and by default inspect the :data:`os.environ`
using a previously established environment variable name, e.g.
``DATABASE_URL``.
Each of them require having an external library installed, e.g. the
:class:`~DatabaseURLValue` class depends on the package ``dj-database-url``.
.. class:: DatabaseURLValue(default, [alias='default', environ=True, environ_name='DATABASE_URL', environ_prefix=None])
A :class:`~Value` subclass that uses the `dj-database-url`_ app to
convert a database configuration value stored in the ``DATABASE_URL``
environment variable into an appropriate setting value. It's inspired by
the `Twelve-Factor methodology`_.
By default this :class:`~Value` subclass looks for the ``DATABASE_URL``
environment variable.
Takes an optional ``alias`` parameter to define which database alias to
use for the ``DATABASES`` setting.
:param alias: which database alias to use
The other parameters have the following default values:
:param environ: ``True``
:param environ_name: ``DATABASE_URL``
:param environ_prefix: ``None``
::
DATABASES = values.DatabaseURLValue('postgres://myuser@localhost/mydb')
.. _`dj-database-url`: https://pypi.python.org/pypi/dj-database-url/
.. class:: CacheURLValue(default, [alias='default', environ=True, environ_name='CACHE_URL', environ_prefix=None])
A :class:`~Value` subclass that uses the `django-cache-url`_ app to
convert a cache configuration value stored in the ``CACHE_URL``
environment variable into an appropriate setting value. It's inspired by
the `Twelve-Factor methodology`_.
By default this :class:`~Value` subclass looks for the ``CACHE_URL``
environment variable.
Takes an optional ``alias`` parameter to define which database alias to
use for the ``CACHES`` setting.
:param alias: which cache alias to use
The other parameters have the following default values:
:param environ: ``True``
:param environ_name: ``CACHE_URL``
:param environ_prefix: ``None``
::
CACHES = values.CacheURLValue('memcached://127.0.0.1:11211/')
.. _`django-cache-url`: https://pypi.python.org/pypi/django-cache-url/
.. class:: EmailURLValue(default, [environ=True, environ_name='EMAIL_URL', environ_prefix=None])
A :class:`~Value` subclass that uses the `dj-email-url`_ app to
convert an email configuration value stored in the ``EMAIL_URL``
environment variable into the appropriate settings. It's inspired by
the `Twelve-Factor methodology`_.
By default this :class:`~Value` subclass looks for the ``EMAIL_URL``
environment variable.
.. note::
This is a special value since email settings are divided into many
different settings. `dj-email-url`_ supports all options though and
simply returns a nested dictionary of settings instead of just one
setting.
The parameters have the following default values:
:param environ: ``True``
:param environ_name: ``EMAIL_URL``
:param environ_prefix: ``None``
::
EMAIL_URL = values.EmailURLValue('console://')
.. _`dj-email-url`: https://pypi.python.org/pypi/dj-email-url/
Other values
^^^^^^^^^^^^
.. class:: BackendsValue
A :class:`~ListValue` subclass that validates the given list of dotted
import paths by trying to import them. In other words, this checks if
the backends exist.
::
MIDDLEWARE_CLASSES = values.BackendsValue([
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
])
.. class:: SecretValue
A :class:`~Value` subclass that doesn't allow setting a default value
during instantiation and force-enables the use of an environment variable
to reduce the risk of accidentally storing secret values in the settings
file.
:raises: ``ValueError`` when given a default value
::
SECRET_KEY = values.SecretValue()
Value mixins
^^^^^^^^^^^^
.. class:: CastingMixin
A mixin to be used with one of the :class:`~Value` subclasses that
requires a ``caster`` class attribute of one of the following types:
- dotted import path, e.g. ``'mysite.utils.custom_caster'``
- a callable, e.g. :func:`int`
Example::
class TemparatureValue(CastingMixin, Value):
caster = 'mysite.temperature.fahrenheit_to_celcius'
Optionally it can take a ``message`` class attribute as the error
message to be shown if the casting fails. Additionally an ``exception``
parameter can be set to a single or a tuple of exception classes that
are required to be handled during the casting.
.. class:: ValidationMixin
A mixin to be used with one of the :class:`~Value` subclasses that
requires a ``validator`` class attribute of one of the following types:
The validator should raise Django's
:exc:`~django.core.exceptions.ValidationError` to indicate a failed
validation attempt.
- dotted import path, e.g. ``'mysite.validators.custom_validator'``
- a callable, e.g. :func:`bool`
Example::
class TemparatureValue(ValidationMixin, Value):
validator = 'mysite.temperature.is_valid_temparature'
Optionally it can take a ``message`` class attribute as the error
message to be shown if the validation fails.
.. class:: MultipleMixin
A mixin to be used with one of the :class:`~Value` subclasses that
enables the return value of the :func:`~Value.to_python` to be
interpreted as a dictionary of settings values to be set at once,
instead of using the return value to just set one setting.
A good example for this mixin is the :class:`~EmailURLValue` value
which requires setting many ``EMAIL_*`` settings.
.. _`Twelve-Factor methodology`: http://www.12factor.net/

View file

@ -1,4 +1,7 @@
flake8
coverage
django-discover-runner
mock
mock
dj-database-url
dj-email-url
django-cache-url

View file

@ -6,7 +6,6 @@ author-email = jannis@leidel.info
summary = A helper for organizing Django settings.
description-file = README.rst
license = BSD
requires-dist = six
home-page = http://django-configurations.readthedocs.org/
project-url =
Github, https://github.com/jezdez/django-configurations/

View file

@ -1,16 +1,18 @@
from configurations import Settings
from configurations import Configuration, values
class Base(Settings):
class Base(Configuration):
# Django settings for test_project project.
DEBUG = True
DEBUG = values.BooleanValue(True, environ=True)
TEMPLATE_DEBUG = DEBUG
ADMINS = (
# ('Your Name', 'your_email@example.com'),
)
EMAIL_URL = values.EmailURLValue('console://', environ=True)
MANAGERS = ADMINS
DATABASES = {