Skip to content

Commit

Permalink
Merge 9741cc9 into 3fcc0c8
Browse files Browse the repository at this point in the history
  • Loading branch information
jamadden committed Sep 20, 2018
2 parents 3fcc0c8 + 9741cc9 commit 087be85
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 27 deletions.
4 changes: 3 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
1.0.0a13 (unreleased)
=====================

- Nothing changed yet.
- Support ``IFromBytes`` fields introduced by zope.schema 4.8.0. See
`issue 92
<https://github.com/NextThought/nti.externalization/issues/92>`_.


1.0.0a12 (2018-09-11)
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ def _c(m):
'setuptools',
'BTrees',
'isodate',
'nti.schema >= 1.5.0',
'nti.schema >= 1.7.0',
'persistent >= 4.4.0',
'PyYAML',
'pytz',
Expand All @@ -183,7 +183,7 @@ def _c(m):
'zope.location',
'zope.mimetype >= 2.3.0',
'zope.proxy',
'zope.schema >= 4.7.0',
'zope.schema >= 4.8.0',
'zope.security',
],
extras_require={
Expand Down
5 changes: 5 additions & 0 deletions src/nti/externalization/internalization/_fields.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ cdef notify

# optimizations
cdef IField_providedBy
cdef IFromBytes_providedBy
cdef IFromUnicode_providedBy
cdef get_exc_info

Expand Down Expand Up @@ -60,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: 36 additions & 14 deletions src/nti/externalization/internalization/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from zope.interface import implementedBy

from zope.schema.interfaces import IField
from zope.schema.interfaces import IFromBytes
from zope.schema.interfaces import IFromUnicode
from zope.schema.interfaces import SchemaNotProvided
from zope.schema.interfaces import SchemaNotCorrectlyImplemented
Expand All @@ -35,6 +36,7 @@
from zope.event import notify

IField_providedBy = IField.providedBy
IFromBytes_providedBy = IFromBytes.providedBy
IFromUnicode_providedBy = IFromUnicode.providedBy

__all__ = [
Expand Down Expand Up @@ -144,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 @@ -263,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 @@ -282,10 +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
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
21 changes: 21 additions & 0 deletions src/nti/externalization/internalization/tests/test_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,27 @@ class Foo(object):
self._callFUT(foo, IFoo, u'field', u'text')()
assert_that(foo, has_attr('field', u'text'))

def test_from_bytes(self):
from zope.schema.interfaces import IFromBytes
from zope.schema import Field
@interface.implementer(IFromBytes)
class OnlyBytes(Field):
_type = bytes

def fromBytes(self, value):
return b'from bytes'

class IFoo(interface.Interface):
field = OnlyBytes(title=u'text')

class Foo(object):
pass

foo = Foo()
self._callFUT(foo, IFoo, u'field', b'text')()
assert_that(foo, has_attr('field', b'from bytes'))


def test_raises_SchemaNotCorrectlyImplemented(self):
from zope.schema.interfaces import SchemaNotCorrectlyImplemented
from zope.schema import TextLine
Expand Down
10 changes: 7 additions & 3 deletions src/nti/externalization/tests/test_datastructures.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,11 +413,15 @@ class O(object):
inst.updateFromExternalObject({'ivar': u'2.0'})
assert_that(ext_self, has_property('ivar', 2.0))

# A byte string is NOT fine
# A byte string is fine
inst.updateFromExternalObject({'ivar': b'3.0'})

# An object is NOT fine
with self.assertRaises(WrongType):
inst.updateFromExternalObject({'ivar': b'3.0'})
inst.updateFromExternalObject({'ivar': object()})

assert_that(ext_self, has_property('ivar', 2.0))

assert_that(ext_self, has_property('ivar', 3.0))


# Now a validation error after set
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 087be85

Please sign in to comment.