Skip to content

Commit

Permalink
Merge pull request #90 from alfred82santa/feature/version-0.8.1
Browse files Browse the repository at this point in the history
Feature/version 0.8.1
  • Loading branch information
alfred82santa authored Oct 17, 2016
2 parents 6c69191 + 44540ca commit 08fc35c
Show file tree
Hide file tree
Showing 8 changed files with 344 additions and 158 deletions.
9 changes: 9 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,15 @@ Features
Changelog
---------

Version 0.8.1
-------------

- Added __contains__ function to models and lists. It allows to use ``in`` operator.
- Added ``default_timezone`` parameter to DateTimeFields and TimeFields. If value entered has no a timezone
defined, default one will be set.
- Added ``force_timezone`` parameter to DateTimeFields in order to convert values to a specific timezone.
- More cleanups.

Version 0.8.0
-------------

Expand Down
112 changes: 79 additions & 33 deletions dirty_models/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@
"""

from datetime import datetime, date, time, timedelta
from dateutil.parser import parse as dateutil_parse
from .model_types import ListModel

from collections import Mapping
from dateutil.parser import parse as dateutil_parse

from .model_types import ListModel

__all__ = ['IntegerField', 'FloatField', 'BooleanField', 'StringField', 'StringIdField',
'TimeField', 'DateField', 'DateTimeField', 'TimedeltaField', 'ModelField', 'ArrayField',
'HashMapField', 'BlobField', 'MultiTypeField']


class BaseField:

"""Base field descriptor."""

def __init__(self, name=None, alias=None, getter=None, setter=None, read_only=False, default=None, doc=None):
Expand Down Expand Up @@ -105,7 +105,6 @@ def __delete__(self, obj):


class IntegerField(BaseField):

"""
It allows to use an integer as value in a field.
Expand All @@ -125,11 +124,10 @@ def check_value(self, value):

def can_use_value(self, value):
return isinstance(value, float) \
or (isinstance(value, str) and value.isdigit())
or (isinstance(value, str) and value.isdigit())


class FloatField(BaseField):

"""
It allows to use a float as value in a field.
Expand All @@ -148,12 +146,11 @@ def check_value(self, value):

def can_use_value(self, value):
return isinstance(value, int) or \
(isinstance(value, str) and
(isinstance(value, str) and
value.replace('.', '', 1).isnumeric())


class BooleanField(BaseField):

"""
It allows to use a boolean as value in a field.
Expand Down Expand Up @@ -181,7 +178,6 @@ def can_use_value(self, value):


class StringField(BaseField):

"""
It allows to use a string as value in a field.
Expand All @@ -204,7 +200,6 @@ def can_use_value(self, value):


class StringIdField(StringField):

"""
It allows to use a string as value in a field, but not allows empty strings. Empty string are like ``None``
and they will remove data of field.
Expand All @@ -225,7 +220,6 @@ def set_value(self, obj, value):


class DateTimeBaseField(BaseField):

"""Base field for time or/and date fields."""

date_parsers = {}
Expand All @@ -242,24 +236,13 @@ def __init__(self, parse_format=None, **kwargs):
:type parse_format: str or dict
"""
super(DateTimeBaseField, self).__init__(**kwargs)
self._parse_format = None
self.parse_format = parse_format

def export_definition(self):
result = super(DateTimeBaseField, self).export_definition()
result['parse_format'] = self.parse_format
return result

@property
def parse_format(self):
"""Parse_format getter: datetime format used on field"""
return self._parse_format

@parse_format.setter
def parse_format(self, value):
"""Parse_format setter: datetime format used on field"""
self._parse_format = value

def get_parsed_value(self, value):
"""
Helper to cast string to datetime using :member:`parse_format`.
Expand Down Expand Up @@ -300,6 +283,7 @@ def get_formatted_value(self, value):
:type value: datetime
:return: str
"""

def get_formatter(parser_desc):
try:
return parser_desc['formatter']
Expand Down Expand Up @@ -330,7 +314,6 @@ def get_formatter(parser_desc):


class TimeField(DateTimeBaseField):

"""
It allows to use a time as value in a field.
Expand All @@ -347,6 +330,24 @@ class TimeField(DateTimeBaseField):
* :class:`~datetime.datetime` will get time part.
"""

def __init__(self, parse_format=None, default_timezone=None, **kwargs):
"""
:param parse_format: String format to cast string to datetime. It could be
an string format or a :class:`dict` with two keys:
* ``parser`` key to set how string must be parsed. It could be a callable.
* ``formatter`` key to set how datetime must be formatted. It could be a callable.
:type parse_format: str or dict
:param default_timezone: Default timezone to use when value does not have one.
:type default_timezone: datetime.tzinfo
"""
super(TimeField, self).__init__(parse_format=parse_format, **kwargs)
self.default_timezone = default_timezone

def convert_value(self, value):
if isinstance(value, list):
return time(*value)
Expand All @@ -372,9 +373,21 @@ def check_value(self, value):
def can_use_value(self, value):
return isinstance(value, (int, str, datetime, list, dict))

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)

class DateField(DateTimeBaseField):
super(TimeField, self).set_value(obj, value)

def export_definition(self):
result = super(TimeField, self).export_definition()
if self.default_timezone:
result['default_timezone'] = self.default_timezone
return result


class DateField(DateTimeBaseField):
"""
It allows to use a date as value in a field.
Expand Down Expand Up @@ -418,7 +431,6 @@ def can_use_value(self, value):


class DateTimeField(DateTimeBaseField):

"""
It allows to use a datetime as value in a field.
Expand All @@ -435,6 +447,30 @@ class DateTimeField(DateTimeBaseField):
* :class:`~datetime.date` will set date part.
"""

def __init__(self, parse_format=None, default_timezone=None, force_timezone=False, **kwargs):
"""
:param parse_format: String format to cast string to datetime. It could be
an string format or a :class:`dict` with two keys:
* ``parser`` key to set how string must be parsed. It could be a callable.
* ``formatter`` key to set how datetime must be formatted. It could be a callable.
:type parse_format: str or dict
:param default_timezone: Default timezone to use when value does not have one.
:type default_timezone: datetime.tzinfo
:param force_timezone: If it is True value will be converted to timezone defined on ``default_timezone``
parameter. It ``default_timezone`` is not defined it is ignored.
:type: bool
"""
super(DateTimeField, self).__init__(parse_format=parse_format, **kwargs)
self.default_timezone = default_timezone
self.force_timezone = force_timezone

def convert_value(self, value):
if isinstance(value, list):
return datetime(*value)
Expand All @@ -460,6 +496,22 @@ def check_value(self, value):
def can_use_value(self, value):
return isinstance(value, (int, str, date, dict, list))

def set_value(self, obj, value):
if self.default_timezone:
if value.tzinfo is None:
value = value.replace(tzinfo=self.default_timezone)
elif self.force_timezone and value.tzinfo != self.default_timezone:
value = value.astimezone(tz=self.default_timezone)

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

def export_definition(self):
result = super(DateTimeField, self).export_definition()
if self.default_timezone:
result['default_timezone'] = self.default_timezone
result['force_timezone'] = self.force_timezone
return result


class TimedeltaField(BaseField):
"""
Expand All @@ -485,7 +537,6 @@ def can_use_value(self, value):


class ModelField(BaseField):

"""
It allows to use a model as value in a field. Model type must be
defined on constructor using param model_class. If it is not defined
Expand All @@ -505,7 +556,7 @@ def __init__(self, model_class=None, **kwargs):
self._model_setter = None
if 'setter' in kwargs:
self._model_setter = kwargs['setter']
del(kwargs['setter'])
del (kwargs['setter'])
super(ModelField, self).__init__(**kwargs)

def export_definition(self):
Expand Down Expand Up @@ -556,7 +607,6 @@ def __set__(self, obj, value):


class InnerFieldTypeMixin:

def __init__(self, field_type=None, **kwargs):
self._field_type = None
if isinstance(field_type, tuple):
Expand All @@ -581,7 +631,6 @@ def field_type(self, value):


class ArrayField(InnerFieldTypeMixin, BaseField):

"""
It allows to create a ListModel (iterable in :mod:`dirty_models.types`) of different elements according
to the specified field_type. So it is possible to have a list of Integers, Strings, Models, etc.
Expand Down Expand Up @@ -652,7 +701,6 @@ def autolist(self, value):


class HashMapField(InnerFieldTypeMixin, ModelField):

"""
It allows to create a field which contains a hash map.
Expand All @@ -675,15 +723,13 @@ def convert_value(self, value):


class BlobField(BaseField):

"""
It allows any type of data.
"""
pass


class MultiTypeField(BaseField):

"""
It allows to define multiple type for a field. So, it is possible to define a field as
a integer and as a model field, for example.
Expand All @@ -703,7 +749,7 @@ def __init__(self, field_types=None, **kwargs):
def get_field_docstring(self):
if len(self._field_types):
return 'Multiple type values are allowed:\n\n{0}'.format(
"\n\n".join(["* {0}".format(field.get_field_docstring())for field in self._field_types]))
"\n\n".join(["* {0}".format(field.get_field_docstring()) for field in self._field_types]))

def export_definition(self):
result = super(MultiTypeField, self).export_definition()
Expand Down
6 changes: 3 additions & 3 deletions dirty_models/model_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,6 @@ class ListModel(InnerFieldTypeMixin, BaseData):
to work also as a model, having the old and the modified values.
"""

__original_data__ = None
__modified_data__ = None

def __init__(self, seq=None, *args, **kwargs):
super(ListModel, self).__init__(*args, **kwargs)
self.__original_data__ = []
Expand Down Expand Up @@ -465,3 +462,6 @@ def __repr__(self):

def __str__(self):
return str([item for item in self])

def __contains__(self, item):
return item in self.__modified_data__ if self.__modified_data__ is not None else item in self.__original_data__
13 changes: 7 additions & 6 deletions dirty_models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,17 +156,14 @@ class BaseModel(BaseData, metaclass=DirtyModelMeta):
Base model with dirty feature. It stores original data and saves
modifications in other side.
"""
__original_data__ = None
__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__ = []
BaseModel.__setattr__(self, '__original_data__', {})
BaseModel.__setattr__(self, '__modified_data__', {})
BaseModel.__setattr__(self, '__deleted_fields__', [])

self.unlock()
self.import_data(self.__default_data__)
Expand Down Expand Up @@ -511,6 +508,10 @@ def __str__(self):
def __repr__(self):
return str(self)

def __contains__(self, item):
return item in self.__modified_data__ or (item in self.__original_data__ and
item not in self.__deleted_fields__)

@classmethod
def get_field_obj(cls, name):
obj_field = getattr(cls, name, None)
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
name='dirty-models',
url='https://github.com/alfred82santa/dirty-models',
author='alfred82santa',
version='0.8.0',
version='0.8.1',
author_email='[email protected]',
classifiers=[
'Intended Audience :: Developers',
Expand All @@ -16,7 +16,7 @@
'License :: OSI Approved :: BSD License',
'Development Status :: 4 - Beta'],
packages=['dirty_models'],
include_package_data=True,
include_package_data=False,
install_requires=['python-dateutil'],
description="Dirty models for python 3",
long_description=open(os.path.join(os.path.dirname(__file__), 'README.rst')).read(),
Expand Down
Loading

0 comments on commit 08fc35c

Please sign in to comment.