diff --git a/rest_framework/compat.py b/rest_framework/compat.py index e435618a2..3a686e6a8 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -6,6 +6,8 @@ versions of Django/Python, and compatibility wrappers around optional packages. # flake8: noqa from __future__ import unicode_literals +import inspect + import django from django.conf import settings from django.db import connection, transaction @@ -185,3 +187,36 @@ def template_render(template, context=None, request=None): # backends template, e.g. django.template.backends.django.Template else: return template.render(context, request=request) + + +def is_simple_callable(obj): + """ + True if the object is a callable that takes no arguments. + """ + function = inspect.isfunction(obj) + method = inspect.ismethod(obj) + + if not (function or method): + return False + + # - `inspect.signature` is introduced in Python 3.3 + # - `inspect.getfullargspec` is introduced in Python 3.0 + # and deprecated since Python 3.5 + # - `inspect.getargspec` is deprecated since Python 3.0 + if hasattr(inspect, 'signature'): + sig = inspect.signature(obj) + for name, param in sig.parameters.items(): + if param.default is not inspect.Parameter.empty: + return False + + return True + else: + if hasattr(inspect, 'getfullargspec'): + spec = inspect.getfullargspec(obj) + args, defaults = spec.args, spec.defaults + else: + args, _, _, defaults = inspect.getargspec(obj) + + len_args = len(args) if function else len(args) - 1 + len_defaults = len(defaults) if defaults else 0 + return len_args <= len_defaults diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 5eb1b3b4c..67ef64ee2 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -30,7 +30,9 @@ from django.utils.ipv6 import clean_ipv6_address from django.utils.translation import ugettext_lazy as _ from rest_framework import ISO_8601 -from rest_framework.compat import unicode_repr, unicode_to_repr +from rest_framework.compat import ( + is_simple_callable, unicode_repr, unicode_to_repr +) from rest_framework.exceptions import ValidationError from rest_framework.settings import api_settings from rest_framework.utils import html, humanize_datetime, representation @@ -46,22 +48,6 @@ class empty: pass -def is_simple_callable(obj): - """ - True if the object is a callable that takes no arguments. - """ - function = inspect.isfunction(obj) - method = inspect.ismethod(obj) - - if not (function or method): - return False - - args, _, _, defaults = inspect.getargspec(obj) - len_args = len(args) if function else len(args) - 1 - len_defaults = len(defaults) if defaults else 0 - return len_args <= len_defaults - - def get_attribute(instance, attrs): """ Similar to Python's built in `getattr(instance, attr)`, diff --git a/rest_framework/relations.py b/rest_framework/relations.py index 572b69170..5d8a41ad8 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -14,9 +14,8 @@ from django.utils.encoding import smart_text from django.utils.six.moves.urllib import parse as urlparse from django.utils.translation import ugettext_lazy as _ -from rest_framework.fields import ( - Field, empty, get_attribute, is_simple_callable, iter_options -) +from rest_framework.compat import is_simple_callable +from rest_framework.fields import Field, empty, get_attribute, iter_options from rest_framework.reverse import reverse from rest_framework.utils import html