Skip to content

Commit

Permalink
Merge pull request #101 from alfred82santa/feature/default-value-factory
Browse files Browse the repository at this point in the history
Feature/default value factory
  • Loading branch information
alfred82santa committed Nov 2, 2017
2 parents 995104f + 64204d7 commit fb25d0d
Show file tree
Hide file tree
Showing 10 changed files with 131 additions and 34 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Expand Up @@ -3,6 +3,7 @@ python:
- "3.3"
- "3.4"
- "3.5"
- "3.6"
# command to install dependencies
install:
- make requirements
Expand Down
11 changes: 4 additions & 7 deletions Makefile
Expand Up @@ -19,14 +19,12 @@ requirements:
@echo "Installing dirty-models requirements..."
pip install -r requirements.txt

requirements-test:
requirements-test: requirements
@echo "Installing dirty-models tests requirements..."
@make requirements
pip install -r requirements-test.txt

requirements-docs:
requirements-docs: requirements
@echo "Installing dirty-models docs requirements..."
@make requirements
pip install -r requirements-docs.txt

run-tests:
Expand All @@ -49,7 +47,6 @@ flake:
autopep:
autopep8 --max-line-length 120 -r -j 8 -i .

prepush:
@make flake
@make run-tests
prepush: flake run-tests


23 changes: 23 additions & 0 deletions README.rst
Expand Up @@ -85,6 +85,29 @@ Features
Changelog
---------

Version 0.10.1
--------------

- :class:`Factory<dirty_models.utils>` feature. It allows to define a factory as
default value in order to be executed each time model is instanced. (Issue #100)

.. code-block:: python
from dirty_models.utils import factory
from datetime import datetime
class Model(BaseModel):
field_1 = DateTimeField(default=factory(datetime.now))
model = Model()
print(model.field_1)
# 2017-11-02 21:52:46.339040
- Makefile fixes.
- Python 3.6 is supported officially. It works since first day, but now tests run on Travis for Python 3.6.

Version 0.10.0
--------------

Expand Down
3 changes: 2 additions & 1 deletion dirty_models/__init__.py
Expand Up @@ -6,5 +6,6 @@

from .models import *
from .fields import *
from .utils import *

__version__ = '0.10.0'
__version__ = '0.10.1'
25 changes: 16 additions & 9 deletions dirty_models/fields.py
Expand Up @@ -2,11 +2,11 @@
Fields to be used with dirty models.
"""

from datetime import datetime, date, time, timedelta
from enum import Enum
from datetime import date, datetime, time, timedelta

from collections import Mapping
from dateutil.parser import parse as dateutil_parse
from enum import Enum
from functools import wraps

from .model_types import ListModel
Expand Down Expand Up @@ -103,10 +103,17 @@ def __set__(self, obj, value):
self._setter(self, obj, value)
return

if value is None:
self.delete_value(obj)
elif self.check_value(value) or self.can_use_value(value):
self.set_value(obj, self.use_value(value))
from dirty_models.utils import Factory

def set_value(v):
if value is None:
self.delete_value(obj)
elif self.check_value(v) or self.can_use_value(v):
self.set_value(obj, self.use_value(v))
elif isinstance(value, Factory):
set_value(v())

set_value(value)

def __delete__(self, obj):
self._check_name()
Expand Down Expand Up @@ -426,7 +433,7 @@ def convert_value(self, value):
return value.time()

return self.convert_value(self.get_parsed_value(value))
except:
except Exception:
return None
elif isinstance(value, datetime):
return value.timetz()
Expand Down Expand Up @@ -486,7 +493,7 @@ def convert_value(self, value):
return value.date()

return self.convert_value(self.get_parsed_value(value))
except:
except Exception:
return None
elif isinstance(value, datetime):
return value.date()
Expand Down Expand Up @@ -556,7 +563,7 @@ def convert_value(self, value):
return dateutil_parse(value)

return self.get_parsed_value(value)
except:
except Exception:
return None
elif isinstance(value, date):
return datetime(year=value.year, month=value.month,
Expand Down
2 changes: 1 addition & 1 deletion dirty_models/models.py
Expand Up @@ -282,7 +282,7 @@ def is_modified_field(self, name):

try:
return self.get_field_value(name).is_modified()
except:
except Exception:
return False

def import_data(self, data):
Expand Down
22 changes: 21 additions & 1 deletion dirty_models/utils.py
@@ -1,14 +1,17 @@
from datetime import date, datetime, time, timedelta
from enum import Enum
from json.encoder import JSONEncoder as BaseJSONEncoder

import re
from enum import Enum

from .fields import MultiTypeField
from .model_types import ListModel
from .models import BaseModel


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


def underscore_to_camel(string):
"""
Converts underscored string to camel case.
Expand Down Expand Up @@ -95,3 +98,20 @@ def default(self, obj):
return {k: v for k, v in obj}
elif isinstance(obj, ListFormatterIter):
return list(obj)


class Factory:
"""
Factory decorator could be used to define result of a function as default value. It could
be useful to define a :class:`~dirty_models.fields.DateTimeField` with :meth:`datetime.datetime.now`
in order to set the current datetime.
"""

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

def __call__(self):
return self.func()


factory = Factory
1 change: 1 addition & 0 deletions setup.py
Expand Up @@ -57,6 +57,7 @@
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'License :: OSI Approved :: BSD License',
'Development Status :: 4 - Beta'],
packages=['dirty_models'],
Expand Down
75 changes: 61 additions & 14 deletions tests/dirty_models/tests_models.py
@@ -1,16 +1,15 @@
import pickle
from datetime import datetime, date, time, timedelta
from enum import Enum
from datetime import date, datetime, time, timedelta
from unittest import TestCase

from enum import Enum
from functools import partial

from dirty_models.base import Unlocker
from dirty_models.fields import (BaseField, IntegerField, FloatField,
StringField, DateTimeField, ModelField,
ArrayField, BooleanField, DateField, TimeField, HashMapField, TimedeltaField,
EnumField, MultiTypeField)
from dirty_models.models import BaseModel, DynamicModel, HashMapModel, FastDynamicModel, CamelCaseMeta
from dirty_models.fields import ArrayField, BaseField, BooleanField, DateField, DateTimeField, EnumField, FloatField, \
HashMapField, IntegerField, ModelField, MultiTypeField, StringField, TimeField, TimedeltaField
from dirty_models.models import BaseModel, CamelCaseMeta, DynamicModel, FastDynamicModel, HashMapModel
from dirty_models.utils import factory

INITIAL_DATA = {
'testField1': 'testValue1',
Expand Down Expand Up @@ -164,8 +163,7 @@ def test_export_data(self):
exported_data = self.model.export_data()
self.assertEqual(exported_data, {'testField1': 'Value1Modified',
'testField4':
{'testField2':
'Field Value2 Modified',
{'testField2': 'Field Value2 Modified',
'testField1': 'Field Value1'}})

def test_export_modified(self):
Expand Down Expand Up @@ -1634,13 +1632,13 @@ def test_wildcard_path_list_inner(self):
self.assertEqual(self.model.get_attrs_by_path('test_list.*.test_field_2'), ['string'])

def test_wildcard_path_all(self):
self.assertEqual(set(self.model.get_attrs_by_path('*')), set([1, self.model.test_list,
self.model.test_model,
self.model.test_list_int]))
self.assertEqual(set(self.model.get_attrs_by_path('*')), {1, self.model.test_list,
self.model.test_model,
self.model.test_list_int})

def test_wildcard_path_list(self):
self.assertEqual(set(self.model.get_attrs_by_path('test_list.*')), set([self.model.test_list[0],
self.model.test_list[1]]))
self.assertEqual(set(self.model.get_attrs_by_path('test_list.*')), {self.model.test_list[0],
self.model.test_list[1]})

def test_first_simple(self):
self.assertEqual(self.model.get_1st_attr_by_path('test_field_1'), 1)
Expand Down Expand Up @@ -1988,3 +1986,52 @@ def test_list_inner_list_model_append_item(self):
self.assertEqual(model.export_modifications(), {'test_array_array_model.0': [{'test_field_int': 3},
{'test_field_int': 4},
{'test_field_int': 6}]})


class IterFactory:

def __init__(self):
self.i = 0

def __call__(self):
self.i += 1
return self.i


class DefaultValueFactoryTests(TestCase):

class Model(BaseModel):
__default_data__ = {'test_field_float': factory(IterFactory())}

test_field_int = IntegerField(default=factory(IterFactory()))
test_field_string = StringField(default=factory(IterFactory()))
test_field_float = FloatField()

def test_default_value_factory(self):
model = self.Model()

self.assertEquals(model.test_field_int, 1)
self.assertEquals(model.test_field_string, '1')
self.assertEquals(model.test_field_float, 1.0)

model = self.Model()

self.assertEquals(model.test_field_int, 2)
self.assertEquals(model.test_field_string, '2')
self.assertEquals(model.test_field_float, 2.0)


class DefaultValueFactoryDateTimeTests(TestCase):

class Model(BaseModel):
__default_data__ = {'test_field_2': factory(datetime.now)}

test_field_1 = DateTimeField(default=factory(datetime.now))
test_field_2 = DateTimeField()
test_field_float = FloatField()

def test_default_value_factory(self):
model = self.Model()

self.assertIsInstance(model.test_field_1, datetime)
self.assertIsInstance(model.test_field_2, datetime)
2 changes: 1 addition & 1 deletion tox.ini
@@ -1,5 +1,5 @@
[tox]
envlist = py33,py34,py35
envlist = py33,py34,py35,py36

[flake8]
max-line-length = 120

0 comments on commit fb25d0d

Please sign in to comment.