diff --git a/configurations/values.py b/configurations/values.py index 1e51bdb..eaabc03 100644 --- a/configurations/values.py +++ b/configurations/values.py @@ -14,10 +14,10 @@ from .utils import import_by_path def setup_value(target, name, value): actual_value = value.setup(name) # overwriting the original Value class with the result - setattr(target, name, actual_value) + setattr(target, name, value.value) if value.multiple: for multiple_name, multiple_value in actual_value.items(): - setattr(target, multiple_name, multiple_value) + setattr(target, multiple_name, multiple_value.value) class Value(object): @@ -26,10 +26,40 @@ class Value(object): and implements a simple validation scheme. """ multiple = False + late_binding = False + + @property + def value(self): + value = self.default + if not hasattr(self, '_value') and self.environ_name: + self.setup(self.environ_name) + if hasattr(self, '_value'): + value = self._value + return value + + @value.setter + def value(self, value): + self._value = value + + def __new__(cls, *args, **kwargs): + """ + checks if the creation can end up directly in the final value. + That is the case whenever environ = False or environ_name is given + """ + instance = object.__new__(cls) + instance.__init__(*args, **kwargs) + if not instance.late_binding: + if (instance.environ and instance.environ_name) \ + or (not instance.environ and instance.default): + instance = instance.setup(instance.environ_name) + + return instance def __init__(self, default=None, environ=True, environ_name=None, environ_prefix='DJANGO', *args, **kwargs): - if isinstance(default, Value): + if 'late_binding' in kwargs: + self.late_binding = kwargs.get('late_binding') + if isinstance(default, Value) and default.default: self.default = copy.copy(default.default) else: self.default = default @@ -39,13 +69,16 @@ class Value(object): self.environ_prefix = environ_prefix self.environ_name = environ_name + def __str__(self): + return str(self.value) + def __repr__(self): - return "".format(self.default) + return repr(self.value) def setup(self, name): value = self.default if self.environ: - if self.environ_name is None: + if not self.environ_name: environ_name = name.upper() else: environ_name = self.environ_name @@ -56,6 +89,7 @@ class Value(object): full_environ_name = environ_name if full_environ_name in os.environ: value = self.to_python(os.environ[full_environ_name]) + self.value = value return value def to_python(self, value): @@ -299,6 +333,7 @@ class SecretValue(Value): class EmailURLValue(CastingMixin, MultipleMixin, Value): caster = 'dj_email_url.parse' message = 'Cannot interpret email URL value {0!r}' + late_binding = True def __init__(self, *args, **kwargs): kwargs.setdefault('environ', True) @@ -334,15 +369,18 @@ class DatabaseURLValue(DictBackendMixin, CastingMixin, Value): caster = 'dj_database_url.parse' message = 'Cannot interpret database URL value {0!r}' environ_name = 'DATABASE_URL' + late_binding = True class CacheURLValue(DictBackendMixin, CastingMixin, Value): caster = 'django_cache_url.parse' message = 'Cannot interpret cache URL value {0!r}' environ_name = 'CACHE_URL' + late_binding = True class SearchURLValue(DictBackendMixin, CastingMixin, Value): caster = 'dj_search_url.parse' message = 'Cannot interpret Search URL value {0!r}' environ_name = 'SEARCH_URL' + late_binding = True diff --git a/tests/test_values.py b/tests/test_values.py index 314529d..849103f 100644 --- a/tests/test_values.py +++ b/tests/test_values.py @@ -29,16 +29,38 @@ class FailingCasterValue(CastingMixin, Value): class ValueTests(TestCase): - def test_value(self): + def test_value_with_default(self): value = Value('default', environ=False) - self.assertEqual(value.setup('TEST'), 'default') + self.assertEqual(type(value), type('default')) + self.assertEqual(value, 'default') + self.assertEqual(str(value), 'default') + + def test_value_with_default_and_late_binding(self): + value = Value('default', environ=False, late_binding=True) + self.assertEqual(type(value), Value) with env(DJANGO_TEST='override'): self.assertEqual(value.setup('TEST'), 'default') + value = Value(environ_name='TEST') + self.assertEqual(type(value), type('override')) + self.assertEqual(value, 'override') + self.assertEqual(str(value), 'override') + self.assertEqual('{0}'.format(value), 'override') + self.assertEqual('%s' % value, 'override') + + value = Value(environ_name='TEST', late_binding=True) + self.assertEqual(type(value), Value) + self.assertEqual(value.value, 'override') + self.assertEqual(str(value), 'override') + self.assertEqual('{0}'.format(value), 'override') + self.assertEqual('%s' % value, 'override') + + self.assertEqual(repr(value), repr('override')) @patch.dict(os.environ, clear=True, DJANGO_TEST='override') def test_env_var(self): value = Value('default') self.assertEqual(value.setup('TEST'), 'override') + self.assertEqual(str(value), 'override') self.assertNotEqual(value.setup('TEST'), value.default) self.assertEqual(value.to_python(os.environ['DJANGO_TEST']), value.setup('TEST')) @@ -61,6 +83,10 @@ class ValueTests(TestCase): value = Value('default', environ_prefix='') self.assertEqual(value.setup('TEST'), 'override') + with patch.dict(os.environ, clear=True, ACME_TEST='override'): + value = Value('default', environ_prefix='ACME_') + self.assertEqual(value.setup('TEST'), 'override') + def test_boolean_values_true(self): value = BooleanValue(False) for truthy in value.true_values: @@ -234,7 +260,8 @@ class ValueTests(TestCase): self.assertEqual(value.setup('SECRET_KEY'), '123') value = SecretValue(environ_name='FACEBOOK_API_SECRET', - environ_prefix=None) + environ_prefix=None, + late_binding=True) self.assertRaises(ValueError, value.setup, 'TEST') with env(FACEBOOK_API_SECRET='123'): self.assertEqual(value.setup('TEST'), '123') @@ -313,3 +340,21 @@ class ValueTests(TestCase): backends = ['non.existing.Backend'] self.assertRaises(ValueError, BackendsValue, backends) + + def test_tuple_value(self): + value = TupleValue(None) + self.assertEqual(value.default, ()) + self.assertEqual(value.value, ()) + + value = TupleValue((1, 2)) + self.assertEqual(value.default, (1, 2)) + self.assertEqual(value.value, (1, 2)) + + def test_set_value(self): + value = SetValue() + self.assertEqual(value.default, set()) + self.assertEqual(value.value, set()) + + value = SetValue([1, 2]) + self.assertEqual(value.default, set([1, 2])) + self.assertEqual(value.value, set([1, 2]))