Skip to content

Commit

Permalink
Raise exception when field source is a built-in (encode#6766)
Browse files Browse the repository at this point in the history
  • Loading branch information
rpkilby authored and Pierre Chiquet committed Mar 24, 2020
1 parent adeb8a5 commit ba4028e
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 1 deletion.
26 changes: 26 additions & 0 deletions rest_framework/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -427,6 +441,18 @@ def get_attribute(self, instance):
"""
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()
Expand Down
28 changes: 27 additions & 1 deletion tests/test_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
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.
# ---------------------------
Expand Down Expand Up @@ -86,6 +88,18 @@ class Meta:

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 = {}
Expand Down Expand Up @@ -206,6 +220,18 @@ def example_callable(self):

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):
Expand Down

0 comments on commit ba4028e

Please sign in to comment.