Skip to content

Commit 68c7fa4

Browse files
committed
Merge pull request #32 from ets-labs/21-set_model_data
Implementation of DomainModel.set_data() method.
2 parents 4dc4402 + 7f25c51 commit 68c7fa4

File tree

5 files changed

+175
-43
lines changed

5 files changed

+175
-43
lines changed

.travis.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,6 @@ env:
1616
- TOXENV=py35
1717
- TOXENV=pypy
1818
- TOXENV=pypy3
19+
notifications:
20+
slack:
21+
secure: QHOypFu1TxCmkbCeNSqY39Bdj+hxOnA7H0zpTZrxDouLFdD+Ah5cKHFOFjFULruCdRo+JI8vPN0fmgRviUcyFVjKbs/3sEEUNU//JJpBPDQB9Epdc8jVp1w1GDF4fN/ujg3XW19U6N1dORRQBFi5EuJn/seYErogIs6F/4G8nEstyTLWqZ7DgnVBMM5aUVuwDae+iUNTutWTvVwJVqS84JC2rFzsRmXc9RJou6/PljBGogE5t+RYHWWAzSodwf3fMIdC7nKk+U6p5IwaDd021vgqk6Dduhu5qum89KsV80hP97qTIiJO/bz5gX6S/DG4cjjXFo2ELZARcRdQ2G7PJBooomiMPW3OTm4uTIbjxDKSIlZm/miHjUVv/+Am5TYgYPnOqU9RlYxAGFbKmkOXDwgJK3nyK8s4hK19cGss3hbRPC7VWRXAaGmO6Yk+UH7wUAbAPSzXvLT/GXc8VlY276KIFVoBX0dLlqSLt/jHrVvKZL1djXKIEa1cDUA9rDqybn+/Oue0QF9ask34lNM2Uu/5IdMAUz8V/U7UHtlfoYFMOJIbgzjUHRLAcIx9+k908rHAOzkMnPns9rDHkJFgJaj3n6Q5HJpQUMwq89Lhb1NHry0nNRE5awtXio6cRcri/ApVxFA+juuyRS64y1Adi8Su/ENAw4CkVeURJuMOqPk=

domain_models/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Domain models."""
22

3-
VERSION = '0.0.4'
3+
VERSION = '0.0.5'

domain_models/fields.py

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -40,21 +40,21 @@ def bind_model_cls(self, model_cls):
4040
return self
4141

4242
def init_model(self, model, value):
43-
"""Init model with field."""
43+
"""Init model with field.
44+
45+
:param DomainModel model:
46+
:param object value:
47+
"""
4448
if value is None and self.default is not None:
4549
value = self.default() if callable(self.default) else self.default
46-
value = self._converter(value)
4750

48-
if value is None and self.required:
49-
raise AttributeError("This field is required.")
50-
51-
setattr(model, self.storage_name, value)
51+
self.set_value(model, value)
5252

5353
def get_value(self, model, default=None):
5454
"""Return field's value.
5555
5656
:param DomainModel model:
57-
:param mixed default:
57+
:param object default:
5858
:rtype object:
5959
"""
6060
if default is not None:
@@ -67,7 +67,7 @@ def set_value(self, model, value):
6767
"""Set field's value.
6868
6969
:param DomainModel model:
70-
:param mixed value:
70+
:param object value:
7171
"""
7272
if value is None and self.required:
7373
raise AttributeError("This field is required.")
@@ -86,9 +86,26 @@ def get_builtin_type(self, model):
8686
return self.get_value(model)
8787

8888
def _converter(self, value):
89-
"""Convert raw input value of the field."""
89+
"""Convert raw input value of the field.
90+
91+
:param object value:
92+
:rtype object:
93+
"""
9094
return value
9195

96+
@staticmethod
97+
def _get_model_instance(model_cls, data):
98+
"""Convert dict into object of class of passed model.
99+
100+
:param class model_cls:
101+
:param object data:
102+
:rtype DomainModel:
103+
"""
104+
if not isinstance(data, (model_cls, dict)):
105+
raise TypeError('{0} is not valid type, instance of '
106+
'{1} or dict required'.format(data, model_cls))
107+
return model_cls(**data) if isinstance(data, dict) else data
108+
92109

93110
class Bool(Field):
94111
"""Bool field."""
@@ -156,16 +173,15 @@ class Model(Field):
156173
def __init__(self, related_model_cls, default=None, required=False):
157174
"""Initializer."""
158175
super(Model, self).__init__(default=default, required=required)
159-
160176
self.related_model_cls = related_model_cls
161177

162178
def _converter(self, value):
163-
"""Convert raw input value of the field."""
164-
if not isinstance(value, self.related_model_cls):
165-
raise TypeError('{0} is not valid model instance, instance of '
166-
'{1} required'.format(value,
167-
self.related_model_cls))
168-
return value
179+
"""Convert raw input value of the field.
180+
181+
:param object value:
182+
:rtype object:
183+
"""
184+
return self._get_model_instance(self.related_model_cls, value)
169185

170186
def get_builtin_type(self, model):
171187
"""Return built-in type representation of Model.
@@ -185,9 +201,15 @@ def __init__(self, related_model_cls, default=None, required=False):
185201
self.related_model_cls = related_model_cls
186202

187203
def _converter(self, value):
188-
"""Convert raw input value of the field."""
204+
"""Convert raw input value of the field.
205+
206+
:param object value:
207+
:rtype object:
208+
"""
189209
if type(value) is not self.related_model_cls.Collection:
190-
value = self.related_model_cls.Collection(value)
210+
value = self.related_model_cls.Collection([
211+
self._get_model_instance(self.related_model_cls, item)
212+
for item in value])
191213
return value
192214

193215
def get_builtin_type(self, model):

domain_models/models.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ def get(self, field_name, default=None):
193193
If the field does not exist, `AttributeError` is raised as well.
194194
195195
:param string field_name:
196-
:param mixed default:
196+
:param object default:
197197
"""
198198
try:
199199
field = self.__class__.__fields__[field_name]
@@ -211,3 +211,11 @@ def get_data(self):
211211
return dict((name, field.get_builtin_type(self))
212212
for name, field in
213213
six.iteritems(self.__class__.__fields__))
214+
215+
def set_data(self, data):
216+
"""Set dictionary data to model.
217+
218+
:param dict data:
219+
"""
220+
for name, field in six.iteritems(self.__class__.__fields__):
221+
field.init_model(self, data.get(name))

tests/test_models.py

Lines changed: 122 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,19 @@
1212
from domain_models import errors
1313

1414

15+
class Photo(models.DomainModel):
16+
id = fields.Int()
17+
storage_path = fields.String()
18+
19+
20+
class Profile(models.DomainModel):
21+
id = fields.Int()
22+
name = fields.String()
23+
main_photo = fields.Model(Photo)
24+
photos = fields.Collection(Photo)
25+
birth_date = fields.Date()
26+
27+
1528
class BaseModelsTests(unittest.TestCase):
1629
"""Basic model tests."""
1730

@@ -127,9 +140,23 @@ class Model2(models.DomainModel):
127140

128141
field = Model1.field
129142

143+
144+
class ModelSetterGetterTests(unittest.TestCase):
145+
"""Tests for getter and setter methods of model."""
146+
data = {
147+
'id': 1,
148+
'name': 'John',
149+
'main_photo': {'id': 1,
150+
'storage_path': 'some/dir/where/photos/live/1.jpg'},
151+
'photos': [
152+
{'id': 1, 'storage_path': 'some/dir/where/photos/live/1.jpg'},
153+
{'id': 2, 'storage_path': 'some/dir/where/photos/live/2.jpg'}
154+
],
155+
'birth_date': datetime.date(year=1986, month=4, day=26)
156+
}
157+
130158
def test_get_method_on_undefined(self):
131159
"""Test method get of Model."""
132-
133160
class Model(models.DomainModel):
134161
"""Test model."""
135162
field = fields.Int()
@@ -183,7 +210,6 @@ class Model(models.DomainModel):
183210

184211
def test_get_method_on_bool(self):
185212
"""Test method get on Bool of Model."""
186-
187213
class Model(models.DomainModel):
188214
"""Test model."""
189215
field = fields.Bool()
@@ -284,38 +310,111 @@ def test_get_method_on_collection(self):
284310
self.skipTest("Test is not implemented yet")
285311

286312
def test_get_data_method(self):
313+
"""Test get_data method."""
314+
photo1 = Photo(id=1, storage_path='some/dir/where/photos/live/1.jpg')
315+
photo2 = Photo(id=2, storage_path='some/dir/where/photos/live/2.jpg')
316+
profile = Profile(id=1, name='John', main_photo=photo1,
317+
photos=[photo1, photo2],
318+
birth_date=datetime.date(year=1986, month=4,
319+
day=26))
320+
321+
self.assertDictEqual(profile.get_data(), self.data)
322+
323+
def test_set_data_method(self):
324+
"""Test set_data method."""
325+
profile = Profile()
326+
profile.set_data(self.data)
327+
328+
self.assertEqual(profile.id, 1)
329+
self.assertEqual(profile.name, 'John')
330+
331+
self.assertIsInstance(profile.main_photo, Photo)
332+
self.assertEqual(profile.main_photo.id, 1)
333+
self.assertEqual(profile.main_photo.storage_path,
334+
'some/dir/where/photos/live/1.jpg')
335+
336+
self.assertIsInstance(profile.photos, Photo.Collection)
337+
self.assertEqual(profile.photos[0].id, 1)
338+
self.assertEqual(profile.photos[0].storage_path,
339+
'some/dir/where/photos/live/1.jpg')
340+
self.assertEqual(profile.photos[1].id, 2)
341+
self.assertEqual(profile.photos[1].storage_path,
342+
'some/dir/where/photos/live/2.jpg')
343+
344+
self.assertEqual(profile.birth_date,
345+
datetime.date(year=1986, month=4, day=26))
346+
347+
def test_set_data_via_constructor(self):
348+
"""Test set data via model."""
349+
profile = Profile(**self.data)
350+
351+
self.assertEqual(profile.id, 1)
352+
self.assertEqual(profile.name, 'John')
353+
354+
self.assertIsInstance(profile.main_photo, Photo)
355+
self.assertEqual(profile.main_photo.id, 1)
356+
self.assertEqual(profile.main_photo.storage_path,
357+
'some/dir/where/photos/live/1.jpg')
358+
359+
self.assertIsInstance(profile.photos, Photo.Collection)
360+
self.assertEqual(profile.photos[0].id, 1)
361+
self.assertEqual(profile.photos[0].storage_path,
362+
'some/dir/where/photos/live/1.jpg')
363+
self.assertEqual(profile.photos[1].id, 2)
364+
self.assertEqual(profile.photos[1].storage_path,
365+
'some/dir/where/photos/live/2.jpg')
366+
367+
self.assertEqual(profile.birth_date,
368+
datetime.date(year=1986, month=4, day=26))
369+
370+
def test_set_data_method_defaults(self):
287371
class Photo(models.DomainModel):
288372
id = fields.Int()
289-
url = fields.String()
373+
storage_path = fields.String(
374+
default='some/dir/where/photos/live/default.jpg')
375+
376+
default_photo = Photo()
377+
378+
class Profile(models.DomainModel):
379+
id = fields.Int()
380+
name = fields.String()
381+
main_photo = fields.Model(Photo, default=default_photo)
382+
photos = fields.Collection(Photo)
383+
birth_date = fields.Date()
384+
something = fields.String(default='def-val')
385+
386+
profile = Profile()
387+
profile.set_data({'id': 1, 'name': 'John'})
388+
389+
self.assertEqual(profile.id, 1)
390+
self.assertEqual(profile.name, 'John')
391+
392+
self.assertIsInstance(profile.main_photo, Photo)
393+
self.assertEqual(profile.main_photo.storage_path,
394+
'some/dir/where/photos/live/default.jpg')
395+
396+
self.assertIsNone(profile.main_photo.id)
397+
self.assertIsNone(profile.photos)
398+
self.assertIsNone(profile.birth_date)
399+
400+
self.assertEqual(profile.something, 'def-val')
401+
402+
def test_set_data_method_requirements(self):
403+
class Photo(models.DomainModel):
404+
id = fields.Int(required=True)
405+
storage_path = fields.String(required=True)
290406

291407
class Profile(models.DomainModel):
292408
id = fields.Int()
293409
name = fields.String()
294410
main_photo = fields.Model(Photo)
295411
photos = fields.Collection(Photo)
296412
birth_date = fields.Date()
297-
sequence = fields.Collection(fields.Int)
298413

299-
photo1 = Photo(id=1, url='http://boonya.info/wat.jpg?1')
300-
photo2 = Photo(id=2, url='http://boonya.info/wat.jpg?2')
301-
profile = Profile(id=1, name='John', main_photo=photo1,
302-
photos=[photo1, photo2],
303-
sequence=[1, 1, 2, 3, 5, 8, 13],
304-
birth_date=datetime.date(year=1986, month=4,
305-
day=26))
414+
profile = Profile()
306415

307-
self.assertDictEqual(profile.get_data(), {
308-
'id': 1,
309-
'name': 'John',
310-
'main_photo': {'id': 1,
311-
'url': 'http://boonya.info/wat.jpg?1'},
312-
'photos': [
313-
{'id': 1, 'url': 'http://boonya.info/wat.jpg?1'},
314-
{'id': 2, 'url': 'http://boonya.info/wat.jpg?2'}
315-
],
316-
'sequence': [1, 1, 2, 3, 5, 8, 13],
317-
'birth_date': datetime.date(year=1986, month=4, day=26)
318-
})
416+
with self.assertRaises(AttributeError):
417+
profile.set_data({'main_photo': {'id': 1}})
319418

320419

321420
class ModelReprTests(unittest.TestCase):

0 commit comments

Comments
 (0)