- 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
This commit is contained in:
Sven Aßmann 2014-05-02 22:51:48 +02:00
parent 280be2e3e2
commit 4018f7b42a
2 changed files with 91 additions and 8 deletions

View file

@ -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 "<Value default: {0}>".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

View file

@ -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]))