Skip to content

Commit

Permalink
Add api_object_schema.utils.loose_isinstance
Browse files Browse the repository at this point in the history
  • Loading branch information
vmalloc committed Jun 29, 2014
1 parent 4f911cc commit 6d32c9c
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 22 deletions.
41 changes: 33 additions & 8 deletions api_object_schema/binding.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import itertools

from ._compat import PY2, string_types
from .utils import loose_isinstance


class ObjectAPIBinding(object):

"""This class is responsible for describing how an API object field translates into a Pythonic
Expand Down Expand Up @@ -60,14 +64,31 @@ def get_value_from_api_value(self, system, objtype, obj, api_value):
returned = api_value
for translator in itertools.chain(self.from_api_translators, [self._normalize_value]):
returned = translator(returned)
assert isinstance(returned, self._field.type.type)
assert returned is None or loose_isinstance(returned, self._field.type.type)
return returned

def _normalize_value(self, value):
return self._field.type.type(value)
return self._coerce(value, self._field.type.type)

def _normalize_api_value(self, value):
return self._field.type.api_type(value)
return self._coerce(value, self._field.type.api_type)

def _coerce(self, value, result_type):
if value is None:
return None

if PY2 and result_type is str:
result_type = string_types

if PY2 and result_type is int:
result_type = (int, long)

if not isinstance(value, result_type):
if isinstance(result_type, tuple):
value = result_type[0](value)
else:
value = result_type(value)
return value

def get_api_value_from_object(self, system, objtype, obj):
"""Retrieves the API representation of the field from a whole Pythonic object containing it.
Expand All @@ -85,10 +106,11 @@ def get_api_value_from_value(self, system, objtype, obj, value):
:param obj: the object itself. May be ``None``.
:param value: the Pythonic value for the field
"""
returned = value
for translator in itertools.chain(self.to_api_translators, [self._normalize_api_value]):
value = translator(value)
assert isinstance(value, self._field.type.api_type)
return value
returned = translator(returned)
assert returned is None or loose_isinstance(returned, self._field.type.api_type)
return returned

def set_object_value(self, system, objtype, obj, value):
"""Controls how a Pythonic value is set for a Pythonic object
Expand Down Expand Up @@ -124,10 +146,10 @@ def set_api_object_api_value(self, system, objtype, obj, api_obj, api_value):
class NoBinding(ObjectAPIBinding):

def get_api_value_from_value(self, *args):
raise NotImplementedError() # pragma: no cover
raise NotImplementedError() # pragma: no cover

def get_value_from_api_value(self, *args):
raise NotImplementedError() # pragma: no cover
raise NotImplementedError() # pragma: no cover


class AttributeBinding(ObjectAPIBinding):
Expand Down Expand Up @@ -164,6 +186,7 @@ def set_object_value(self, obj, value):
class FunctionBinding(ObjectAPIBinding):

def __init__(self, get_func=None, set_func=None):
super(FunctionBinding, self).__init__()
self._get_func = get_func
self._set_func = set_func

Expand All @@ -187,6 +210,7 @@ def get_object_value(self, system, objtype, obj):
class ConstBinding(ObjectAPIBinding):

def __init__(self, value):
super(ConstBinding, self).__init__()
self._value = value

def get_object_value(self, system, objtype, obj):
Expand All @@ -196,6 +220,7 @@ def get_object_value(self, system, objtype, obj):
class CountBinding(ObjectAPIBinding):

def __init__(self, list_to_sum_name_or_func):
super(CountBinding, self).__init__()
self._list_name_or_func = list_to_sum_name_or_func

def get_object_value(self, system, objtype, obj):
Expand Down
14 changes: 0 additions & 14 deletions api_object_schema/field.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from ._compat import string_types, PY2
from .binding import NoBinding
from .type_info import TypeInfo
from sentinels import NOTHING
Expand Down Expand Up @@ -66,16 +65,3 @@ def get_is_visible(self, obj):
return self._is_visible(obj)
return self._is_visible

def get_internal_value(self, data):
validation_type = self.type.api_type
if PY2 and validation_type is str:
validation_type = string_types

if PY2 and validation_type is int:
validation_type = (int, long)

if (validation_type is not bool and isinstance(data, bool)) or (data is not None and not isinstance(data, validation_type)):
raise TypeError("{0!r} is not of field internal type(s) {1!r}".format(data, validation_type))

internal_value = self.type.translator.from_api(data)
return internal_value
13 changes: 13 additions & 0 deletions api_object_schema/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
def loose_isinstance(obj, type_):
"""
Like isinstance, only handles ridiculous cases like isinstance(True, int) and isinstance(u"hello", str)
"""
if type_ is int or type_ is long:
if isinstance(obj, bool):
return False
elif isinstance(obj, (int, long)):
return True
# fallthrough
elif type_ is str and isinstance(obj, unicode):
return True
return isinstance(obj, type_)
13 changes: 13 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from api_object_schema.utils import loose_isinstance

def test_loose_isinstance():
for type, matching, not_matching in [
(str, ["hello", u"hello"], [1, True, None]),
(bool, [True, False], ["s", 1.0, 1]),
(int, [100L, 100, 183874837847398473], [1.2, "hello", True, False]),
(long, [100L, 100, 183874837847398473], [1.2, "hello", True, False]),
]:
for m in matching:
assert loose_isinstance(m, type)
for m in not_matching:
assert not loose_isinstance(m, type)

0 comments on commit 6d32c9c

Please sign in to comment.