Skip to content

Commit

Permalink
Call fromObject earlier in the process, if present.
Browse files Browse the repository at this point in the history
  • Loading branch information
jamadden committed Sep 20, 2018
1 parent ca423b7 commit 9741cc9
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 23 deletions.
4 changes: 4 additions & 0 deletions src/nti/externalization/internalization/_fields.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,9 @@ cdef _handle_WrongContainedType(field_name, field, value)

cdef str _as_native_str(s)

cdef _not_set
cdef list _CONVERTERS
cdef _test_and_validate(value, kind, field, meth_name)

cpdef validate_field_value(self, field_name, field, value)
cpdef validate_named_field_value(self, iface, field_name, value)
50 changes: 34 additions & 16 deletions src/nti/externalization/internalization/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,22 +146,21 @@ def _adapt_sequence(field, value):
# using it. Some other things do not, for example nti.schema.field.Variant
# They might provide a `fromObject` function to do the conversion
# The field may be able to handle the whole thing by itself or we may need
# to do the individual objects
# to do the individual objects. If there was a `fromObject` for the field,
# we called it already.

# The conversion process may raise TypeError
if hasattr(field, 'fromObject'):
value = field.fromObject(value)
value_type = field.value_type
if hasattr(value_type, 'fromObject'):
converter = value_type.fromObject
elif hasattr(value_type, 'schema'):
converter = value_type.schema
else:
if hasattr(field.value_type, 'fromObject'):
converter = field.value_type.fromObject
elif hasattr(field.value_type, 'schema'):
converter = field.value_type.schema
else:
raise CannotConvertSequenceError(
"Don't know how to convert sequence %r for field %s"
% (value, field))
raise CannotConvertSequenceError(
"Don't know how to convert sequence %r for field %s"
% (value, field))

value = [converter(v) for v in value]
value = [converter(v) for v in value]

return value

Expand Down Expand Up @@ -265,6 +264,21 @@ def _handle_WrongContainedType(field_name, field, value):

return value

_not_set = object()

_CONVERTERS = [
('fromUnicode', text_type),
('fromBytes', bytes),
('fromObject', object)
]

def _test_and_validate(value, kind, field, meth_name):
if isinstance(value, kind):
meth = getattr(field, meth_name, None)
if meth is not None:
return meth(value)
return _not_set

def validate_field_value(self, field_name, field, value):
"""
Given a :class:`zope.schema.interfaces.IField` object from a schema
Expand All @@ -284,12 +298,16 @@ def validate_field_value(self, field_name, field, value):
"""
field = field.bind(self)
try:
if isinstance(value, text_type) and IFromUnicode_providedBy(field):
value = field.fromUnicode(value) # implies validation
elif isinstance(value, bytes) and IFromBytes_providedBy(field):
value = field.fromBytes(value) # implies validation
for meth_name, kind in _CONVERTERS:
result = _test_and_validate(value, kind, field, meth_name)
if result is not _not_set:
# We did it! Yay!
value = result
break
else:
# Here if we do not break out of the loop.
field.validate(value)

except SchemaNotProvided:
# Raised by Object fields
value = _handle_SchemaNotProvided(field_name, field, value)
Expand Down
22 changes: 15 additions & 7 deletions src/nti/externalization/tests/test_internalization.py
Original file line number Diff line number Diff line change
Expand Up @@ -702,26 +702,34 @@ def bind(self, _):
self._callFUT(Field(), [object()])


def test_wrong_contained_type_field_fromObject(self):
def test_wrong_contained_type_field(self):
from zope.schema import Object
from zope.schema import List
from zope.schema.interfaces import WrongContainedType

class FromList(List):
def fromObject(self, o):
assert isinstance(o, list)
return o

class IThing(interface.Interface):
pass

field = FromList(value_type=Object(IThing))
field = List(value_type=Object(IThing))

# This gets us to the second pass, after we run the fromObject
# one time.
# one time. _adapt_sequence fails.
with self.assertRaises(WrongContainedType):
self._callFUT(field, [object()])

class Conforms(object):

def __conform__(self, iface):
assert iface is IThing
return 'conforms'

with self.assertRaises(WrongContainedType) as exc:
self._callFUT(field, [Conforms()])

ex = exc.exception
assert_that(ex.errors[0], has_property('value', is_(Conforms)))

def test_wrong_contained_type_value_type_fromObject(self):
from zope.schema import Object
from zope.schema import List
Expand Down

0 comments on commit 9741cc9

Please sign in to comment.