Skip to content

Commit

Permalink
Merge pull request #82 from alfred82santa/feature/formatters
Browse files Browse the repository at this point in the history
Formatters
  • Loading branch information
alfred82santa committed May 30, 2016
2 parents 9adac60 + 52a0e41 commit 87a9293
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 8 deletions.
10 changes: 10 additions & 0 deletions dirty_models/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,16 @@ def can_use_value(self, value):
return True
return False

def get_field_type_by_value(self, value):
for ft in self._field_types:
if ft.check_value(value):
return ft
for ft in self._field_types:
if ft.can_use_value(value):
return ft

raise TypeError("Value `{0}` can not be used on field `{1}`".format(value, self.name))

@property
def field_types(self):
return self._field_types.copy()
2 changes: 1 addition & 1 deletion dirty_models/model_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Internal types for dirty models
"""
import itertools
from dirty_models.base import BaseData, InnerFieldTypeMixin
from .base import BaseData, InnerFieldTypeMixin
from functools import wraps


Expand Down
11 changes: 6 additions & 5 deletions dirty_models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@
from collections import Mapping
from copy import deepcopy

from dirty_models.base import BaseData, InnerFieldTypeMixin
from dirty_models.fields import IntegerField, FloatField, BooleanField, StringField, DateTimeField
from dirty_models.model_types import ListModel
from dirty_models.utils import underscore_to_camel
from .fields import BaseField, ModelField, ArrayField
from .base import BaseData, InnerFieldTypeMixin
from .fields import IntegerField, FloatField, BooleanField, StringField, DateTimeField, \
BaseField, ModelField, ArrayField
from .model_types import ListModel


__all__ = ['BaseModel', 'DynamicModel', 'FastDynamicModel', 'HashMapModel']
Expand Down Expand Up @@ -92,6 +91,8 @@ class CamelCaseMeta(DirtyModelMeta):
"""

def process_base_field(self, field, key):
from .utils import underscore_to_camel

if not field.name:
field.name = underscore_to_camel(key)
super(CamelCaseMeta, self).process_base_field(field, key)
Expand Down
93 changes: 92 additions & 1 deletion dirty_models/utils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,99 @@
import re
from json.encoder import JSONEncoder as BaseJSONEncoder
from datetime import date, datetime, time, timedelta
from .fields import MultiTypeField, DateTimeBaseField
from .model_types import ListModel
from .models import BaseModel, HashMapModel


def underscore_to_camel(string):
"""
Converts the underscored string to camel case.
Converts underscored string to camel case.
"""
return re.sub('_([a-z])', lambda x: x.group(1).upper(), string)


class BaseFormatterIter:
pass


class BaseFieldtypeFormatterIter(BaseFormatterIter):

def __init__(self, obj, field, parent_formatter):
self.obj = obj
self.field = field
self.parent_formatter = parent_formatter


class ListFormatterIter(BaseFieldtypeFormatterIter):

def __iter__(self):
for item in self.obj:
yield self.parent_formatter.format_field(self.field, item)


class HashMapFormatterIter(BaseFieldtypeFormatterIter):

def __iter__(self):
for fieldname in self.obj.get_fields():
value = self.obj.get_field_value(fieldname)
yield fieldname, self.parent_formatter.format_field(self.field, value)


class BaseModelFormatterIter(BaseFormatterIter):
"""
Base formatter iterator for Dirty Models.
"""

def __init__(self, model):
self.model = model

def __iter__(self):
fields = self.model.get_fields()
for fieldname in fields:
field = self.model.get_field_obj(fieldname)
yield field.name, self.format_field(field,
self.model.get_field_value(fieldname))

def format_field(self, field, value):
if isinstance(field, MultiTypeField):
return self.format_field(field.get_field_type_by_value(value), value)
elif isinstance(value, HashMapModel):
return HashMapFormatterIter(obj=value, field=value.get_field_type(), parent_formatter=self)
elif isinstance(value, BaseModel):
return self.__class__(value)
elif isinstance(value, ListModel):
return ListFormatterIter(obj=value, field=value.get_field_type(), parent_formatter=self)

return value


class ModelFormatterIter(BaseModelFormatterIter):

"""
Iterate over model fields formatting them.
"""

def format_field(self, field, value):
if isinstance(value, (date, datetime, time)) and \
isinstance(field, DateTimeBaseField):
return field.get_formatted_value(value)
elif isinstance(value, timedelta):
return value.total_seconds()

return super(ModelFormatterIter, self).format_field(field, value)


class JSONEncoder(BaseJSONEncoder):

"""
Json encoder for Dirty Models
"""

def default(self, obj):
if isinstance(obj, BaseModel):
return {k: v for k, v in ModelFormatterIter(obj)}
elif isinstance(obj, (HashMapFormatterIter, ModelFormatterIter)):
return {k: v for k, v in obj}
elif isinstance(obj, ListFormatterIter):
return list(obj)
12 changes: 12 additions & 0 deletions tests/dirty_models/tests_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -1448,6 +1448,18 @@ def test_update_integer_field(self):
self.model.multi_field = 3
self.assertEqual(self.model.multi_field, 3)

def test_get_field_type_by_value(self):
multi_field = MultiTypeField(field_types=[IntegerField(), (ArrayField, {"field_type": StringField()})])
self.assertIsInstance(multi_field.get_field_type_by_value(['foo', 'bar']),
ArrayField)
self.assertIsInstance(multi_field.get_field_type_by_value(3),
IntegerField)

def test_get_field_type_by_value_fail(self):
multi_field = MultiTypeField(field_types=[IntegerField(), (ArrayField, {"field_type": StringField()})])
with self.assertRaises(TypeError):
multi_field.get_field_type_by_value({})


class TestAutoreferenceModel(TestCase):

Expand Down
102 changes: 101 additions & 1 deletion tests/dirty_models/tests_utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
from datetime import datetime, date, timedelta
from json import dumps, loads
from unittest.case import TestCase

from dirty_models.utils import underscore_to_camel
from dirty_models.fields import StringIdField, IntegerField, DateTimeField, ArrayField, MultiTypeField, ModelField, \
HashMapField, DateField, TimedeltaField
from dirty_models.models import BaseModel
from dirty_models.utils import underscore_to_camel, ModelFormatterIter, ListFormatterIter, HashMapFormatterIter, \
JSONEncoder


class UnderscoreToCamelTests(TestCase):
Expand All @@ -19,3 +25,97 @@ def test_underscore_number(self):

def test_underscore_multi_number(self):
self.assertEqual(underscore_to_camel('foo_bar_tor_pir_1'), 'fooBarTorPir_1')


class TestModel(BaseModel):

test_string_field_1 = StringIdField(name='other_field')
test_int_field_1 = IntegerField()
test_datetime = DateTimeField(parse_format="%Y-%m-%dT%H:%M:%S")
test_array_datetime = ArrayField(field_type=DateTimeField(parse_format="%Y-%m-%dT%H:%M:%S"))
test_array_multitype = ArrayField(field_type=MultiTypeField(field_types=[IntegerField(),
DateTimeField(
parse_format="%Y-%m-%dT%H:%M:%S"
)]))
test_model_field_1 = ArrayField(field_type=ArrayField(field_type=ModelField()))
test_hash_map = HashMapField(field_type=DateField(parse_format="%Y-%m-%d date"))
test_timedelta = TimedeltaField()


class ModelFormatterIterTests(TestCase):

def test_model_formatter(self):

model = TestModel(data={'test_string_field_1': 'foo',
'test_int_field_1': 4,
'test_datetime': datetime(year=2016, month=5, day=30,
hour=22, minute=22, second=22),
'test_array_datetime': [datetime(year=2015, month=5, day=30,
hour=22, minute=22, second=22),
datetime(year=2015, month=6, day=30,
hour=22, minute=22, second=22)],
'test_array_multitype': [datetime(year=2015, month=5, day=30,
hour=22, minute=22, second=22),
4, 5],
'test_model_field_1': [[{'test_datetime': datetime(year=2015, month=7, day=30,
hour=22, minute=22, second=22)}]],
'test_hash_map': {'foo': date(year=2015, month=7, day=30)},
'test_timedelta': timedelta(seconds=32.1122)})

formatter = ModelFormatterIter(model)
data = {k: v for k, v in formatter}
self.assertEqual(data['other_field'], 'foo')
self.assertEqual(data['test_int_field_1'], 4)
self.assertEqual(data['test_datetime'], '2016-05-30T22:22:22')
self.assertIsInstance(data['test_array_datetime'], ListFormatterIter)
self.assertEqual(list(data['test_array_datetime']), ['2015-05-30T22:22:22', '2015-06-30T22:22:22'])
self.assertIsInstance(data['test_array_multitype'], ListFormatterIter)
self.assertEqual(list(data['test_array_multitype']), ['2015-05-30T22:22:22', 4, 5])
self.assertIsInstance(data['test_model_field_1'], ListFormatterIter)
self.assertIsInstance(list(data['test_model_field_1'])[0], ListFormatterIter)
self.assertEqual({k: v for k, v in list(list(data['test_model_field_1'])[0])[0]},
{'test_datetime': '2015-07-30T22:22:22'})
self.assertIsInstance(data['test_hash_map'], HashMapFormatterIter)
self.assertEqual({k: v for k, v in data['test_hash_map']}, {'foo': '2015-07-30 date'})
self.assertEqual(data['test_timedelta'], 32.1122)


class JSONEncoderTests(TestCase):

def test_model_json(self):

model = TestModel(data={'test_string_field_1': 'foo',
'test_int_field_1': 4,
'test_datetime': datetime(year=2016, month=5, day=30,
hour=22, minute=22, second=22),
'test_array_datetime': [datetime(year=2015, month=5, day=30,
hour=22, minute=22, second=22),
datetime(year=2015, month=6, day=30,
hour=22, minute=22, second=22)],
'test_array_multitype': [datetime(year=2015, month=5, day=30,
hour=22, minute=22, second=22),
4, 5],
'test_model_field_1': [[{'test_datetime': datetime(year=2015, month=7, day=30,
hour=22, minute=22, second=22)}]],
'test_hash_map': {'foo': date(year=2015, month=7, day=30)},
'test_timedelta': timedelta(seconds=32.1122)})

json_str = dumps(model, cls=JSONEncoder)

data = {'other_field': 'foo',
'test_int_field_1': 4,
'test_datetime': '2016-05-30T22:22:22',
'test_array_datetime': ['2015-05-30T22:22:22',
'2015-06-30T22:22:22'],
'test_array_multitype': ['2015-05-30T22:22:22', 4, 5],
'test_model_field_1': [[{'test_datetime': '2015-07-30T22:22:22'}]],
'test_hash_map': {'foo': '2015-07-30 date'},
'test_timedelta': 32.1122}

self.assertEqual(loads(json_str), data)

def test_general_use_json(self):

data = {'foo': 3, 'bar': 'str'}
json_str = dumps(data, cls=JSONEncoder)
self.assertEqual(loads(json_str), data)

0 comments on commit 87a9293

Please sign in to comment.