Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix bug #107; Added ModelIterator utility; Some fixes #108

Merged
merged 1 commit into from
Feb 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,26 @@ Features
Changelog
---------


Version 0.11.2
--------------

- Fix bug #107.

- Added :class:`~dirty_models.utils.ModelIterator` class in order to be able to iterate over model fields.

.. code-block:: python

from dirty_models.utils import ModelIterator

for fieldname, field_obj, value in ModelIterator(my_model):
print('Field name: {}'.format(fieldname))
print('Field alias: {}'.format(field_obj.alias))
print('Field value: {}'.format(value))

- Some fixes about read only data.


Version 0.11.1
--------------

Expand Down
2 changes: 1 addition & 1 deletion dirty_models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
from .fields import *
from .utils import *

__version__ = '0.11.1'
__version__ = '0.11.2'
8 changes: 3 additions & 5 deletions dirty_models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ class BaseData:
Base class for data inside dirty model.
"""

__slots__ = []

__locked__ = None
__read_only__ = None
__parent__ = None
Expand Down Expand Up @@ -86,15 +84,15 @@ def _prepare_child(self, value):

class InnerFieldTypeMixin:

_field_type = None
__field_type__ = None

def __init__(self, *args, **kwargs):
if 'field_type' in kwargs:
self._field_type = kwargs.pop('field_type')
self.__field_type__ = kwargs.pop('field_type')
super(InnerFieldTypeMixin, self).__init__(*args, **kwargs)

def get_field_type(self):
return self._field_type
return self.__field_type__


class Unlocker():
Expand Down
3 changes: 1 addition & 2 deletions dirty_models/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,8 +456,7 @@ def can_use_value(self, value):

def set_value(self, obj, value: time):
if self.default_timezone and value.tzinfo is None:
value = time(hour=value.hour, minute=value.minute, second=value.microsecond,
microsecond=value.microsecond, tzinfo=self.default_timezone)
value = value.replace(tzinfo=self.default_timezone)

super(TimeField, self).set_value(obj, value)

Expand Down
10 changes: 5 additions & 5 deletions dirty_models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -715,7 +715,7 @@ class BaseDynamicModel(BaseModel):
"""

"""
_dynamic_model = None
__dynamic_model__ = None

def __getattr__(self, name):
try:
Expand Down Expand Up @@ -746,7 +746,7 @@ def _get_field_type(self, key, value):
elif isinstance(value, Enum):
return EnumField(name=key, enum_class=type(value))
elif isinstance(value, (dict, BaseDynamicModel, Mapping)):
return ModelField(name=key, model_class=self._dynamic_model or self.__class__)
return ModelField(name=key, model_class=self.__dynamic_model__ or self.__class__)
elif isinstance(value, BaseModel):
return ModelField(name=key, model_class=value.__class__)
elif isinstance(value, (list, set, ListModel)):
Expand Down Expand Up @@ -802,7 +802,7 @@ def __init__(self, *args, **kwargs):
self.__structure__ = {}

def __new__(cls, *args, **kwargs):
new_class = type('DynamicModel_' + str(cls._next_id), (cls,), {'_dynamic_model': DynamicModel})
new_class = type('DynamicModel_' + str(cls._next_id), (cls,), {'__dynamic_model__': DynamicModel})
cls._next_id = id(new_class)
return super(DynamicModel, new_class).__new__(new_class)

Expand Down Expand Up @@ -880,7 +880,7 @@ def get_real_name(self, name):
return new_name if new_name else name

def get_field_obj(self, name):
return super(HashMapModel, self).get_field_obj(name) or self._field_type
return super(HashMapModel, self).get_field_obj(name) or self.__field_type__

def copy(self):
"""
Expand Down Expand Up @@ -975,7 +975,7 @@ class FastDynamicModel(BaseDynamicModel):

def __init__(self, *args, **kwargs):
self.__field_types__ = {}
self._dynamic_model = FastDynamicModel
self.__dynamic_model__ = FastDynamicModel
super(FastDynamicModel, self).__init__(*args, **kwargs)

def get_real_name(self, name):
Expand Down
46 changes: 36 additions & 10 deletions dirty_models/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from .model_types import ListModel
from .models import BaseModel


__all__ = ['factory', 'JSONEncoder', 'Factory']


Expand All @@ -19,6 +18,39 @@ def underscore_to_camel(string):
return re.sub('_([a-z])', lambda x: x.group(1).upper(), string)


class BaseModelIterator:

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)
name = self.model.get_real_name(fieldname)
yield name, field, self.model.get_field_value(fieldname)


class ModelIterator(BaseModelIterator):
"""
Helper in order to iterate over model fields.
"""

def __iter__(self):
for name, field, value in super(ModelIterator, self).__iter__():
yield name, value

items = __iter__

def values(self):
for _, value in self:
yield value

def keys(self):
for name, _ in self:
yield name


class BaseFormatterIter:
pass

Expand All @@ -38,21 +70,15 @@ def __iter__(self):
yield self.parent_formatter.format_field(self.field, item)


class BaseModelFormatterIter(BaseFormatterIter):
class BaseModelFormatterIter(BaseModelIterator, 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)
name = self.model.get_real_name(fieldname)
for name, field, value in super(BaseModelFormatterIter, self).__iter__():
yield name, self.format_field(field,
self.model.get_field_value(fieldname))
self.model.get_field_value(name))

def format_field(self, field, value):
if isinstance(field, MultiTypeField):
Expand Down
119 changes: 112 additions & 7 deletions tests/dirty_models/tests_utils.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from datetime import datetime, date, timedelta
from enum import Enum
from datetime import date, datetime, timedelta
from json import dumps, loads
from unittest.case import TestCase

from dirty_models.fields import StringIdField, IntegerField, DateTimeField, ArrayField, MultiTypeField, ModelField, \
HashMapField, DateField, TimedeltaField, EnumField
from enum import Enum

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


class UnderscoreToCamelTests(TestCase):
Expand All @@ -28,7 +29,6 @@ def test_underscore_multi_number(self):


class TestModel(BaseModel):

class TestEnum(Enum):
value_1 = 1
value_2 = '2'
Expand All @@ -41,7 +41,7 @@ class TestEnum(Enum):
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()
Expand Down Expand Up @@ -195,3 +195,108 @@ def test_general_use_json(self):
data = {'foo': 3, 'bar': 'str'}
json_str = dumps(data, cls=JSONEncoder)
self.assertEqual(loads(json_str), data)


class ModelIteratorTests(TestCase):

def test_model_iterator(self):
data = {'other_field': '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),
'test_enum': TestModel.TestEnum.value_3,
'test_multi_field': date(year=2015, month=7, day=30)}

model = TestModel(data=data)

result = {k: v for k, v in ModelIterator(model)}

self.assertEqual(set(result.keys()), set(data.keys()))
self.assertIsInstance(result['test_hash_map'], BaseModel)

def test_model_iterator_items(self):
data = {'other_field': '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),
'test_enum': TestModel.TestEnum.value_3,
'test_multi_field': date(year=2015, month=7, day=30)}

model = TestModel(data=data)

result = {k: v for k, v in ModelIterator(model).items()}

self.assertEqual(set(result.keys()), set(data.keys()))
self.assertIsInstance(result['test_hash_map'], BaseModel)

def test_model_iterator_keys(self):
data = {'other_field': '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),
'test_enum': TestModel.TestEnum.value_3,
'test_multi_field': date(year=2015, month=7, day=30)}

model = TestModel(data=data)

result = [v for v in ModelIterator(model).keys()]

self.assertEqual(set(data.keys()), set(result))

def test_model_iterator_values(self):
data = {'other_field': '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),
'test_enum': TestModel.TestEnum.value_3,
'test_multi_field': date(year=2015, month=7, day=30)}

model = TestModel(data=data)

result = [v for v in ModelIterator(model).values()]

self.assertIn(model.test_hash_map, result)