From 4018f7b42a5e67a79a83ce1d2c53bc30d8b5a69b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sven=20A=C3=9Fmann?= Date: Fri, 2 May 2014 22:51:48 +0200 Subject: [PATCH] - add late_binding to value with default False - this enables a value to load from environment if the env variable name is given at construction time - cache value of environment value now in property value - add __new__ to allow a construction of a given type from environment directly. In this case now Value instance is constructed but an instance of the desired type that is covered by the Value implementation. For Value it is str, for DictValue it is dict etc. - extend tests for this new behavior - extend test coverage for some other places --- configurations/values.py | 48 +++++++++++++++++++++++++++++++++---- tests/test_values.py | 51 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 91 insertions(+), 8 deletions(-) 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]))