Skip to content

Commit

Permalink
merge develop
Browse files Browse the repository at this point in the history
  • Loading branch information
holinnn committed Oct 17, 2017
2 parents 24ae569 + e94750a commit 14f9627
Show file tree
Hide file tree
Showing 52 changed files with 937 additions and 30 deletions.
5 changes: 5 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,8 @@ install:
- pip install -e .
script:
- pytest -q

notifications:
email:
on_success: never
on_failure: always
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ mapper = Mapper()
# 3) register a schema for each of your models you want to map to JSON objects
artist_mapping = mapper.register(Artist, Schema({
"name": f.String(),
"birthDate": f.Datetime(binding="birth_date", format="%Y-%m-%d")
"birthDate": f.DateTime(binding="birth_date", format="%Y-%m-%d")
}))

painting_mapping = mapper.register(Painting, Schema({
Expand Down
165 changes: 165 additions & 0 deletions lupin/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
class MissingMapping(Exception):
"""Error raised when no mapper has been defined for a class
while dumping an instance of that class.
"""
def __init__(self, type):
"""
Args:
type (type): a type
"""
message = "Missing mapping for object of type %s" % type
super(MissingMapping, self).__init__(message)
self.type = type


class InvalidDocument(Exception):
"""Error raised when validating a document.
It's composed of all the errors detected.
"""
def __init__(self, errors):
super(InvalidDocument, self).__init__()
self.errors = errors

def __len__(self):
"""Returns errors count
Returns:
int
"""
return len(self.errors)

def __getitem__(self, index):
"""Returns error at index
Args:
index (int): index
Returns:
Exception
"""
return self.errors[index]


class ValidationError(Exception):
"""Base class for validation errors"""

def __init__(self, message, path):
"""
Args:
message (str): error message
path (list): path of the invalid data
"""
super(ValidationError, self).__init__(message)
self.path = path


class InvalidType(ValidationError):
"""Error raised by `Type` validator"""

def __init__(self, invalid, expected, path):
"""
Args:
invalid (type): invalid type received
expected (type): type expected
path (list): path of the invalid data
"""
message = "Invalid type, got %s instead of %s" % (invalid, expected)
super(InvalidType, self).__init__(message, path)
self.invalid = invalid
self.expected = expected


class NotEqual(ValidationError):
"""Error raised by `Equal` validator"""

def __init__(self, invalid, expected, path):
"""
Args:
invalid (object): invalid value
expected (object): expected value
path (list): path of the invalid data
"""
message = "Invalid value, got %s instead of %s" % (invalid, expected)
super(NotEqual, self).__init__(message, path)
self.invalid = invalid
self.expected = expected


class InvalidMatch(ValidationError):
"""Error raised by `Match` validator"""

def __init__(self, invalid, regexp, path):
"""
Args:
invalid (str): invalid value
regexp (regexp): a regexp
path (list): path of the invalid data
"""
message = "Value \"%s\" does not match pattern \"%s\"" % (invalid, regexp.pattern)
super(InvalidMatch, self).__init__(message, path)
self.invalid = invalid
self.regexp = regexp


class InvalidIn(ValidationError):
"""Error raised by `In` validator"""

def __init__(self, invalid, expected, path):
"""
Args:
invalid (str): invalid value
expected (list): list of expected values
path (list): path of the invalid data
"""
message = "Value \"%s\" is not in %s" % (invalid, expected)
super(InvalidIn, self).__init__(message, path)
self.invalid = invalid
self.expected = expected


class InvalidLength(ValidationError):
"""Error raised by `Length` validator"""

def __init__(self, length, min, max, path):
"""
Args:
length (int): received length
min (int): minimum length
max (int): maximum length
path (list): path of the invalid data
"""
message = "Got %s items, should have been between %s and %s" % (length, min, max)
super(InvalidLength, self).__init__(message, path)
self.max = max
self.min = min
self.length = length


class InvalidDateTimeFormat(ValidationError):
"""Error raised by `DateTimeFormat` validator"""

def __init__(self, value, format, path):
"""
Args:
value (str): invalid datetime string
format (str): format used to parse datetime
path (list): path of the invalid data
"""
message = "Date value \"%s\" can't be parsed with format \"%s\"" % (value, format)
super(InvalidDateTimeFormat, self).__init__(message, path)
self.value = value
self.format = format


class MissingPolymorphicKey(ValidationError):
"""Error raised if Polymorphic object do not contain a type key"""

def __init__(self, key, path):
"""
Args:
key (str): polymorphic key
path (list): path of the invalid data
"""
message = "Polymorphic document does not contain the \"%s\" key " % key
super(MissingPolymorphicKey, self).__init__(message, path)
self.key = key
7 changes: 6 additions & 1 deletion lupin/fields/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
from .field import Field # NOQA
from .datetime_field import Datetime # NOQA
from .datetime_field import DateTime # NOQA
from .date import Date # NOQA
from .string import String # NOQA
from .object import Object # NOQA
from .list import List # NOQA
from .polymorphic_list import PolymorphicList # NOQA
from .constant import Constant # NOQA
from .int import Int # NOQA
from .float import Float # NOQA
from .number import Number # NOQA
from .bool import Bool # NOQA
10 changes: 10 additions & 0 deletions lupin/fields/bool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from . import Field
from ..validators import Type


class Bool(Field):
"""Field used to handle boolean values"""

def __init__(self, **kwargs):
kwargs.setdefault("validators", []).append(Type(bool))
super(Bool, self).__init__(**kwargs)
2 changes: 2 additions & 0 deletions lupin/fields/constant.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from . import Field
from ..validators import Equal


class Constant(Field):
Expand All @@ -9,6 +10,7 @@ def __init__(self, value, **kwargs):
Args:
value (object): fixed value
"""
kwargs.setdefault("validators", []).append(Equal(value))
super(Constant, self).__init__(**kwargs)
self._value = value

Expand Down
39 changes: 39 additions & 0 deletions lupin/fields/date.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from datetime import datetime

from . import Field
from ..validators import Type


class Date(Field):
"""Field used to handle date values"""

def __init__(self, format="%Y-%m-%d", **kwargs):
"""
Args:
format (str): datetime format to use
"""
kwargs.setdefault("validators", []).append(Type(str))
super(Date, self).__init__(**kwargs)
self._format = format

def load(self, value):
"""Loads a date python object from a JSON string
Args:
value (str): a value
Returns:
date
"""
return datetime.strptime(value, self._format).date()

def dump(self, value):
"""Dump a date to string representation
Args:
value (date): a date
Returns:
str
"""
return value.strftime(self._format)
7 changes: 5 additions & 2 deletions lupin/fields/datetime_field.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
from datetime import datetime

from . import Field
from ..validators import Type


class Datetime(Field):
class DateTime(Field):
"""Field used to handle datetime values"""

def __init__(self, format="%Y-%m-%dT%H:%M:%S", *args, **kwargs):
"""
Args:
format (str): datetime format to use
"""
kwargs.setdefault("validators", []).append(Type(str))
super(DateTime, self).__init__(*args, **kwargs)
self._format = format
super(Datetime, self).__init__(*args, **kwargs)

def load(self, value):
"""Loads a datetime python object from a JSON string
Expand Down
36 changes: 27 additions & 9 deletions lupin/fields/field.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
class Field(object):
"""Generic field that does not convert the values"""

def __init__(self, binding=None):
def __init__(self, binding=None, default=None, validators=None):
"""
Args:
binding (str): attribute name to map on object
default (object): default value if data is absent
validators (list): list of validators
"""
self.binding = binding
self._validators = validators or []
self._default = default

def load(self, value):
"""Loads python object from JSON value
Expand Down Expand Up @@ -46,15 +50,29 @@ def extract_value(self, obj, key=None):
raw_value = getattr(obj, key)
return self.dump(raw_value)

def inject_value(self, value, destination, key=None):
"""Load and add value to `destination` dict.
If field has been provided a `binding` then it will
override `key`.
def inject_attr(self, data, key, attributes):
"""Load value from `data` at `key` and inject it in the attributes dictionary.
If field has been provided a `binding` then it will override `key`.
Args:
value (object): a value to load
destination (dict): dictionary to fill with key, value
data (dict): JSON data
key (str): an attribute name
destination (dict): dictionary to fill with key, value
"""
key = self.binding or key
destination[key] = self.load(value)
if key in data:
value = self.load(data[key])
else:
value = self._default

attr_name = self.binding or key
attributes[attr_name] = value

def validate(self, value, path):
"""Validate value againt field validators
Args:
value (object): value to validate
path (list): JSON path of value
"""
for validator in self._validators:
validator(value, path)
10 changes: 10 additions & 0 deletions lupin/fields/float.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from . import Field
from ..validators import Type


class Float(Field):
"""Field used to handle float values"""

def __init__(self, **kwargs):
kwargs.setdefault("validators", []).append(Type(float))
super(Float, self).__init__(**kwargs)
10 changes: 10 additions & 0 deletions lupin/fields/int.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from . import Field
from ..validators import Type


class Int(Field):
"""Field used to handle int values"""

def __init__(self, **kwargs):
kwargs.setdefault("validators", []).append(Type(int))
super(Int, self).__init__(**kwargs)
14 changes: 14 additions & 0 deletions lupin/fields/list.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from . import Field
from ..validators import Type


class List(Field):
Expand All @@ -11,6 +12,7 @@ def __init__(self, field, **kwargs):
Args:
field (Field): a field handling list items
"""
kwargs.setdefault("validators", []).append(Type(list))
super(List, self).__init__(**kwargs)
self._field = field

Expand All @@ -35,3 +37,15 @@ def dump(self, value):
list
"""
return [self._field.dump(item) for item in value]

def validate(self, value, path):
"""Validate each items of list against nested field validators.
Args:
value (list): value to validate
path (list): JSON path of value
"""
super(List, self).validate(value, path)
for item_index, item in enumerate(value):
item_path = path + [str(item_index)]
self._field.validate(item, item_path)

0 comments on commit 14f9627

Please sign in to comment.