Skip to content

Commit

Permalink
Added tests and documentation around validation.
Browse files Browse the repository at this point in the history
* Added another exception that is raised when an unknown attribute is
  encountered.
* Expanded validate() for #8. This fixes #8.
  • Loading branch information
brownhead committed Dec 23, 2013
1 parent a0de57d commit 7793334
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 21 deletions.
9 changes: 9 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ Fields
.. automodule:: mangoengine
:members: Field, StringField, UnicodeField, NumericField, IntegralField, DictField, ListField

Errors
------

.. autoclass:: mangoengine.ValidationFailure
:members:

.. autoclass:: mangoengine.UnknownAttribute
:members:

The ModelMetaclass Metaclass
----------------------------

Expand Down
19 changes: 17 additions & 2 deletions mangoengine/errors.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
class ValidationFailure(RuntimeError):
"""
Raised by MangoEngine when the value given for a particular field is not
valid.
Raised by :meth:`Model.validate()` when the value given for a particular
field is not valid.
:ivar field_name: The value of the field's ``name`` attribute.
:ivar description: A description of the failure.
Expand All @@ -14,3 +14,18 @@ def __init__(self, field_name, description):

def __str__(self):
return "In field '%s': %s" % (self.field_name, self.description)

class UnknownAttribute(ValidationFailure):
"""
Raised by :meth:`Model.validate()` when ``allow_unknown_data`` is ``False``
and an unknown attribute is encountered.
Inherits from :class:`.ValidationFailure`.
"""

def __init__(self, attribute_name):
self.attribute_name = attribute_name

def __str__(self):
return "Unknown attribute '%s'." % (self.attribute_name, )
2 changes: 1 addition & 1 deletion mangoengine/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def __init__(self, of = None, **default_kwargs):

def validate(self, value):
# Validate all of the list items
if self.of is not None:
if value is not None and self.of is not None:
for i in value:
self.of.validate(i)

Expand Down
44 changes: 26 additions & 18 deletions mangoengine/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# mongoengine
import fields
from .fields import Field
from .errors import UnknownAttribute

class ModelMetaclass(type):
"""
Expand Down Expand Up @@ -48,7 +49,7 @@ def __new__(cls, clsname, bases, dct):

for k, v in dct.items():
# If this attribute defines a field...
if isinstance(v, fields.Field):
if isinstance(v, Field):
# Set the name attribute of the field, this allows for better
# error messages.
v.name = k
Expand All @@ -65,8 +66,10 @@ class Model(object):
"""
Derive from this class to make your own models.
:ivar _fields: A dictionary mapping any field names to their
:cvar _fields: A dictionary mapping any field names to their
:class:`mangoengine.Field` instances.
:cvar _allow_unknown_data: Sets the default value for the
``allow_unknown_data`` parameter in :meth:`.validate()`.
.. code-block:: python
Expand Down Expand Up @@ -111,17 +114,31 @@ def __repr__(self):
args = ", ".join("%s = %s" % (k, repr(v)) for k, v in arg_list)
return "%s(%s)" % (type(self).__name__, args)

def validate(self):
def validate(self, allow_unknown_data = None):
"""
Validates the object to ensure each field has an appropriate value. An
exception is thrown if validation fails.
Validates the object to ensure each field has an appropriate value. A
:class:`mangoengine.ValidationFailure` will be thrown if validation
fails.
:returns: None
:param allow_unknown_data: If True, when an unknown attribute is found
the validation will fail. Uses the value of
``self._allow_unknown_data`` if ``None`` is specified, or ``True``
if no such attribute exists.
:raises ValidationFailure: On failure.
:returns: ``None``
"""

# Default to the class attribute if the user didn't specify a value
if allow_unknown_data is None:
allow_unknown_data = getattr(self, "_allow_unknown_data", True)

if not allow_unknown_data:
expected_keys = set(self._fields.keys())
for i in self.to_dict().keys():
if i not in expected_keys:
raise UnknownAttribute(i)

for k, v in self._fields.items():
v.validate(getattr(self, k))

Expand All @@ -145,15 +162,6 @@ def from_dict(cls, dictionary):
return instance

def to_dict(self):
"""
Tranforms the current object into a dictionary representation.
.. note::
It very uninterestingly calls the ``vars()`` built-in on itself,
but should be used instead of calling ``vars()`` directly in case
of implementation changes in the future.
"""
"""Tranforms the current object into a dictionary representation."""

return vars(self)
38 changes: 38 additions & 0 deletions mangoengine/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# internal
from ..models import Model
from ..fields import *
from ..errors import *

# external
import pytest

class TestModel:
def test_unrelated_attributes(self):
Expand Down Expand Up @@ -137,3 +142,36 @@ class Person(Model):
"name": None,
"siblings": None
}

def test_validate(self):
class Person(Model):
name = StringField()
age = IntegralField(bounds = (0, None))
siblings = ListField(of = StringField())

data1 = {
"name": "Joe Shmoe",
"age": 21,
"siblings": ["Dick Shmoe", "Jane Shmoe"]
}
person1 = Person(**data1)
person1.validate() # Should not raise an exception

# Create a copy of the dictionary with the additional keyvalue
# "chocolate": "chips".
data2 = dict(data1.items() + [("chocolate", "chips")])
person2 = Person.from_dict(data2)
person2.validate()
with pytest.raises(UnknownAttribute):
person2.validate(allow_unknown_data = False)

# Make sure we can override the default properly
class Person2(Model):
_allow_unknown_data = False

name = StringField()
age = IntegralField(bounds = (0, None))
siblings = ListField(of = StringField())
person3 = Person2.from_dict(data2)
with pytest.raises(UnknownAttribute):
person3.validate()

0 comments on commit 7793334

Please sign in to comment.