Skip to content

Commit

Permalink
Merge pull request #76 from alfred82santa/feature/default-values
Browse files Browse the repository at this point in the history
Feature/default values
  • Loading branch information
alfred82santa committed Mar 15, 2016
2 parents 4e136bd + 03a9c83 commit 65bd96c
Show file tree
Hide file tree
Showing 9 changed files with 349 additions and 61 deletions.
2 changes: 1 addition & 1 deletion .env
@@ -1 +1 @@
dirtymodels@0.3.6
dirtymodels@0.6.0
2 changes: 2 additions & 0 deletions .travis.yml
@@ -1,6 +1,8 @@
language: python
python:
- "3.3"
- "3.4"
- "3.5"
# command to install dependencies
install:
- pip install -r requirements-test.txt
Expand Down
4 changes: 4 additions & 0 deletions Makefile
Expand Up @@ -12,6 +12,7 @@ help:
@echo "clean: Clean compiled files"
@echo "flake: Run Flake8"
@echo "prepush: Helper to run before to push to repo"
@echo "autopep: Reformat code using PEP8"
@echo "---------------------------------------------------------------"

requirements:
Expand Down Expand Up @@ -45,6 +46,9 @@ flake:
flake8 dirty_models
flake8 tests

autopep:
autopep8 --max-line-length 120 -r -j 8 -i .

prepush:
@make flake
@make run-tests
Expand Down
96 changes: 41 additions & 55 deletions README.rst
Expand Up @@ -68,21 +68,61 @@ Features
- Automatic cast value.
- Easy import from/export to dict.
- Basic field type implemented.
- Multi type fields.
- Default values for each field or whole model.
- HashMap model. It could be used instead of DynamicModel.
- FastDynamicModel. It could be used instead of DynamicModel. Same behavior, better performance.
- Pickable models.
- Datetime fields can use any datetime format using parser and formatter functions.
- No database dependent.
- Auto documentation using https://github.com/alfred82santa/dirty-models-sphinx
- Opensource (BSD License)

*********
Changelog
*********

Version 0.6.0
-------------

- Added default value for fields.

.. code-block:: python
class ExampleModel(BaseModel):
integer_field = IntegerField(default=1)
model = ExampleModel()
assert model.integer_field is 1
- Added default values at model level. Inherit default values could be override on new model classes.

.. code-block:: python
class InheritExampleModel(ExampleModel):
_default_data = {'integer_field': 2}
model = InheritExampleModel()
assert model.integer_field is 2
- Added multi type fields.

.. code-block:: python
class ExampleModel(BaseModel):
multi_field = MultiTypeField(field_types=[IntegerField(), StringField()])
model = ExampleModel()
model.multi_field = 2
assert model.multi_field is 2
model.multi_field = 'foo'
assert model.multi_field is 'foo'
Version 0.5.2
-------------

- Fix model structure.
- Fixex model structure.
- Makefile helpers.


Expand Down Expand Up @@ -155,57 +195,3 @@ Basic usage
.. note::

Look at tests for more examples


*****************
Performance Tests
*****************

.. code-block:: bash
$ python3 performancerunner.py
DynamicModel start
DynamicModel: iteration no. 0 start
DynamicModel: iteration no. 0 => 0:00:02.528166
DynamicModel: iteration no. 1 start
DynamicModel: iteration no. 1 => 0:00:03.415274
DynamicModel: iteration no. 2 start
DynamicModel: iteration no. 2 => 0:00:03.115128
DynamicModel: iteration no. 3 start
DynamicModel: iteration no. 3 => 0:00:04.091488
DynamicModel: iteration no. 4 start
DynamicModel: iteration no. 4 => 0:00:05.275302
DynamicModel => 0:00:18.425358
FastDynamicModel start
FastDynamicModel: iteration no. 0 start
FastDynamicModel: iteration no. 0 => 0:00:01.351796
FastDynamicModel: iteration no. 1 start
FastDynamicModel: iteration no. 1 => 0:00:01.265681
FastDynamicModel: iteration no. 2 start
FastDynamicModel: iteration no. 2 => 0:00:01.270142
FastDynamicModel: iteration no. 3 start
FastDynamicModel: iteration no. 3 => 0:00:01.273443
FastDynamicModel: iteration no. 4 start
FastDynamicModel: iteration no. 4 => 0:00:01.280512
FastDynamicModel => 0:00:06.441574
BlobField start
BlobField: iteration no. 0 start
BlobField: iteration no. 0 => 0:00:00.000082
BlobField: iteration no. 1 start
BlobField: iteration no. 1 => 0:00:00.000027
BlobField: iteration no. 2 start
BlobField: iteration no. 2 => 0:00:00.000025
BlobField: iteration no. 3 start
BlobField: iteration no. 3 => 0:00:00.000024
BlobField: iteration no. 4 start
BlobField: iteration no. 4 => 0:00:00.000023
BlobField => 0:00:00.000181
{'DynamicModel': {'results': [datetime.timedelta(0, 2, 528166), datetime.timedelta(0, 3, 415274),
datetime.timedelta(0, 3, 115128), datetime.timedelta(0, 4, 91488), datetime.timedelta(0, 5, 275302)],
'total': datetime.timedelta(0, 18, 425358)}, 'FastDynamicModel': {'results': [datetime.timedelta(0, 1, 351796),
datetime.timedelta(0, 1, 265681), datetime.timedelta(0, 1, 270142), datetime.timedelta(0, 1, 273443),
datetime.timedelta(0, 1, 280512)], 'total': datetime.timedelta(0, 6, 441574)}, 'BlobField':
{'results': [datetime.timedelta(0, 0, 82), datetime.timedelta(0, 0, 27), datetime.timedelta(0, 0, 25),
datetime.timedelta(0, 0, 24), datetime.timedelta(0, 0, 23)], 'total': datetime.timedelta(0, 0, 181)}}
45 changes: 44 additions & 1 deletion dirty_models/fields.py
Expand Up @@ -13,11 +13,12 @@ class BaseField:

"""Base field descriptor."""

def __init__(self, name=None, alias=None, getter=None, setter=None, read_only=False, doc=None):
def __init__(self, name=None, alias=None, getter=None, setter=None, read_only=False, default=None, doc=None):
self._name = None
self.name = name
self.alias = alias
self.read_only = read_only
self.default = default
self._getter = getter
self._setter = setter
self.__doc__ = doc or self.get_field_docstring()
Expand Down Expand Up @@ -482,3 +483,45 @@ def convert_value(self, value):

class BlobField(BaseField):
pass


class MultiTypeField(BaseField):

def __init__(self, field_types=None, **kwargs):
self._field_types = []

field_types = field_types or []

for field_type in field_types:
if isinstance(field_type, tuple):
field_type = field_type[0](**field_type[1])
self._field_types.append(field_type if field_type else BaseField())
super(MultiTypeField, self).__init__(**kwargs)

def get_field_docstring(self):
if len(self._field_types):
return 'Multiple type values allowed:\n{0}'.format("\n".join(["* {0}".format(field.get_field_docstring())
for field in self._field_types]))

def export_definition(self):
result = super(MultiTypeField, self).export_definition()
result['field_types'] = [(field_type.__class__, field_type.export_definition())
for field_type in self._field_types]
return result

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

def check_value(self, value):
for ft in self._field_types:
if ft.check_value(value):
return True
return False

def can_use_value(self, value):
for ft in self._field_types:
if ft.can_use_value(value):
return True
return False
14 changes: 14 additions & 0 deletions dirty_models/models.py
Expand Up @@ -8,6 +8,7 @@
from datetime import datetime

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
Expand Down Expand Up @@ -35,6 +36,16 @@ def __init__(cls, name, bases, classdict):
read_only_fields.append(field.name)

cls._structure = structure
default_data = {}
for p in bases:
try:
default_data.update(deepcopy(p._default_data))
except AttributeError:
pass

default_data.update(deepcopy(cls._default_data))
default_data.update({f.name: f.default for f in structure.values() if f.default is not None})
cls._default_data = default_data

def process_base_field(cls, field, key):
"""
Expand Down Expand Up @@ -83,13 +94,16 @@ class BaseModel(BaseData, metaclass=DirtyModelMeta):
_modified_data = None
_deleted_fields = None

_default_data = {}

def __init__(self, data=None, flat=False, *args, **kwargs):
super(BaseModel, self).__init__(*args, **kwargs)
self._original_data = {}
self._modified_data = {}
self._deleted_fields = []

self.unlock()
self.import_data(self._default_data)
if isinstance(data, (dict, Mapping)):
self.import_data(data)
self.import_data(kwargs)
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Expand Up @@ -5,13 +5,14 @@
name='dirty-models',
url='https://github.com/alfred82santa/dirty-models',
author='alfred82santa',
version='0.5.2',
version='0.6.0',
author_email='alfred82santa@gmail.com',
classifiers=[
'Intended Audience :: Developers',
'Programming Language :: Python',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'License :: OSI Approved :: BSD License',
'Development Status :: 4 - Beta'],
packages=['dirty_models'],
Expand Down
97 changes: 96 additions & 1 deletion tests/dirty_models/tests_fields.py
@@ -1,7 +1,7 @@
from unittest import TestCase
from dirty_models.fields import (IntegerField, StringField, BooleanField,
FloatField, ModelField, TimeField, DateField,
DateTimeField, ArrayField, StringIdField, HashMapField)
DateTimeField, ArrayField, StringIdField, HashMapField, MultiTypeField)
from dirty_models.models import BaseModel, HashMapModel
from dirty_models.model_types import ListModel

Expand Down Expand Up @@ -1309,3 +1309,98 @@ def test_array_field_no_autolist(self):
self.model.__class__.__dict__['array_field'].autolist = False
self.model.array_field = 'foo'
self.assertEqual(self.model.export_data(), {})


class TestMultiTypeFieldSimpleTypes(TestCase):

def setUp(self):
super(TestMultiTypeFieldSimpleTypes, self).setUp()

class MultiTypeModel(BaseModel):
multi_field = MultiTypeField(field_types=[IntegerField(), StringField()])

self.model = MultiTypeModel()

def test_string_field(self):
self.model.multi_field = 'foo'
self.assertEqual(self.model.multi_field, 'foo')

def test_integer_field(self):
self.model.multi_field = 3
self.assertEqual(self.model.multi_field, 3)

def test_update_string_field(self):
self.model.multi_field = 3
self.model.flat_data()
self.model.multi_field = 'foo'
self.assertEqual(self.model.multi_field, 'foo')

def test_update_integer_field(self):
self.model.multi_field = 'foo'
self.model.flat_data()
self.model.multi_field = 3
self.assertEqual(self.model.multi_field, 3)

def test_no_update_integer_field(self):
self.model.multi_field = 3
self.model.flat_data()
self.model.multi_field = [3, 4]
self.assertEqual(self.model.multi_field, 3)

def test_integer_field_use_float(self):
self.model.multi_field = 3.0
self.assertEqual(self.model.multi_field, 3)

def test_string_field_conversion_priority(self):
self.model.multi_field = '3'
self.assertEqual(self.model.multi_field, '3')

def test_multi_field_desc(self):
self.maxDiff = None
field = MultiTypeField(field_types=[IntegerField(), StringField()])
self.assertEqual(field.export_definition(), {
'alias': None,
'doc': "\n".join(['Multiple type values allowed:',
'* IntegerField field',
'* StringField field']),
'field_types': [(IntegerField, {'alias': None,
'doc': 'IntegerField field',
'name': None,
'read_only': False}),
(StringField, {'alias': None,
'doc': 'StringField field',
'name': None,
'read_only': False})],
'name': None,
'read_only': False})


class TestMultiTypeFieldComplexTypes(TestCase):

def setUp(self):
super(TestMultiTypeFieldComplexTypes, self).setUp()

class MultiTypeModel(BaseModel):
multi_field = MultiTypeField(field_types=[IntegerField(), (ArrayField, {"field_type": StringField()})])

self.model = MultiTypeModel()

def test_integer_field(self):
self.model.multi_field = 3
self.assertEqual(self.model.multi_field, 3)

def test_array_field(self):
self.model.multi_field = ['foo', 'bar']
self.assertEqual(self.model.multi_field.export_data(), ['foo', 'bar'])

def test_update_array_field(self):
self.model.multi_field = 3
self.model.flat_data()
self.model.multi_field = ['foo', 'bar']
self.assertEqual(self.model.multi_field.export_data(), ['foo', 'bar'])

def test_update_integer_field(self):
self.model.multi_field = ['foo', 'bar']
self.model.flat_data()
self.model.multi_field = 3
self.assertEqual(self.model.multi_field, 3)

0 comments on commit 65bd96c

Please sign in to comment.