Repository: encode/django-rest-framework

Issue:
Objects builtin methods are not called when passed in fields source
I open this ticket to check whether it's worth working on a specific case I spotted recently.
It is not possible to use built-in methods (or functions) in fields "source" argument, as they are not treated as `simple_callable`.

## Steps to reproduce
Example with Django User's date joined:

    # serializers.py
    day_joined = serializer.ReadOnlyField(source=`date_joined.date`)

#### Result
    {
        "day_joined": "<built-in method date of datetime.datetime object at 0x109400000>"
    }

#### Expected result
    {
        "day_joined": "2008-09-03"
    }

## Suggestion
There are [ways](https://stackoverflow.com/questions/48567935/get-parameterarg-count-of-builtin-functions-in-python) to ensure a built-in method/callable is a simple callable (ie can be called without arguments as per [`rest_framework.fields.is_simple_callable`](https://github.com/encode/django-rest-framework/blob/master/rest_framework/fields.py#L56)).

I'm happy to create the PR for this, but need to know if it's worth it beforehand.

Related: see #5114


Discussion:
aha, a bit frustrating to write SerializerMethodField.... instead

I stumbled upon same named method in IPython:
```python
# @ta_env/lib/python3.5/site-packages/IPython/core/oinspect.py:197
def is_simple_callable(obj):
    """True if obj is a function ()"""
    return (inspect.isfunction(obj) or inspect.ismethod(obj) or \
            isinstance(obj, _builtin_func_type) or isinstance(obj, _builtin_meth_type))

# @ta_env/lib/python3.5/site-packages/IPython/core/oinspect.py:58
_builtin_func_type = type(all)
_builtin_meth_type = type(str.upper)  # Bound methods have the same type as builtin functions

```
Hi @antwan. I'm going to echo Tom's response in https://github.com/encode/django-rest-framework/issues/5114#issuecomment-298956002. If you can submit a minimal, low-friction pull request, then that would be worth including. However, the solution in the SO answer is too complex/brittle for the value that it provides. 

That said, if a simple solution can't be found, it would be worth raising a helpful error message. e.g., 
> Python's built-in functions are not supported. Wrap the call in a function, method, or property.

Resolving patch:
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -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 @@ 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()


Test patch:
diff --git a/tests/test_fields.py b/tests/test_fields.py
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -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.
 # ---------------------------
@@ -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 = {}
@@ -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):