Skip to content

Commit

Permalink
Merge pull request #85 from abbottc/master
Browse files Browse the repository at this point in the history
Add values.TupleOfTuplesValue (plus associated tests and docs)
  • Loading branch information
jezdez committed Feb 13, 2015
2 parents 13980b3 + 8287ab6 commit 8f617ad
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 33 deletions.
104 changes: 75 additions & 29 deletions configurations/values.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,37 +171,97 @@ class DecimalValue(CastingMixin, Value):
exception = decimal.InvalidOperation


class ListValue(Value):
class SequenceValue(Value):
"""
Common code for sequence-type values (lists and tuples).
Do not use this class directly. Instead use a subclass.
"""

# Specify this value in subclasses, e.g. with 'list' or 'tuple'
sequence_type = None
converter = None
message = 'Cannot interpret list item {0!r} in list {1!r}'

def __init__(self, *args, **kwargs):
msg = 'Cannot interpret {0} item {{0!r}} in {0} {{1!r}}'
self.message = msg.format(self.sequence_type.__name__)
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
super(SequenceValue, self).__init__(*args, **kwargs)
# make sure the default is the correct sequence type
if self.default is None:
self.default = []
self.default = self.sequence_type()
else:
self.default = self.sequence_type(self.default)
# initial conversion
if self.converter is not None:
self.default = [self.converter(value) for value in self.default]
self.default = self._convert(self.default)

def _convert(self, sequence):
converted_values = []
for value in sequence:
try:
converted_values.append(self.converter(value))
except (TypeError, ValueError):
raise ValueError(self.message.format(value, value))
return self.sequence_type(converted_values)

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 list(value_list)
if self.converter is not None:
value_list = self._convert(value_list)
return self.sequence_type(value_list)


class ListValue(SequenceValue):
sequence_type = 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 TupleValue(SequenceValue):
sequence_type = tuple


class SingleNestedSequenceValue(SequenceValue):
"""
Common code for nested sequences (list of lists, or tuple of tuples).
Do not use this class directly. Instead use a subclass.
"""

def __init__(self, *args, **kwargs):
self.seq_separator = kwargs.pop('seq_separator', ';')
super(SingleNestedSequenceValue, self).__init__(*args, **kwargs)

def _convert(self, items):
# This could receive either a bare or nested sequence
if items and isinstance(items[0], self.sequence_type):
converted_sequences = [
super(SingleNestedSequenceValue, self)._convert(i) for i in items
]
return self.sequence_type(converted_sequences)
return self.sequence_type(
super(SingleNestedSequenceValue, self)._convert(items))

def to_python(self, value):
split_value = [
v.strip() for v in value.strip().split(self.seq_separator)
]
# Remove empty items
filtered = filter(None, split_value)
sequence = [
super(SingleNestedSequenceValue, self).to_python(f) for f in filtered
]
return self.sequence_type(sequence)


class SingleNestedListValue(SingleNestedSequenceValue):
sequence_type = list


class SingleNestedTupleValue(SingleNestedSequenceValue):
sequence_type = tuple


class BackendsValue(ListValue):
Expand All @@ -214,20 +274,6 @@ def converter(self, value):
return value


class TupleValue(ListValue):
message = 'Cannot interpret tuple item {0!r} in tuple {1!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 {0!r} in set {1!r}'

Expand Down
37 changes: 35 additions & 2 deletions docs/values.rst
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ Type values

.. class:: ListValue(default, [separator=',', converter=None])

A :class:`~Value` subclass that handles list values.
A :class:`~SequenceValue` 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
Expand Down Expand Up @@ -266,14 +266,47 @@ Type values

.. class:: TupleValue

A :class:`~Value` subclass that handles tuple values.
A :class:`~SequenceValue` 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:: SingleNestedTupleValue(default, [seq_separator=';', separator=',', converter=None])

A :class:`~SingleNestedSequenceValue` subclass that handles single nested tuple values,
e.g. ``((a, b), (c, d))``.

:param seq_separator: the separator to split each tuple with
:param separator: the separator to split the inner tuple contents with
:param converter: the optional converter callable to apply for each inner
tuple item

Useful for ADMINS, MANAGERS, and the like. For example::

ADMINS = SingleNestedTupleValue((
('John', 'jcleese@site.com'),
('Eric', 'eidle@site.com'),
))

Override using environment variables like this::

DJANGO_ADMINS=Terry,tjones@site.com;Graham,gchapman@site.com

.. class:: SingleNestedListValue(default, [seq_separator=';', separator=',', converter=None])

A :class:`~SingleNestedSequenceValue` subclass that handles single nested list values,
e.g. ``[[a, b], [c, d]]``.

:param seq_separator: the separator to split each list with
:param separator: the separator to split the inner list contents with
:param converter: the optional converter callable to apply for each inner
list item

See the :class:`~SingleNestedTupleValue` examples above.

.. class:: SetValue

A :class:`~Value` subclass that handles set values.
Expand Down
67 changes: 65 additions & 2 deletions tests/test_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@

from configurations.values import (Value, BooleanValue, IntegerValue,
FloatValue, DecimalValue, ListValue,
TupleValue, SetValue, DictValue,
URLValue, EmailValue, IPValue,
TupleValue, SingleNestedTupleValue,
SingleNestedListValue, SetValue,
DictValue, URLValue, EmailValue, IPValue,
RegexValue, PathValue, SecretValue,
DatabaseURLValue, EmailURLValue,
CacheURLValue, BackendsValue,
Expand Down Expand Up @@ -181,6 +182,68 @@ def test_tuple_values_default(self):
with env(DJANGO_TEST=''):
self.assertEqual(value.setup('TEST'), ())

def test_single_nested_list_values_default(self):
value = SingleNestedListValue()
with env(DJANGO_TEST='2,3;4,5'):
expected = [['2', '3'], ['4', '5']]
self.assertEqual(value.setup('TEST'), expected)
with env(DJANGO_TEST='2;3;4;5'):
expected = [['2'], ['3'], ['4'], ['5']]
self.assertEqual(value.setup('TEST'), expected)
with env(DJANGO_TEST='2,3,4,5'):
expected = [['2', '3', '4', '5']]
self.assertEqual(value.setup('TEST'), expected)
with env(DJANGO_TEST='2, 3 , ; 4 , 5 ; '):
expected = [['2', '3'], ['4', '5']]
self.assertEqual(value.setup('TEST'), expected)
with env(DJANGO_TEST=''):
self.assertEqual(value.setup('TEST'), [])

def test_single_nested_list_values_separator(self):
value = SingleNestedListValue(seq_separator=':')
with env(DJANGO_TEST='2,3:4,5'):
self.assertEqual(value.setup('TEST'), [['2', '3'], ['4', '5']])

def test_single_nested_list_values_converter(self):
value = SingleNestedListValue(converter=int)
with env(DJANGO_TEST='2,3;4,5'):
self.assertEqual(value.setup('TEST'), [[2, 3], [4, 5]])

def test_single_nested_list_values_converter_default(self):
value = SingleNestedListValue([['2', '3'], ['4', '5']], converter=int)
self.assertEqual(value.value, [[2, 3], [4, 5]])

def test_single_nested_tuple_values_default(self):
value = SingleNestedTupleValue()
with env(DJANGO_TEST='2,3;4,5'):
expected = (('2', '3'), ('4', '5'))
self.assertEqual(value.setup('TEST'), expected)
with env(DJANGO_TEST='2;3;4;5'):
expected = (('2',), ('3',), ('4',), ('5',))
self.assertEqual(value.setup('TEST'), expected)
with env(DJANGO_TEST='2,3,4,5'):
expected = (('2', '3', '4', '5'),)
self.assertEqual(value.setup('TEST'), expected)
with env(DJANGO_TEST='2, 3 , ; 4 , 5 ; '):
expected = (('2', '3'), ('4', '5'))
self.assertEqual(value.setup('TEST'), expected)
with env(DJANGO_TEST=''):
self.assertEqual(value.setup('TEST'), ())

def test_single_nested_tuple_values_separator(self):
value = SingleNestedTupleValue(seq_separator=':')
with env(DJANGO_TEST='2,3:4,5'):
self.assertEqual(value.setup('TEST'), (('2', '3'), ('4', '5')))

def test_single_nested_tuple_values_converter(self):
value = SingleNestedTupleValue(converter=int)
with env(DJANGO_TEST='2,3;4,5'):
self.assertEqual(value.setup('TEST'), ((2, 3), (4, 5)))

def test_single_nested_tuple_values_converter_default(self):
value = SingleNestedTupleValue((('2', '3'), ('4', '5')), converter=int)
self.assertEqual(value.value, ((2, 3), (4, 5)))

def test_set_values_default(self):
value = SetValue()
with env(DJANGO_TEST='2,2'):
Expand Down

0 comments on commit 8f617ad

Please sign in to comment.