Skip to content

Commit 071db10

Browse files
committed
Add Collection and Collection field
1 parent d007213 commit 071db10

File tree

8 files changed

+378
-47
lines changed

8 files changed

+378
-47
lines changed

domain_models/collections.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"""Collections module."""
2+
3+
import six
4+
5+
from . import errors
6+
7+
8+
class Collection(list):
9+
"""Collection."""
10+
11+
def __init__(self, value_type, iterable=None, type_check=True):
12+
"""Initializer."""
13+
if not isinstance(value_type, six.class_types):
14+
raise errors.Error('Collection value_type should be valid type, '
15+
'instead got - {0}'.format(value_type))
16+
self.value_type = value_type
17+
18+
if not iterable:
19+
iterable = tuple()
20+
21+
if type_check:
22+
iterable = self._ensure_iterable_is_valid(iterable)
23+
24+
super(Collection, self).__init__(iterable)
25+
26+
def append(self, value):
27+
"""Add an item to the end of the list."""
28+
return super(Collection, self).append(
29+
self._ensure_value_is_valid(value))
30+
31+
def extend(self, iterable):
32+
"""Extend the list by appending all the items in the given list."""
33+
return super(Collection, self).extend(
34+
self._ensure_iterable_is_valid(iterable))
35+
36+
def insert(self, index, value):
37+
"""Insert an item at a given position."""
38+
return super(Collection, self).insert(
39+
index, self._ensure_value_is_valid(value))
40+
41+
def __setitem__(self, index, value):
42+
"""Set an item at a given position."""
43+
if isinstance(index, slice):
44+
return super(Collection, self).__setitem__(
45+
index, self.__class__(self.value_type, value))
46+
else:
47+
return super(Collection, self).__setitem__(
48+
index, self._ensure_value_is_valid(value))
49+
50+
def __getitem__(self, index):
51+
"""Return value by index or slice of values if index is slice."""
52+
value = super(Collection, self).__getitem__(index)
53+
if isinstance(index, slice):
54+
return self.__class__(self.value_type, value, type_check=False)
55+
return value
56+
57+
if six.PY2: # pragma: nocover
58+
def __getslice__(self, start, stop):
59+
"""Return slice of values."""
60+
return self.__class__(self.value_type,
61+
super(Collection, self).__getslice__(start,
62+
stop),
63+
type_check=False)
64+
65+
def __setslice__(self, start, stop, iterable):
66+
"""Set slice of values."""
67+
iterable = self.__class__(self.value_type, iterable)
68+
super(Collection, self).__setslice__(start, stop, iterable)
69+
70+
def _ensure_iterable_is_valid(self, iterable):
71+
"""Ensure that iterable values are a valid collection's values."""
72+
for value in iterable:
73+
self._ensure_value_is_valid(value)
74+
return iterable
75+
76+
def _ensure_value_is_valid(self, value):
77+
"""Ensure that value is a valid collection's value."""
78+
if not isinstance(value, self.value_type):
79+
raise TypeError('{0} is not valid collection value, instance '
80+
'of {1} required'.format(value, self.value_type))
81+
return value

domain_models/errors.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Domain models error module."""
1+
"""Errors module."""
22

33

44
class Error(Exception):

domain_models/fields.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
"""Domain models fields."""
1+
"""Fields module."""
22

33
import datetime
44

55
import six
66

7+
from . import collections
78
from . import errors
89

910

@@ -129,3 +130,23 @@ def _converter(self, value):
129130
'{1} required'.format(value,
130131
self.related_model_cls))
131132
return value
133+
134+
135+
class Collection(Field):
136+
"""Models collection relation field."""
137+
138+
def __init__(self, related_model_cls, default=None):
139+
"""Initializer."""
140+
super(Collection, self).__init__(default=default)
141+
142+
self.related_model_cls = related_model_cls
143+
144+
def _converter(self, value):
145+
"""Convert raw input value of the field."""
146+
if isinstance(value, collections.Collection):
147+
if value.value_type is not self.related_model_cls:
148+
value = collections.Collection(self.related_model_cls, value)
149+
else:
150+
value = collections.Collection(self.related_model_cls, value)
151+
152+
return value

domain_models/model.py renamed to domain_models/models.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
"""Domain models model."""
1+
"""Models module."""
2+
3+
from __future__ import absolute_import
24

35
import collections
46
import six

tests/test_collections.py

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
"""Collections tests."""
2+
3+
import unittest2
4+
5+
from domain_models import collections
6+
from domain_models import errors
7+
8+
9+
class CollectionTests(unittest2.TestCase):
10+
"""Collection tests."""
11+
12+
def test_init_empty(self):
13+
"""Test creation of collection."""
14+
collection = collections.Collection(int)
15+
16+
self.assertIsInstance(collection, collections.Collection)
17+
self.assertIsInstance(collection, list)
18+
19+
def test_init_with_correct_values(self):
20+
"""Test creation of collection."""
21+
collection = collections.Collection(int, (1, 2, 3))
22+
23+
self.assertEqual(collection, [1, 2, 3])
24+
25+
def test_init_with_incorrect_values(self):
26+
"""Test creation of collection."""
27+
with self.assertRaises(TypeError):
28+
collections.Collection(int, ('1', '2', '3'))
29+
30+
def test_init_without_value_type(self):
31+
"""Test creation of collection."""
32+
with self.assertRaises(errors.Error):
33+
collections.Collection(None)
34+
35+
with self.assertRaises(errors.Error):
36+
collections.Collection(1)
37+
38+
with self.assertRaises(errors.Error):
39+
collections.Collection(object())
40+
41+
def test_append_valid_type(self):
42+
"""Test append."""
43+
collection = collections.Collection(int)
44+
45+
collection.append(1)
46+
47+
self.assertEqual(collection, [1])
48+
49+
def test_append_invalid_type(self):
50+
"""Test append."""
51+
collection = collections.Collection(int)
52+
53+
with self.assertRaises(TypeError):
54+
collection.append('1')
55+
56+
def test_extend_valid_type(self):
57+
"""Test extend."""
58+
collection = collections.Collection(int)
59+
60+
collection.extend([1])
61+
62+
self.assertEqual(collection, [1])
63+
64+
def test_extend_invalid_type(self):
65+
"""Test extend."""
66+
collection = collections.Collection(int)
67+
68+
with self.assertRaises(TypeError):
69+
collection.extend(['1'])
70+
71+
def test_insert_valid_type(self):
72+
"""Test insert."""
73+
collection = collections.Collection(int)
74+
75+
collection.insert(0, 1)
76+
77+
self.assertEqual(collection, [1])
78+
79+
def test_insert_invalid_type(self):
80+
"""Test insert."""
81+
collection = collections.Collection(int)
82+
83+
with self.assertRaises(TypeError):
84+
collection.insert(0, '1')
85+
86+
def test_set_valid_type(self):
87+
"""Test set."""
88+
collection = collections.Collection(int, [0])
89+
90+
collection[0] = 1
91+
92+
self.assertEqual(collection, [1])
93+
94+
def test_set_invalid_type(self):
95+
"""Test set."""
96+
collection = collections.Collection(int, [0])
97+
98+
with self.assertRaises(TypeError):
99+
collection[0] = '1'
100+
101+
def test_set_valid_slice(self):
102+
"""Test set slice."""
103+
collection = collections.Collection(int, [1, 2, 3])
104+
105+
collection[0:3] = [7, 7, 7]
106+
107+
self.assertEqual(collection, [7, 7, 7])
108+
109+
def test_set_invalid_slice(self):
110+
"""Test set slice."""
111+
collection = collections.Collection(int, [1, 2, 3])
112+
113+
with self.assertRaises(TypeError):
114+
collection[0:3] = [7, '7', 7]
115+
116+
self.assertEqual(collection, [1, 2, 3])
117+
118+
def test_set_valid_slice_setitem(self):
119+
"""Test set slice."""
120+
collection = collections.Collection(int, [1, 2, 3])
121+
122+
collection.__setitem__(slice(0, 3), [7, 7, 7])
123+
124+
self.assertEqual(collection, [7, 7, 7])
125+
126+
def test_set_invalid_slice_setitem(self):
127+
"""Test set slice."""
128+
collection = collections.Collection(int, [1, 2, 3])
129+
130+
with self.assertRaises(TypeError):
131+
collection.__setitem__(slice(0, 3), [7, '7', 7])
132+
133+
self.assertEqual(collection, [1, 2, 3])
134+
135+
def test_get_item(self):
136+
"""Test getting of item."""
137+
collection = collections.Collection(int, [1, 2, 3])
138+
139+
self.assertEqual(collection[0], 1)
140+
self.assertEqual(collection[1], 2)
141+
self.assertEqual(collection[2], 3)
142+
143+
def test_get_slice(self):
144+
"""Test getting of slice."""
145+
collection = collections.Collection(int, [1, 2, 3])
146+
147+
collection_slice = collection[0:2]
148+
149+
self.assertEqual(collection_slice, [1, 2])
150+
self.assertIsInstance(collection_slice, collections.Collection)
151+
self.assertIs(collection.value_type, collection_slice.value_type)
152+
153+
def test_getitem_slice(self):
154+
"""Test getting of slice."""
155+
collection = collections.Collection(int, [1, 2, 3])
156+
157+
collection_slice = collection.__getitem__(slice(0, 2))
158+
159+
self.assertEqual(collection_slice, [1, 2])
160+
self.assertIsInstance(collection_slice, collections.Collection)
161+
self.assertIs(collection.value_type, collection_slice.value_type)

0 commit comments

Comments
 (0)