Skip to content

Commit 7179ea9

Browse files
rpkilbytomchristie
authored andcommitted
Raise exception when field source is a built-in (#6766)
1 parent 91ea138 commit 7179ea9

File tree

2 files changed

+53
-1
lines changed

2 files changed

+53
-1
lines changed

rest_framework/fields.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,24 @@ class empty:
4747
pass
4848

4949

50+
class BuiltinSignatureError(Exception):
51+
"""
52+
Built-in function signatures are not inspectable. This exception is raised
53+
so the serializer can raise a helpful error message.
54+
"""
55+
pass
56+
57+
5058
def is_simple_callable(obj):
5159
"""
5260
True if the object is a callable that takes no arguments.
5361
"""
62+
# Bail early since we cannot inspect built-in function signatures.
63+
if inspect.isbuiltin(obj):
64+
raise BuiltinSignatureError(
65+
'Built-in function signatures are not inspectable. '
66+
'Wrap the function call in a simple, pure Python function.')
67+
5468
if not (inspect.isfunction(obj) or inspect.ismethod(obj) or isinstance(obj, functools.partial)):
5569
return False
5670

@@ -427,6 +441,18 @@ def get_attribute(self, instance):
427441
"""
428442
try:
429443
return get_attribute(instance, self.source_attrs)
444+
except BuiltinSignatureError as exc:
445+
msg = (
446+
'Field source for `{serializer}.{field}` maps to a built-in '
447+
'function type and is invalid. Define a property or method on '
448+
'the `{instance}` instance that wraps the call to the built-in '
449+
'function.'.format(
450+
serializer=self.parent.__class__.__name__,
451+
field=self.field_name,
452+
instance=instance.__class__.__name__,
453+
)
454+
)
455+
raise type(exc)(msg)
430456
except (KeyError, AttributeError) as exc:
431457
if self.default is not empty:
432458
return self.get_default()

tests/test_fields.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
import rest_framework
1515
from rest_framework import exceptions, serializers
1616
from rest_framework.compat import ProhibitNullCharactersValidator
17-
from rest_framework.fields import DjangoImageField, is_simple_callable
17+
from rest_framework.fields import (
18+
BuiltinSignatureError, DjangoImageField, is_simple_callable
19+
)
1820

1921
# Tests for helper functions.
2022
# ---------------------------
@@ -86,6 +88,18 @@ class Meta:
8688

8789
assert is_simple_callable(ChoiceModel().get_choice_field_display)
8890

91+
def test_builtin_function(self):
92+
# Built-in function signatures are not easily inspectable, so the
93+
# current expectation is to just raise a helpful error message.
94+
timestamp = datetime.datetime.now()
95+
96+
with pytest.raises(BuiltinSignatureError) as exc_info:
97+
is_simple_callable(timestamp.date)
98+
99+
assert str(exc_info.value) == (
100+
'Built-in function signatures are not inspectable. Wrap the '
101+
'function call in a simple, pure Python function.')
102+
89103
def test_type_annotation(self):
90104
# The annotation will otherwise raise a syntax error in python < 3.5
91105
locals = {}
@@ -206,6 +220,18 @@ def example_callable(self):
206220

207221
assert 'method call failed' in str(exc_info.value)
208222

223+
def test_builtin_callable_source_raises(self):
224+
class BuiltinSerializer(serializers.Serializer):
225+
date = serializers.ReadOnlyField(source='timestamp.date')
226+
227+
with pytest.raises(BuiltinSignatureError) as exc_info:
228+
BuiltinSerializer({'timestamp': datetime.datetime.now()}).data
229+
230+
assert str(exc_info.value) == (
231+
'Field source for `BuiltinSerializer.date` maps to a built-in '
232+
'function type and is invalid. Define a property or method on '
233+
'the `dict` instance that wraps the call to the built-in function.')
234+
209235

210236
class TestReadOnly:
211237
def setup(self):

0 commit comments

Comments
 (0)