Skip to content

Commit

Permalink
Merge ae00e01 into 3783039
Browse files Browse the repository at this point in the history
  • Loading branch information
alfred82santa committed Oct 31, 2016
2 parents 3783039 + ae00e01 commit 6e3890b
Show file tree
Hide file tree
Showing 15 changed files with 285 additions and 126 deletions.
4 changes: 3 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ python:
- "3.4"
- "3.5"
# command to install dependencies
install:
install:
- make requirements
- pip install -r requirements-test.txt
- if [[ $TRAVIS_PYTHON_VERSION == 3.3.* ]]; then pip install -r requirements-py33.txt --use-mirrors; fi
- pip install coveralls
# command to run tests
script:
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ requirements-docs:

run-tests:
@echo "Running tests..."
nosetests --with-coverage -d --cover-package=dirty_models --cover-erase
nosetests --with-coverage -d --cover-package=dirty_models --cover-erase -x

publish:
@echo "Publishing new version on Pypi..."
Expand Down
16 changes: 13 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,16 @@ Features
Changelog
---------

Version 0.9.0
-------------

- New EnumField.
- Fixes on setup.py.
- Fixes on requirements.
- Fixes on formatter iters.
- Fixes on code.


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

Expand All @@ -101,10 +111,10 @@ Version 0.8.0
- Raise a RunTimeError exception if two fields use same alias in a model.
- Fixed default docstrings.
- Cleanup default data. Only real name fields are allowed to use as key.
- Added :meth:`~dirty_models.models.get_attrs_by_path` in order to get all values using path.
- Added :meth:`~dirty_models.models.get_1st_attr_by_path` in order to get first value using path.
- Added :meth:`~dirty_models.models.BaseModel.get_attrs_by_path` in order to get all values using path.
- Added :meth:`~dirty_models.models.BaseModel.get_1st_attr_by_path` in order to get first value using path.
- Added option to access fields like in a dictionary, but using wildcards. Only for getters.
See: :meth:`~dirty_models.models.get_1st_attr_by_path`.
See: :meth:`~dirty_models.models.BaseModel.get_1st_attr_by_path`.
- Added some documentation.

Version 0.7.2
Expand Down
95 changes: 84 additions & 11 deletions dirty_models/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
"""

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

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

from .model_types import ListModel

Expand Down Expand Up @@ -79,31 +81,70 @@ def delete_value(self, obj):
"""Removes field value from model"""
obj.delete_field_value(self.name)

def _check_name(self):
if self._name is None:
raise AttributeError("Field name must be set")

def __get__(self, obj, cls=None):
if obj is None:
return self

self._check_name()

if self._getter:
return self._getter(self, obj, cls)
if self._name is None:
raise AttributeError("Field name must be set")

return self.get_value(obj)

def __set__(self, obj, value):
self._check_name()

if self._setter:
self._setter(self, obj, value)
return
if self._name is None:
raise AttributeError("Field name must be set")

if self.check_value(value) or self.can_use_value(value):
self.set_value(obj, self.use_value(value))

def __delete__(self, obj):
if self._name is None:
raise AttributeError("Field name must be set")
self._check_name()
self.delete_value(obj)


def can_use_enum(func):
"""
Decorator to use Enum value on type checks.
"""

@wraps(func)
def inner(self, value):
if isinstance(value, Enum):
return self.check_value(value.value) or func(self, value.value)

return func(self, value)

return inner


def convert_enum(func):
"""
Decorator to use Enum value on type casts.
"""

@wraps(func)
def inner(self, value):
try:
if self.check_value(value.value):
return value.value
return func(self, value.value)
except AttributeError:
pass

return func(self, value)

return inner


class IntegerField(BaseField):
"""
It allows to use an integer as value in a field.
Expand All @@ -114,17 +155,21 @@ class IntegerField(BaseField):
* :class:`str` if all characters are digits
* :class:`~enum.Enum` if value of enum can be cast.
"""

@convert_enum
def convert_value(self, value):
return int(value)

def check_value(self, value):
return isinstance(value, int)

@can_use_enum
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):
Expand All @@ -136,18 +181,22 @@ class FloatField(BaseField):
* :class:`int`
* :class:`str` if all characters are digits and there is only one dot (``.``).
* :class:`~enum.Enum` if value of enum can be cast.
"""

@convert_enum
def convert_value(self, value):
return float(value)

def check_value(self, value):
return isinstance(value, float)

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


class BooleanField(BaseField):
Expand All @@ -159,8 +208,11 @@ class BooleanField(BaseField):
* :class:`int` ``0`` become ``False``, anything else ``True``
* :class:`str` ``true`` and ``yes`` become ``True``, anything else ``False``. It is case-insensitive.
* :class:`~enum.Enum` if value of enum can be cast.
"""

@convert_enum
def convert_value(self, value):
if isinstance(value, str):
if value.lower().strip() in ['true', 'yes']:
Expand All @@ -173,6 +225,7 @@ def convert_value(self, value):
def check_value(self, value):
return isinstance(value, bool)

@can_use_enum
def can_use_value(self, value):
return isinstance(value, (int, str))

Expand All @@ -187,14 +240,18 @@ class StringField(BaseField):
* :class:`int`
* :class:`float`
* :class:`~enum.Enum` if value of enum can be cast.
"""

@convert_enum
def convert_value(self, value):
return str(value)

def check_value(self, value):
return isinstance(value, str)

@can_use_enum
def can_use_value(self, value):
return isinstance(value, (int, float))

Expand All @@ -209,6 +266,8 @@ class StringIdField(StringField):
* :class:`int`
* :class:`float`
* :class:`~enum.Enum` if value of enum can be cast.
"""

def set_value(self, obj, value):
Expand Down Expand Up @@ -328,6 +387,8 @@ class TimeField(DateTimeBaseField):
* :class:`int` will be used as timestamp.
* :class:`~datetime.datetime` will get time part.
* :class:`~enum.Enum` if value of enum can be cast.
"""

def __init__(self, parse_format=None, default_timezone=None, **kwargs):
Expand All @@ -348,6 +409,7 @@ def __init__(self, parse_format=None, default_timezone=None, **kwargs):
super(TimeField, self).__init__(parse_format=parse_format, **kwargs)
self.default_timezone = default_timezone

@convert_enum
def convert_value(self, value):
if isinstance(value, list):
return time(*value)
Expand All @@ -370,6 +432,7 @@ def convert_value(self, value):
def check_value(self, value):
return isinstance(value, time)

@can_use_enum
def can_use_value(self, value):
return isinstance(value, (int, str, datetime, list, dict))

Expand Down Expand Up @@ -402,8 +465,11 @@ class DateField(DateTimeBaseField):
* :class:`int` will be used as timestamp.
* :class:`~datetime.datetime` will get date part.
* :class:`~enum.Enum` if value of enum can be cast.
"""

@convert_enum
def convert_value(self, value):
if isinstance(value, list):
return date(*value)
Expand All @@ -426,6 +492,7 @@ def convert_value(self, value):
def check_value(self, value):
return type(value) is date

@can_use_enum
def can_use_value(self, value):
return isinstance(value, (int, str, datetime, list, dict))

Expand All @@ -445,6 +512,8 @@ class DateTimeField(DateTimeBaseField):
* :class:`int` will be used as timestamp.
* :class:`~datetime.date` will set date part.
* :class:`~enum.Enum` if value of enum can be cast.
"""

def __init__(self, parse_format=None, default_timezone=None, force_timezone=False, **kwargs):
Expand All @@ -471,6 +540,7 @@ def __init__(self, parse_format=None, default_timezone=None, force_timezone=Fals
self.default_timezone = default_timezone
self.force_timezone = force_timezone

@convert_enum
def convert_value(self, value):
if isinstance(value, list):
return datetime(*value)
Expand All @@ -493,6 +563,7 @@ def convert_value(self, value):
def check_value(self, value):
return type(value) is datetime

@can_use_enum
def can_use_value(self, value):
return isinstance(value, (int, str, date, dict, list))

Expand Down Expand Up @@ -523,15 +594,18 @@ class TimedeltaField(BaseField):
* :class:`int` as seconds.
* :class:`~enum.Enum` if value of enum can be cast.
"""

@convert_enum
def convert_value(self, value):
if isinstance(value, (int, float)):
return timedelta(seconds=value)

def check_value(self, value):
return type(value) is timedelta

@can_use_enum
def can_use_value(self, value):
return isinstance(value, (int, float))

Expand Down Expand Up @@ -607,7 +681,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 Down

0 comments on commit 6e3890b

Please sign in to comment.