Skip to content

Commit 780ed61

Browse files
committed
Couple model with collection
1 parent 071db10 commit 780ed61

File tree

6 files changed

+104
-71
lines changed

6 files changed

+104
-71
lines changed

domain_models/collections.py

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,15 @@
22

33
import six
44

5-
from . import errors
6-
75

86
class Collection(list):
97
"""Collection."""
108

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
9+
value_type = object
10+
"""Type of values that collection could contain."""
1711

12+
def __init__(self, iterable=None, type_check=True):
13+
"""Initializer."""
1814
if not iterable:
1915
iterable = tuple()
2016

@@ -41,8 +37,8 @@ def insert(self, index, value):
4137
def __setitem__(self, index, value):
4238
"""Set an item at a given position."""
4339
if isinstance(index, slice):
44-
return super(Collection, self).__setitem__(
45-
index, self.__class__(self.value_type, value))
40+
return super(Collection, self).__setitem__(index,
41+
self.__class__(value))
4642
else:
4743
return super(Collection, self).__setitem__(
4844
index, self._ensure_value_is_valid(value))
@@ -51,21 +47,20 @@ def __getitem__(self, index):
5147
"""Return value by index or slice of values if index is slice."""
5248
value = super(Collection, self).__getitem__(index)
5349
if isinstance(index, slice):
54-
return self.__class__(self.value_type, value, type_check=False)
50+
return self.__class__(value, type_check=False)
5551
return value
5652

5753
if six.PY2: # pragma: nocover
5854
def __getslice__(self, start, stop):
5955
"""Return slice of values."""
60-
return self.__class__(self.value_type,
61-
super(Collection, self).__getslice__(start,
56+
return self.__class__(super(Collection, self).__getslice__(start,
6257
stop),
6358
type_check=False)
6459

6560
def __setslice__(self, start, stop, iterable):
6661
"""Set slice of values."""
67-
iterable = self.__class__(self.value_type, iterable)
68-
super(Collection, self).__setslice__(start, stop, iterable)
62+
super(Collection, self).__setslice__(start, stop,
63+
self.__class__(iterable))
6964

7065
def _ensure_iterable_is_valid(self, iterable):
7166
"""Ensure that iterable values are a valid collection's values."""
@@ -75,7 +70,8 @@ def _ensure_iterable_is_valid(self, iterable):
7570

7671
def _ensure_value_is_valid(self, value):
7772
"""Ensure that value is a valid collection's value."""
78-
if not isinstance(value, self.value_type):
73+
if not isinstance(value, self.__class__.value_type):
7974
raise TypeError('{0} is not valid collection value, instance '
80-
'of {1} required'.format(value, self.value_type))
75+
'of {1} required'.format(
76+
value, self.__class__.value_type))
8177
return value

domain_models/fields.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
import six
66

7-
from . import collections
87
from . import errors
98

109

@@ -138,15 +137,10 @@ class Collection(Field):
138137
def __init__(self, related_model_cls, default=None):
139138
"""Initializer."""
140139
super(Collection, self).__init__(default=default)
141-
142140
self.related_model_cls = related_model_cls
143141

144142
def _converter(self, value):
145143
"""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-
144+
if type(value) is not self.related_model_cls.Collection:
145+
value = self.related_model_cls.Collection(value)
152146
return value

domain_models/models.py

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22

33
from __future__ import absolute_import
44

5-
import collections
5+
import collections as std_collections
66
import six
77

88
from . import fields
9+
from . import collections
910
from . import errors
1011

1112

@@ -29,6 +30,8 @@ def __new__(mcs, class_name, bases, attributes):
2930
attribute_name='__view_key__', attributes=attributes,
3031
class_name=class_name)
3132

33+
mcs.bind_collection_to_model_cls(cls)
34+
3235
return cls
3336

3437
@staticmethod
@@ -49,7 +52,7 @@ def prepare_fields_attribute(attribute_name, attributes, class_name):
4952
attribute = attributes.get(attribute_name)
5053
if not attribute:
5154
attribute = tuple()
52-
elif isinstance(attribute, collections.Iterable):
55+
elif isinstance(attribute, std_collections.Iterable):
5356
attribute = tuple(attribute)
5457
else:
5558
raise errors.Error('{0}.{1} is supposed to be a list of {2}, '
@@ -63,22 +66,51 @@ def bind_fields_to_model_cls(cls, model_fields):
6366
return tuple(field.bind_model_cls(cls)
6467
for field in model_fields)
6568

69+
@staticmethod
70+
def bind_collection_to_model_cls(cls):
71+
"""Bind collection to model's class.
72+
73+
If collection was not specialized in process of model's declaration,
74+
subclass of collection will be created.
75+
"""
76+
cls.Collection = type('{0}.Collection'.format(cls.__name__),
77+
(cls.Collection,),
78+
{'value_type': cls})
79+
cls.Collection.__module__ = cls.__module__
80+
6681

6782
@six.python_2_unicode_compatible
6883
@six.add_metaclass(DomainModelMetaClass)
6984
class DomainModel(object):
7085
"""Base domain model.
7186
72-
:param __fields__: Tuple of all model fields.
73-
:type __fields__: tuple[fields.Field]
87+
.. py:attribute:: Collection
88+
89+
Model's collection class.
90+
91+
:type: collections.Collection
92+
93+
.. py:attribute:: __fields__
7494
75-
:param __unique_key__: Tuple of model fields that represents unique key.
76-
:type __unique_key__: tuple[fields.Field]
95+
Tuple of all model fields.
7796
78-
:param __view_key__: Tuple of model fields that represents view key.
79-
:type __view_key__: tuple[fields.Field]
97+
:type: tuple[fields.Field]
98+
99+
.. py:attribute:: __unique_key__
100+
101+
Tuple of model fields that represents unique key.
102+
103+
:type: tuple[fields.Field]
104+
105+
.. py:attribute:: __view_key__
106+
107+
Tuple of model fields that represents view key.
108+
109+
:type: tuple[fields.Field]
80110
"""
81111

112+
Collection = collections.Collection
113+
82114
__fields__ = tuple()
83115
__view_key__ = tuple()
84116
__unique_key__ = tuple()

tests/test_collections.py

Lines changed: 26 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,112 +3,106 @@
33
import unittest2
44

55
from domain_models import collections
6-
from domain_models import errors
6+
7+
8+
class TestCollection(collections.Collection):
9+
"""Test collection of ints."""
10+
11+
value_type = int
712

813

914
class CollectionTests(unittest2.TestCase):
1015
"""Collection tests."""
1116

1217
def test_init_empty(self):
1318
"""Test creation of collection."""
14-
collection = collections.Collection(int)
19+
collection = TestCollection()
1520

1621
self.assertIsInstance(collection, collections.Collection)
1722
self.assertIsInstance(collection, list)
1823

1924
def test_init_with_correct_values(self):
2025
"""Test creation of collection."""
21-
collection = collections.Collection(int, (1, 2, 3))
26+
collection = TestCollection((1, 2, 3))
2227

2328
self.assertEqual(collection, [1, 2, 3])
2429

2530
def test_init_with_incorrect_values(self):
2631
"""Test creation of collection."""
2732
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())
33+
TestCollection(('1', '2', '3'))
4034

4135
def test_append_valid_type(self):
4236
"""Test append."""
43-
collection = collections.Collection(int)
37+
collection = TestCollection()
4438

4539
collection.append(1)
4640

4741
self.assertEqual(collection, [1])
4842

4943
def test_append_invalid_type(self):
5044
"""Test append."""
51-
collection = collections.Collection(int)
45+
collection = TestCollection()
5246

5347
with self.assertRaises(TypeError):
5448
collection.append('1')
5549

5650
def test_extend_valid_type(self):
5751
"""Test extend."""
58-
collection = collections.Collection(int)
52+
collection = TestCollection()
5953

6054
collection.extend([1])
6155

6256
self.assertEqual(collection, [1])
6357

6458
def test_extend_invalid_type(self):
6559
"""Test extend."""
66-
collection = collections.Collection(int)
60+
collection = TestCollection()
6761

6862
with self.assertRaises(TypeError):
6963
collection.extend(['1'])
7064

7165
def test_insert_valid_type(self):
7266
"""Test insert."""
73-
collection = collections.Collection(int)
67+
collection = TestCollection()
7468

7569
collection.insert(0, 1)
7670

7771
self.assertEqual(collection, [1])
7872

7973
def test_insert_invalid_type(self):
8074
"""Test insert."""
81-
collection = collections.Collection(int)
75+
collection = TestCollection()
8276

8377
with self.assertRaises(TypeError):
8478
collection.insert(0, '1')
8579

8680
def test_set_valid_type(self):
8781
"""Test set."""
88-
collection = collections.Collection(int, [0])
82+
collection = TestCollection([0])
8983

9084
collection[0] = 1
9185

9286
self.assertEqual(collection, [1])
9387

9488
def test_set_invalid_type(self):
9589
"""Test set."""
96-
collection = collections.Collection(int, [0])
90+
collection = TestCollection([0])
9791

9892
with self.assertRaises(TypeError):
9993
collection[0] = '1'
10094

10195
def test_set_valid_slice(self):
10296
"""Test set slice."""
103-
collection = collections.Collection(int, [1, 2, 3])
97+
collection = TestCollection([1, 2, 3])
10498

10599
collection[0:3] = [7, 7, 7]
106100

107101
self.assertEqual(collection, [7, 7, 7])
108102

109103
def test_set_invalid_slice(self):
110104
"""Test set slice."""
111-
collection = collections.Collection(int, [1, 2, 3])
105+
collection = TestCollection([1, 2, 3])
112106

113107
with self.assertRaises(TypeError):
114108
collection[0:3] = [7, '7', 7]
@@ -117,15 +111,15 @@ def test_set_invalid_slice(self):
117111

118112
def test_set_valid_slice_setitem(self):
119113
"""Test set slice."""
120-
collection = collections.Collection(int, [1, 2, 3])
114+
collection = TestCollection([1, 2, 3])
121115

122116
collection.__setitem__(slice(0, 3), [7, 7, 7])
123117

124118
self.assertEqual(collection, [7, 7, 7])
125119

126120
def test_set_invalid_slice_setitem(self):
127121
"""Test set slice."""
128-
collection = collections.Collection(int, [1, 2, 3])
122+
collection = TestCollection([1, 2, 3])
129123

130124
with self.assertRaises(TypeError):
131125
collection.__setitem__(slice(0, 3), [7, '7', 7])
@@ -134,28 +128,26 @@ def test_set_invalid_slice_setitem(self):
134128

135129
def test_get_item(self):
136130
"""Test getting of item."""
137-
collection = collections.Collection(int, [1, 2, 3])
131+
collection = TestCollection([1, 2, 3])
138132

139133
self.assertEqual(collection[0], 1)
140134
self.assertEqual(collection[1], 2)
141135
self.assertEqual(collection[2], 3)
142136

143137
def test_get_slice(self):
144138
"""Test getting of slice."""
145-
collection = collections.Collection(int, [1, 2, 3])
139+
collection = TestCollection([1, 2, 3])
146140

147141
collection_slice = collection[0:2]
148142

149143
self.assertEqual(collection_slice, [1, 2])
150-
self.assertIsInstance(collection_slice, collections.Collection)
151-
self.assertIs(collection.value_type, collection_slice.value_type)
144+
self.assertIsInstance(collection_slice, TestCollection)
152145

153146
def test_getitem_slice(self):
154147
"""Test getting of slice."""
155-
collection = collections.Collection(int, [1, 2, 3])
148+
collection = TestCollection([1, 2, 3])
156149

157150
collection_slice = collection.__getitem__(slice(0, 2))
158151

159152
self.assertEqual(collection_slice, [1, 2])
160-
self.assertIsInstance(collection_slice, collections.Collection)
161-
self.assertIs(collection.value_type, collection_slice.value_type)
153+
self.assertIsInstance(collection_slice, TestCollection)

tests/test_fields.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ def test_set_collection(self):
378378
"""Test setting of collection."""
379379
model = ExampleModel()
380380
related_model = RelatedModel()
381-
some_collection = collections.Collection(RelatedModel, [related_model])
381+
some_collection = RelatedModel.Collection([related_model])
382382

383383
model.collection_field = some_collection
384384

@@ -391,13 +391,12 @@ class RelatedModelChild(RelatedModel):
391391

392392
model = ExampleModel()
393393
related_model = RelatedModelChild()
394-
some_collection = collections.Collection(RelatedModelChild,
395-
[related_model])
394+
some_collection = RelatedModelChild.Collection([related_model])
396395

397396
model.collection_field = some_collection
398397

399398
self.assertIsNot(model.collection_field, some_collection)
400-
self.assertIsInstance(model.collection_field, collections.Collection)
399+
self.assertIsInstance(model.collection_field, RelatedModel.Collection)
401400
self.assertIs(model.collection_field.value_type, RelatedModel)
402401
self.assertEqual(model.collection_field, [related_model])
403402

0 commit comments

Comments
 (0)