Skip to content

Commit

Permalink
Merge pull request #335 from Scille/dict_io_validate
Browse files Browse the repository at this point in the history
Add _dict_io_validate
  • Loading branch information
lafrech committed Jan 4, 2021
2 parents c0c9a55 + 08a4d20 commit 4b74ee6
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 6 deletions.
37 changes: 37 additions & 0 deletions tests/frameworks/test_motor_asyncio.py
Expand Up @@ -394,6 +394,10 @@ class EmbeddedDoc(EmbeddedDocument):
class IOStudent(Student):
io_field = fields.StrField(io_validate=io_validate)
list_io_field = fields.ListField(fields.IntField(io_validate=io_validate))
dict_io_field = fields.DictField(
fields.StrField(),
fields.IntField(io_validate=io_validate),
)
reference_io_field = fields.ReferenceField(
classroom_model.Course, io_validate=io_validate)
embedded_io_field = fields.EmbeddedField(EmbeddedDoc, io_validate=io_validate)
Expand All @@ -403,6 +407,7 @@ class IOStudent(Student):
name='Marty',
io_field='io?',
list_io_field=[1, 2],
dict_io_field={"1": 1, "2": 2},
reference_io_field=bad_reference,
embedded_io_field={'io_field': 42}
)
Expand All @@ -411,6 +416,7 @@ class IOStudent(Student):
assert exc.value.messages == {
'io_field': ['Ho boys !'],
'list_io_field': {0: ['Ho boys !'], 1: ['Ho boys !']},
'dict_io_field': {"1": {"value": ['Ho boys !']}, "2": {"value": ['Ho boys !']}},
'reference_io_field': ['Ho boys !', 'Reference not found for document Course.'],
'embedded_io_field': {'io_field': ['Ho boys !']}
}
Expand Down Expand Up @@ -495,6 +501,37 @@ class IOStudent(Student):

loop.run_until_complete(do_test())

def test_io_validate_dict(self, loop, instance, classroom_model):

async def do_test():

Student = classroom_model.Student
called = []
keys = ["1", "2", "3", "4"]
values = [1, 2, 3, 4]

def io_validate(field, value):
called.append(value)

@instance.register
class IOStudent(Student):
io_field = fields.DictField(
fields.StrField(),
fields.IntField(io_validate=io_validate),
allow_none=True
)

student = IOStudent(name='Marty', io_field=dict(zip(keys, values)))
await student.io_validate()
assert called == values

student.io_field = None
await student.io_validate()
del student.io_field
await student.io_validate()

loop.run_until_complete(do_test())

def test_io_validate_embedded(self, loop, instance, classroom_model):
Student = classroom_model.Student

Expand Down
32 changes: 32 additions & 0 deletions tests/frameworks/test_pymongo.py
Expand Up @@ -287,6 +287,10 @@ class EmbeddedDoc(EmbeddedDocument):
class IOStudent(Student):
io_field = fields.StrField(io_validate=io_validate)
list_io_field = fields.ListField(fields.IntField(io_validate=io_validate))
dict_io_field = fields.DictField(
fields.StrField(),
fields.IntField(io_validate=io_validate),
)
reference_io_field = fields.ReferenceField(
classroom_model.Course, io_validate=io_validate)
embedded_io_field = fields.EmbeddedField(EmbeddedDoc, io_validate=io_validate)
Expand All @@ -296,6 +300,7 @@ class IOStudent(Student):
name='Marty',
io_field='io?',
list_io_field=[1, 2],
dict_io_field={"1": 1, "2": 2},
reference_io_field=bad_reference,
embedded_io_field={'io_field': 42}
)
Expand All @@ -304,6 +309,7 @@ class IOStudent(Student):
assert exc.value.messages == {
'io_field': ['Ho boys !'],
'list_io_field': {0: ['Ho boys !'], 1: ['Ho boys !']},
'dict_io_field': {"1": {"value": ['Ho boys !']}, "2": {"value": ['Ho boys !']}},
'reference_io_field': ['Ho boys !', 'Reference not found for document Course.'],
'embedded_io_field': {'io_field': ['Ho boys !']}
}
Expand Down Expand Up @@ -347,6 +353,32 @@ class IOStudent(Student):
del student.io_field
student.io_validate()

def test_io_validate_dict(self, instance, classroom_model):
Student = classroom_model.Student
called = []
keys = ["1", "2", "3", "4"]
values = [1, 2, 3, 4]

def io_validate(field, value):
called.append(value)

@instance.register
class IOStudent(Student):
io_field = fields.DictField(
fields.StrField(),
fields.IntField(io_validate=io_validate),
allow_none=True
)

student = IOStudent(name='Marty', io_field=dict(zip(keys, values)))
student.io_validate()
assert called == values

student.io_field = None
student.io_validate()
del student.io_field
student.io_validate()

def test_io_validate_embedded(self, instance, classroom_model):
Student = classroom_model.Student

Expand Down
33 changes: 33 additions & 0 deletions tests/frameworks/test_txmongo.py
Expand Up @@ -344,6 +344,10 @@ class EmbeddedDoc(EmbeddedDocument):
class IOStudent(Student):
io_field = fields.StrField(io_validate=io_validate)
list_io_field = fields.ListField(fields.IntField(io_validate=io_validate))
dict_io_field = fields.DictField(
fields.StrField(),
fields.IntField(io_validate=io_validate),
)
reference_io_field = fields.ReferenceField(
classroom_model.Course, io_validate=io_validate)
embedded_io_field = fields.EmbeddedField(EmbeddedDoc, io_validate=io_validate)
Expand All @@ -353,6 +357,7 @@ class IOStudent(Student):
name='Marty',
io_field='io?',
list_io_field=[1, 2],
dict_io_field={"1": 1, "2": 2},
reference_io_field=bad_reference,
embedded_io_field={'io_field': 42}
)
Expand All @@ -361,6 +366,7 @@ class IOStudent(Student):
assert exc.value.messages == {
'io_field': ['Ho boys !'],
'list_io_field': {0: ['Ho boys !'], 1: ['Ho boys !']},
'dict_io_field': {"1": {"value": ['Ho boys !']}, "2": {"value": ['Ho boys !']}},
'reference_io_field': ['Ho boys !', 'Reference not found for document Course.'],
'embedded_io_field': {'io_field': ['Ho boys !']}
}
Expand Down Expand Up @@ -427,6 +433,33 @@ class IOStudent(Student):
yield student.io_validate()
assert called == values

@pytest_inlineCallbacks
def test_io_validate_dict(self, instance, classroom_model):
Student = classroom_model.Student
called = []
keys = ["1", "2", "3", "4"]
values = [1, 2, 3, 4]

def io_validate(field, value):
called.append(value)

@instance.register
class IOStudent(Student):
io_field = fields.DictField(
fields.StrField(),
fields.IntField(io_validate=io_validate),
allow_none=True
)

student = IOStudent(name='Marty', io_field=dict(zip(keys, values)))
yield student.io_validate()
assert called == values

student.io_field = None
yield student.io_validate()
del student.io_field
yield student.io_validate()

@pytest_inlineCallbacks
def test_io_validate_embedded(self, instance, classroom_model):
Student = classroom_model.Student
Expand Down
25 changes: 24 additions & 1 deletion umongo/frameworks/motor_asyncio.py
@@ -1,3 +1,4 @@
import collections
from contextvars import ContextVar
from contextlib import asynccontextmanager

Expand All @@ -13,7 +14,7 @@
from ..document import DocumentImplementation
from ..data_objects import Reference
from ..exceptions import NotCreatedError, UpdateError, DeleteError, NoneReferenceError
from ..fields import ReferenceField, ListField, EmbeddedField
from ..fields import ReferenceField, ListField, DictField, EmbeddedField
from ..query_mapper import map_query

from .tools import cook_find_filter, remove_cls_field_from_embedded_docs
Expand Down Expand Up @@ -361,6 +362,26 @@ async def _list_io_validate(field, value):
raise ma.ValidationError(errors)


async def _dict_io_validate(field, value):
if not value or not field.value_field:
return
validators = field.value_field.io_validate
if not validators:
return
tasks = []
for key, val in value.items():
tasks.append(_run_validators(validators, field.value_field, val))
results = await asyncio.gather(*tasks, return_exceptions=True)
errors = collections.defaultdict(dict)
for key, res in zip(value.keys(), results):
if isinstance(res, ma.ValidationError):
errors[key]["value"] = res.messages
elif res:
raise res
if errors:
raise ma.ValidationError(errors)


async def _embedded_document_io_validate(field, value):
if not value:
return
Expand Down Expand Up @@ -405,6 +426,8 @@ def _patch_field(self, field):
]
if isinstance(field, ListField):
field.io_validate_recursive = _list_io_validate
if isinstance(field, DictField):
field.io_validate_recursive = _dict_io_validate
if isinstance(field, ReferenceField):
field.io_validate.append(_reference_io_validate)
field.reference_cls = MotorAsyncIOReference
Expand Down
21 changes: 20 additions & 1 deletion umongo/frameworks/pymongo.py
@@ -1,3 +1,4 @@
import collections
from contextvars import ContextVar
from contextlib import contextmanager

Expand All @@ -11,7 +12,7 @@
from ..document import DocumentImplementation
from ..data_objects import Reference
from ..exceptions import NotCreatedError, UpdateError, DeleteError, NoneReferenceError
from ..fields import ReferenceField, ListField, EmbeddedField
from ..fields import ReferenceField, ListField, DictField, EmbeddedField
from ..query_mapper import map_query

from .tools import cook_find_filter, remove_cls_field_from_embedded_docs
Expand Down Expand Up @@ -293,6 +294,22 @@ def _list_io_validate(field, value):
raise ma.ValidationError(errors)


def _dict_io_validate(field, value):
if not value or not field.value_field:
return
errors = collections.defaultdict(dict)
validators = field.value_field.io_validate
if not validators:
return
for key, val in value.items():
try:
_run_validators(validators, field.value_field, val)
except ma.ValidationError as exc:
errors[key]["value"] = exc.messages
if errors:
raise ma.ValidationError(errors)


def _embedded_document_io_validate(field, value):
if not value:
return
Expand Down Expand Up @@ -333,6 +350,8 @@ def _patch_field(self, field):
field.io_validate = [validators]
if isinstance(field, ListField):
field.io_validate_recursive = _list_io_validate
if isinstance(field, DictField):
field.io_validate_recursive = _dict_io_validate
if isinstance(field, ReferenceField):
field.io_validate.append(_reference_io_validate)
field.reference_cls = PyMongoReference
Expand Down
31 changes: 27 additions & 4 deletions umongo/frameworks/txmongo.py
Expand Up @@ -10,7 +10,7 @@
from ..document import DocumentImplementation
from ..data_objects import Reference
from ..exceptions import NotCreatedError, UpdateError, DeleteError, NoneReferenceError
from ..fields import ReferenceField, ListField, EmbeddedField
from ..fields import ReferenceField, ListField, DictField, EmbeddedField
from ..query_mapper import map_query

from .tools import cook_find_filter, remove_cls_field_from_embedded_docs
Expand Down Expand Up @@ -216,14 +216,17 @@ def ensure_indexes(cls):
yield cls.collection.create_index(index, **kwargs)


def _errback_factory(errors, field=None):
def _errback_factory(errors, field=None, subkey=None):

def errback(err):
if isinstance(err.value, ma.ValidationError):
error = err.value.messages
if subkey is not None:
error = {subkey: error}
if field is not None:
errors[field] = err.value.messages
errors[field] = error
else:
errors.extend(err.value.messages)
errors.extend(error)
else:
raise err.value

Expand Down Expand Up @@ -299,6 +302,24 @@ def _list_io_validate(field, value):
raise ma.ValidationError(errors)


@inlineCallbacks
def _dict_io_validate(field, value):
if not value or not field.value_field:
return
validators = field.value_field.io_validate
if not validators:
return
errors = {}
defers = []
for key, exc in value.items():
defer = _run_validators(validators, field.value_field, exc)
defer.addErrback(_errback_factory(errors, key, subkey="value"))
defers.append(defer)
yield DeferredList(defers)
if errors:
raise ma.ValidationError(errors)


def _embedded_document_io_validate(field, value):
if not value:
return
Expand Down Expand Up @@ -341,6 +362,8 @@ def _patch_field(self, field):
field.io_validate = validators
if isinstance(field, ListField):
field.io_validate_recursive = _list_io_validate
if isinstance(field, DictField):
field.io_validate_recursive = _dict_io_validate
if isinstance(field, ReferenceField):
field.io_validate.append(_reference_io_validate)
field.reference_cls = TxMongoReference
Expand Down

0 comments on commit 4b74ee6

Please sign in to comment.