From f9f1f51d75dd990d79df623136e6184e3f33a459 Mon Sep 17 00:00:00 2001 From: alfred82santa Date: Thu, 2 Nov 2017 20:59:42 +0100 Subject: [PATCH 1/2] Added factory decorator in order to allow to set factories as default values --- dirty_models/__init__.py | 3 +- dirty_models/fields.py | 25 ++++++++----- dirty_models/models.py | 2 +- dirty_models/utils.py | 22 ++++++++++- tests/dirty_models/tests_models.py | 59 +++++++++++++++++++++++------- 5 files changed, 85 insertions(+), 26 deletions(-) diff --git a/dirty_models/__init__.py b/dirty_models/__init__.py index 20fc01c..dfc7a4f 100644 --- a/dirty_models/__init__.py +++ b/dirty_models/__init__.py @@ -6,5 +6,6 @@ from .models import * from .fields import * +from .utils import * -__version__ = '0.10.0' +__version__ = '0.10.1' diff --git a/dirty_models/fields.py b/dirty_models/fields.py index 90cb0f9..87dd2f0 100644 --- a/dirty_models/fields.py +++ b/dirty_models/fields.py @@ -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 @@ -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() @@ -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() @@ -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() @@ -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, diff --git a/dirty_models/models.py b/dirty_models/models.py index d816600..f400381 100644 --- a/dirty_models/models.py +++ b/dirty_models/models.py @@ -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): diff --git a/dirty_models/utils.py b/dirty_models/utils.py index 056c24c..3deca9d 100644 --- a/dirty_models/utils.py +++ b/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'] + + def underscore_to_camel(string): """ Converts underscored string to camel case. @@ -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 a default value as result of a function. It could + be useful to define a :class:`~dirty_models.field.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 diff --git a/tests/dirty_models/tests_models.py b/tests/dirty_models/tests_models.py index dbba976..f31ac95 100644 --- a/tests/dirty_models/tests_models.py +++ b/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', @@ -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): @@ -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) @@ -1988,3 +1986,36 @@ 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) From 64204d7ea8455f70a7863172229ed7e10e121db2 Mon Sep 17 00:00:00 2001 From: alfred82santa Date: Thu, 2 Nov 2017 22:06:05 +0100 Subject: [PATCH 2/2] Docs & fixes --- .travis.yml | 1 + Makefile | 11 ++++------- README.rst | 23 +++++++++++++++++++++++ dirty_models/utils.py | 6 +++--- setup.py | 1 + tests/dirty_models/tests_models.py | 16 ++++++++++++++++ tox.ini | 2 +- 7 files changed, 49 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index c000fa8..5a100fa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ python: - "3.3" - "3.4" - "3.5" + - "3.6" # command to install dependencies install: - make requirements diff --git a/Makefile b/Makefile index ea0dd49..dee3a8d 100644 --- a/Makefile +++ b/Makefile @@ -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: @@ -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 + diff --git a/README.rst b/README.rst index 1033742..f6aca16 100644 --- a/README.rst +++ b/README.rst @@ -85,6 +85,29 @@ Features Changelog --------- +Version 0.10.1 +-------------- + +- :class:`Factory` 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 -------------- diff --git a/dirty_models/utils.py b/dirty_models/utils.py index 3deca9d..d318d72 100644 --- a/dirty_models/utils.py +++ b/dirty_models/utils.py @@ -9,7 +9,7 @@ from .models import BaseModel -__all__ = ['factory', 'JSONEncoder'] +__all__ = ['factory', 'JSONEncoder', 'Factory'] def underscore_to_camel(string): @@ -102,8 +102,8 @@ def default(self, obj): class Factory: """ - Factory decorator could be used to define a default value as result of a function. It could - be useful to define a :class:`~dirty_models.field.DateTimeField` with :meth:`datetime.datetime.now` + 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. """ diff --git a/setup.py b/setup.py index c850786..ca23ac5 100644 --- a/setup.py +++ b/setup.py @@ -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'], diff --git a/tests/dirty_models/tests_models.py b/tests/dirty_models/tests_models.py index f31ac95..a64f64d 100644 --- a/tests/dirty_models/tests_models.py +++ b/tests/dirty_models/tests_models.py @@ -2019,3 +2019,19 @@ def test_default_value_factory(self): 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) diff --git a/tox.ini b/tox.ini index 433157f..8a4ba42 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py33,py34,py35 +envlist = py33,py34,py35,py36 [flake8] max-line-length = 120