mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-02-03 13:14:30 +03:00
Raise exception when field source is a built-in (#6766)
This commit is contained in:
parent
91ea138406
commit
7179ea9984
|
@ -47,10 +47,24 @@ class empty:
|
||||||
pass
|
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):
|
def is_simple_callable(obj):
|
||||||
"""
|
"""
|
||||||
True if the object is a callable that takes no arguments.
|
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)):
|
if not (inspect.isfunction(obj) or inspect.ismethod(obj) or isinstance(obj, functools.partial)):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -427,6 +441,18 @@ class Field:
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return get_attribute(instance, self.source_attrs)
|
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:
|
except (KeyError, AttributeError) as exc:
|
||||||
if self.default is not empty:
|
if self.default is not empty:
|
||||||
return self.get_default()
|
return self.get_default()
|
||||||
|
|
|
@ -14,7 +14,9 @@ from django.utils.timezone import activate, deactivate, override, utc
|
||||||
import rest_framework
|
import rest_framework
|
||||||
from rest_framework import exceptions, serializers
|
from rest_framework import exceptions, serializers
|
||||||
from rest_framework.compat import ProhibitNullCharactersValidator
|
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.
|
# Tests for helper functions.
|
||||||
# ---------------------------
|
# ---------------------------
|
||||||
|
@ -86,6 +88,18 @@ class TestIsSimpleCallable:
|
||||||
|
|
||||||
assert is_simple_callable(ChoiceModel().get_choice_field_display)
|
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):
|
def test_type_annotation(self):
|
||||||
# The annotation will otherwise raise a syntax error in python < 3.5
|
# The annotation will otherwise raise a syntax error in python < 3.5
|
||||||
locals = {}
|
locals = {}
|
||||||
|
@ -206,6 +220,18 @@ class TestSource:
|
||||||
|
|
||||||
assert 'method call failed' in str(exc_info.value)
|
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:
|
class TestReadOnly:
|
||||||
def setup(self):
|
def setup(self):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user