diff --git a/configurations/tests/test_values.py b/configurations/tests/test_values.py index 15f59cc..99a4688 100644 --- a/configurations/tests/test_values.py +++ b/configurations/tests/test_values.py @@ -30,22 +30,22 @@ class FailingCasterValue(CastingMixin, Value): class ValueTests(TestCase): def test_value(self): - value = Value('default') + value = Value('default', environ=False) 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) + value = Value('default') 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) + value1 = Value('default') + value2 = Value(value1) self.assertEqual(value1.setup('TEST1'), 'default') self.assertEqual(value2.setup('TEST2'), 'default') with env(DJANGO_TEST1='override1', DJANGO_TEST2='override2'): @@ -54,15 +54,15 @@ class ValueTests(TestCase): def test_env_var_prefix(self): with patch.dict(os.environ, clear=True, ACME_TEST='override'): - value = Value('default', environ=True, environ_prefix='ACME') + value = Value('default', 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='') + value = Value('default', environ_prefix='') self.assertEqual(value.setup('TEST'), 'override') def test_boolean_values_true(self): - value = BooleanValue(False, environ=True) + value = BooleanValue(False) for truthy in value.true_values: with env(DJANGO_TEST=truthy): self.assertTrue(value.setup('TEST')) @@ -71,32 +71,32 @@ class ValueTests(TestCase): self.assertRaises(ImproperlyConfigured, BooleanValue, 'false') def test_boolean_values_false(self): - value = BooleanValue(True, environ=True) + value = BooleanValue(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) + value = BooleanValue(True) with env(DJANGO_TEST='nonboolean'): self.assertRaises(ImproperlyConfigured, value.setup, 'TEST') def test_integer_values(self): - value = IntegerValue(1, environ=True) + value = IntegerValue(1) with env(DJANGO_TEST='2'): self.assertEqual(value.setup('TEST'), 2) with env(DJANGO_TEST='noninteger'): self.assertRaises(ImproperlyConfigured, value.setup, 'TEST') def test_float_values(self): - value = FloatValue(1.0, environ=True) + value = FloatValue(1.0) with env(DJANGO_TEST='2.0'): self.assertEqual(value.setup('TEST'), 2.0) with env(DJANGO_TEST='noninteger'): self.assertRaises(ImproperlyConfigured, value.setup, 'TEST') def test_decimal_values(self): - value = DecimalValue(decimal.Decimal(1), environ=True) + value = DecimalValue(decimal.Decimal(1)) with env(DJANGO_TEST='2'): self.assertEqual(value.setup('TEST'), decimal.Decimal(2)) with env(DJANGO_TEST='nondecimal'): @@ -106,7 +106,7 @@ class ValueTests(TestCase): self.assertRaises(ImproperlyConfigured, FailingCasterValue) def test_list_values_default(self): - value = ListValue(environ=True) + value = ListValue() with env(DJANGO_TEST='2,2'): self.assertEqual(value.setup('TEST'), ['2', '2']) with env(DJANGO_TEST='2, 2 ,'): @@ -115,32 +115,32 @@ class ValueTests(TestCase): self.assertEqual(value.setup('TEST'), []) def test_list_values_separator(self): - value = ListValue(environ=True, separator=':') + value = ListValue(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) + value = ListValue(converter=int) with env(DJANGO_TEST='2,2'): self.assertEqual(value.setup('TEST'), [2, 2]) - value = ListValue(environ=True, converter=float) + value = ListValue(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) + value = ListValue(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) + value = ListValue(converter=int) with env(DJANGO_TEST='2,b'): self.assertRaises(ImproperlyConfigured, value.setup, 'TEST') def test_tuple_values_default(self): - value = TupleValue(environ=True) + value = TupleValue() with env(DJANGO_TEST='2,2'): self.assertEqual(value.setup('TEST'), ('2', '2')) with env(DJANGO_TEST='2, 2 ,'): @@ -149,7 +149,7 @@ class ValueTests(TestCase): self.assertEqual(value.setup('TEST'), ()) def test_set_values_default(self): - value = SetValue(environ=True) + value = SetValue() with env(DJANGO_TEST='2,2'): self.assertEqual(value.setup('TEST'), set(['2', '2'])) with env(DJANGO_TEST='2, 2 ,'): @@ -158,7 +158,7 @@ class ValueTests(TestCase): self.assertEqual(value.setup('TEST'), set()) def test_dict_values_default(self): - value = DictValue(environ=True) + value = DictValue() with env(DJANGO_TEST='{2: 2}'): self.assertEqual(value.setup('TEST'), {2: 2}) expected = {2: 2, '3': '3', '4': [1, 2, 3]} @@ -176,21 +176,21 @@ class ValueTests(TestCase): self.assertRaises(ImproperlyConfigured, value.setup, 'TEST') def test_email_values(self): - value = EmailValue('spam@eg.gs', environ=True) + value = EmailValue('spam@eg.gs') with env(DJANGO_TEST='spam@sp.am'): self.assertEqual(value.setup('TEST'), 'spam@sp.am') with env(DJANGO_TEST='spam'): self.assertRaises(ImproperlyConfigured, value.setup, 'TEST') def test_url_values(self): - value = URLValue('http://eggs.spam', environ=True) + value = URLValue('http://eggs.spam') with env(DJANGO_TEST='http://spam.eggs'): self.assertEqual(value.setup('TEST'), 'http://spam.eggs') with env(DJANGO_TEST='httb://spam.eggs'): self.assertRaises(ImproperlyConfigured, value.setup, 'TEST') def test_ip_values(self): - value = IPValue('0.0.0.0', environ=True) + value = IPValue('0.0.0.0') with env(DJANGO_TEST='127.0.0.1'): self.assertEqual(value.setup('TEST'), '127.0.0.1') with env(DJANGO_TEST='::1'): @@ -199,14 +199,14 @@ class ValueTests(TestCase): self.assertRaises(ImproperlyConfigured, value.setup, 'TEST') def test_regex_values(self): - value = RegexValue('000--000', environ=True, regex=r'\d+--\d+') + value = RegexValue('000--000', regex=r'\d+--\d+') with env(DJANGO_TEST='123--456'): self.assertEqual(value.setup('TEST'), '123--456') with env(DJANGO_TEST='123456'): self.assertRaises(ImproperlyConfigured, value.setup, 'TEST') def test_path_values_with_check(self): - value = PathValue(environ=True) + value = PathValue() with env(DJANGO_TEST='/'): self.assertEqual(value.setup('TEST'), '/') with env(DJANGO_TEST='~/'): @@ -215,7 +215,7 @@ class ValueTests(TestCase): self.assertRaises(ImproperlyConfigured, value.setup, 'TEST') def test_path_values_no_check(self): - value = PathValue(environ=True, check_exists=False) + value = PathValue(check_exists=False) with env(DJANGO_TEST='/'): self.assertEqual(value.setup('TEST'), '/') with env(DJANGO_TEST='~/spam/eggs'): @@ -233,7 +233,7 @@ class ValueTests(TestCase): self.assertEqual(value.setup('SECRET_KEY'), '123') def test_database_url_value(self): - value = DatabaseURLValue(environ=True) + value = DatabaseURLValue() self.assertEqual(value.default, {}) with env(DATABASE_URL='sqlite://'): self.assertEqual(value.setup('DATABASE_URL'), { @@ -247,7 +247,7 @@ class ValueTests(TestCase): }}) def test_email_url_value(self): - value = EmailURLValue(environ=True) + value = EmailURLValue() self.assertEqual(value.default, {}) with env(EMAIL_URL='smtps://user@domain.com:password@smtp.example.com:587'): self.assertEqual(value.setup('EMAIL_URL'), { @@ -271,7 +271,7 @@ class ValueTests(TestCase): self.assertRaises(ImproperlyConfigured, value.setup, 'TEST') def test_cache_url_value(self): - value = CacheURLValue(environ=True) + value = CacheURLValue() self.assertEqual(value.default, {}) with env(CACHE_URL='redis://user@host:port/1'): self.assertEqual(value.setup('CACHE_URL'), { diff --git a/configurations/values.py b/configurations/values.py index 6d05afe..f2d633c 100644 --- a/configurations/values.py +++ b/configurations/values.py @@ -28,7 +28,7 @@ class Value(object): """ multiple = False - def __init__(self, default=None, environ=False, environ_name=None, + def __init__(self, default=None, environ=True, environ_name=None, environ_prefix='DJANGO', *args, **kwargs): if isinstance(default, Value): self.default = copy.copy(default.default) diff --git a/docs/values.rst b/docs/values.rst index b403e8d..7baeb84 100644 --- a/docs/values.rst +++ b/docs/values.rst @@ -22,8 +22,8 @@ Here is an example (from a **settings.py**):: 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 -- +to one of the included values 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 @@ -47,39 +47,67 @@ 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. +Default behavior +^^^^^^^^^^^^^^^^ -First make sure you set the ``environ`` option of the :class:`~Value` instance -to ``True``:: +For example, imagine you'd like to override the ``ROOT_URLCONF`` setting +on your staging server to be able to debug a problem with your in-development +code. You're also using a web server that passes the environment variables from +the shell it was started from into your Django WSGI process. + +Each :class:`~Value` class instance has an ``environ`` option, that when set to +``True`` (default) django-configurations will look for an uppercase +environment variable named like the :class:`~Value` instance's name, prefixed +with ``DJANGO_``. So imagine the following example:: from configurations import values # .. - TEMPLATE_DEBUG = values.BooleanValue(True, environ=True) + ROOT_URLCONF = values.Value('mysite.urls') -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. +django-configurations will try to read the ``DJANGO_ROOT_URLCONF`` environment +variable when deciding which value the ``ROOT_URLCONF`` setting should have. 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 + DJANGO_ROOT_URLCONF=mysite.debugging_urls 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:: +Disabling environment variables +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - DJANGO_TEMPLATE_DEBUG=no ./manage.py runserver +To disable django-configurations' automatic use of environment variables, +you can specify the ``environ`` parameter of the :class:`~Value` class. +For example this would disable it for the ``TIME_ZONE`` setting value:: + + TIME_ZONE = values.Value('UTC', environ=False) + +Custom environment variable names +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To support legacy systems, integrate with other parts of your sofware stack or +simply better match your taste in naming public configuration variables, +django-configurations allows you to use the ``environ_name`` parameter of the +:class:`~Value` class to change the name of the environment variable it looks +for. For example this would enforce a specific environment variable name +instead of using the name of the :class:`~Value` instance.:: + + TIME_ZONE = values.Value('UTC', environ_name='MYSITE_TZ') + +Custom environment variable prefixes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In case you just want to change the default environment variable name prefix +of ``DJANGO`` to something to your likening, use the ``environ_prefix`` +parameter of the :class:`~Value` instance. Here it'll look for the +``MYSITE_TIME_ZONE`` environment variable (instead of ``DJANGO_TIME_ZONE``):: + + TIME_ZONE = values.Value('UTC', environ_prefix='MYSITE') ``Value`` class --------------- -.. class:: Value(default, [environ=False, environ_name=None, environ_prefix='DJANGO']) +.. class:: Value(default, [environ=True, environ_name=None, environ_prefix='DJANGO']) The ``Value`` class takes one required and several optional parameters. @@ -114,10 +142,10 @@ a series of possible formats for a boolean value (``true``, ``yes``, 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. + of the :class:`~Value` class is set to ``True`` (the default) 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 --------- @@ -185,8 +213,7 @@ Type values return person MONTY_PYTHONS = ListValue(['John Cleese', 'Eric Idle'], - converter=check_monty_python, - environ=True) + converter=check_monty_python) You can override this list with an environment variable like this:: @@ -194,8 +221,7 @@ Type values Use a custom separator:: - EMERGENCY_EMAILS = ListValue(['admin@mysite.net'], - separator=';', environ=True) + EMERGENCY_EMAILS = ListValue(['admin@mysite.net'], separator=';') And override it:: @@ -253,7 +279,7 @@ Validator values LOADBALANCER_IP = values.IPValue('127.0.0.1') -.. class:: RegexValue(default, regex, [environ=False, environ_name=None, environ_prefix='DJANGO']) +.. class:: RegexValue(default, regex, [environ=True, 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`. @@ -264,7 +290,7 @@ Validator values 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']) +.. class:: PathValue(default, [check_exists=True, environ=True, 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. diff --git a/manage.py b/manage.py index 91fb9ca..6d47d54 100755 --- a/manage.py +++ b/manage.py @@ -3,7 +3,8 @@ import os import sys if __name__ == "__main__": - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'configurations.tests.settings.main') + os.environ.setdefault('DJANGO_SETTINGS_MODULE', + 'configurations.tests.settings.main') os.environ.setdefault('DJANGO_CONFIGURATION', 'Test') from configurations.management import execute_from_command_line