Raise exception when field source is a built-in (#6766)

This commit is contained in:
Ryan P Kilby 2019-07-01 05:25:47 -07:00 committed by Tom Christie
parent 91ea138406
commit 7179ea9984
2 changed files with 53 additions and 1 deletions

View File

@ -47,10 +47,24 @@ class empty:
pass
class BuiltinSignatureError(Exception):
"""
Built-in function signatures are not inspectable. This exception is raised
so the serializer can raise a helpful error message.
"""
pass
def is_simple_callable(obj):
"""
True if the object is a callable that takes no arguments.
"""
# Bail early since we cannot inspect built-in function signatures.
if inspect.isbuiltin(obj):
raise BuiltinSignatureError(
'Built-in function signatures are not inspectable. '
'Wrap the function call in a simple, pure Python function.')
if not (inspect.isfunction(obj) or inspect.ismethod(obj) or isinstance(obj, functools.partial)):
return False
@ -427,6 +441,18 @@ class Field:
"""
try:
return get_attribute(instance, self.source_attrs)
except BuiltinSignatureError as exc:
msg = (
'Field source for `{serializer}.{field}` maps to a built-in '
'function type and is invalid. Define a property or method on '
'the `{instance}` instance that wraps the call to the built-in '
'function.'.format(
serializer=self.parent.__class__.__name__,
field=self.field_name,
instance=instance.__class__.__name__,
)
)
raise type(exc)(msg)
except (KeyError, AttributeError) as exc:
if self.default is not empty:
return self.get_default()

View File

@ -14,7 +14,9 @@ from django.utils.timezone import activate, deactivate, override, utc
import rest_framework
from rest_framework import exceptions, serializers
from rest_framework.compat import ProhibitNullCharactersValidator
from rest_framework.fields import DjangoImageField, is_simple_callable
from rest_framework.fields import (
BuiltinSignatureError, DjangoImageField, is_simple_callable
)
# Tests for helper functions.
# ---------------------------
@ -86,6 +88,18 @@ class TestIsSimpleCallable:
assert is_simple_callable(ChoiceModel().get_choice_field_display)
def test_builtin_function(self):
# Built-in function signatures are not easily inspectable, so the
# current expectation is to just raise a helpful error message.
timestamp = datetime.datetime.now()
with pytest.raises(BuiltinSignatureError) as exc_info:
is_simple_callable(timestamp.date)
assert str(exc_info.value) == (
'Built-in function signatures are not inspectable. Wrap the '
'function call in a simple, pure Python function.')
def test_type_annotation(self):
# The annotation will otherwise raise a syntax error in python < 3.5
locals = {}
@ -206,6 +220,18 @@ class TestSource:
assert 'method call failed' in str(exc_info.value)
def test_builtin_callable_source_raises(self):
class BuiltinSerializer(serializers.Serializer):
date = serializers.ReadOnlyField(source='timestamp.date')
with pytest.raises(BuiltinSignatureError) as exc_info:
BuiltinSerializer({'timestamp': datetime.datetime.now()}).data
assert str(exc_info.value) == (
'Field source for `BuiltinSerializer.date` maps to a built-in '
'function type and is invalid. Define a property or method on '
'the `dict` instance that wraps the call to the built-in function.')
class TestReadOnly:
def setup(self):