From 82475ed5f5f8f56215b9807a44f8569c7340614a Mon Sep 17 00:00:00 2001
From: "p.kamayev"
Date: Wed, 27 Apr 2016 20:51:11 +0300
Subject: [PATCH 1/4] fixed check for simple function in fields to support type
annotated functions[F
---
rest_framework/fields.py | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 5dcd546c0..643aa762f 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -55,8 +55,10 @@ def is_simple_callable(obj):
if not (function or method):
return False
-
- args, _, _, defaults = inspect.getargspec(obj)
+ if six.PY2:
+ args, _, _, defaults = inspect.getargspec(obj)
+ else:
+ args, _, _, defaults, _, _, _ = inspect.getfullargspec(obj)
len_args = len(args) if function else len(args) - 1
len_defaults = len(defaults) if defaults else 0
return len_args <= len_defaults
From aadbc0c83c728fe99d1a01b50a10c55c0a5ef018 Mon Sep 17 00:00:00 2001
From: "p.kamayev"
Date: Thu, 28 Apr 2016 08:02:28 +0300
Subject: [PATCH 2/4] moved to compat and covered by tests
---
rest_framework/compat.py | 34 ++++++++++++++++++++
rest_framework/fields.py | 20 +-----------
rest_framework/relations.py | 3 +-
tests/compat/__init__.py | 0
tests/compat/test_compat_py35.py | 17 ++++++++++
tests/test_compat.py | 53 ++++++++++++++++++++++++++++++++
6 files changed, 107 insertions(+), 20 deletions(-)
create mode 100644 tests/compat/__init__.py
create mode 100644 tests/compat/test_compat_py35.py
create mode 100644 tests/test_compat.py
diff --git a/rest_framework/compat.py b/rest_framework/compat.py
index e435618a2..759cd1fbc 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
@@ -136,11 +138,43 @@ if six.PY3:
SHORT_SEPARATORS = (',', ':')
LONG_SEPARATORS = (', ', ': ')
INDENT_SEPARATORS = (',', ': ')
+
+ def is_simple_callable(obj):
+ function = inspect.isfunction(obj)
+ method = inspect.ismethod(obj)
+
+ if not (function or method):
+ return False
+ # when we drop support of python3.2, we should replace getfullargspec with singnature
+ # signature = inspect.signature(obj)
+ # defaults = [p for p in signature.parameters.values() if p.default is not inspect.Parameter.empty]
+ # return len(signature.parameters) <= len(defaults)
+ function = inspect.isfunction(obj)
+ args, _, _, defaults, _, kwonly, kwdefaults = inspect.getfullargspec(obj)
+ len_args = (len(args) if function else len(args) - 1) + len(kwonly or ()) + len(kwdefaults or ())
+ len_defaults = (len(defaults) if defaults else 0) + len(kwdefaults or ())
+ return len_args <= len_defaults
+
else:
SHORT_SEPARATORS = (b',', b':')
LONG_SEPARATORS = (b', ', b': ')
INDENT_SEPARATORS = (b',', b': ')
+ 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
+
try:
# DecimalValidator is unavailable in Django < 1.9
from django.core.validators import DecimalValidator
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 643aa762f..0e79cf918 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -30,7 +30,7 @@ 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 unicode_repr, unicode_to_repr, is_simple_callable
from rest_framework.exceptions import ValidationError
from rest_framework.settings import api_settings
from rest_framework.utils import html, humanize_datetime, representation
@@ -46,24 +46,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
- if six.PY2:
- args, _, _, defaults = inspect.getargspec(obj)
- else:
- args, _, _, defaults, _, _, _ = inspect.getfullargspec(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..554a8c943 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -14,8 +14,9 @@ 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.compat import is_simple_callable
from rest_framework.fields import (
- Field, empty, get_attribute, is_simple_callable, iter_options
+ Field, empty, get_attribute, iter_options
)
from rest_framework.reverse import reverse
from rest_framework.utils import html
diff --git a/tests/compat/__init__.py b/tests/compat/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/compat/test_compat_py35.py b/tests/compat/test_compat_py35.py
new file mode 100644
index 000000000..19b849b0c
--- /dev/null
+++ b/tests/compat/test_compat_py35.py
@@ -0,0 +1,17 @@
+class FunctionSimplicityCheckPy35Mixin:
+ def get_good_cases(self):
+ def annotated_simple() -> int:
+ return 0
+
+ def annotated_defaults(x: int = 0) -> int:
+ return 0
+
+ def kwonly_defaults(*, x=0):
+ pass
+ return super().get_good_cases() + (annotated_simple, annotated_defaults, kwonly_defaults)
+
+ def get_bad_cases(self):
+ def kwonly(*, x):
+ pass
+
+ return super().get_bad_cases() + (kwonly,)
diff --git a/tests/test_compat.py b/tests/test_compat.py
new file mode 100644
index 000000000..b0c40ce34
--- /dev/null
+++ b/tests/test_compat.py
@@ -0,0 +1,53 @@
+from __future__ import unicode_literals
+
+import pytest
+import sys
+
+from rest_framework.compat import is_simple_callable
+
+
+class TestFunctionSimplicityCheck:
+ def get_good_cases(self):
+ def simple():
+ pass
+
+ def simple_with_default(x=0):
+ pass
+
+ class SimpleMethods(object):
+ def simple(self):
+ pass
+
+ def simple_with_default(self, x=0):
+ pass
+
+ return simple, simple_with_default, SimpleMethods().simple, SimpleMethods().simple_with_default
+
+ def get_bad_cases(self):
+ def positional(x):
+ pass
+
+ def many_positional_and_defaults(x, y, z=0):
+ pass
+
+ nofunc = 0
+
+ class Callable:
+ pass
+
+ return positional, many_positional_and_defaults, nofunc, Callable
+
+ def test_good_cases(self):
+ for case in self.get_good_cases():
+ assert is_simple_callable(case)
+
+ def test_bad_cases(self):
+ for case in self.get_bad_cases():
+ assert not is_simple_callable(case)
+
+
+if sys.version_info >= (3, 5):
+ from tests.compat.test_compat_py35 import FunctionSimplicityCheckPy35Mixin
+
+ class TestFunctionSimplicityCheckPy35(FunctionSimplicityCheckPy35Mixin, TestFunctionSimplicityCheck):
+ pass
From edc7680b76103818874e580c8c7a342d7530905a Mon Sep 17 00:00:00 2001
From: "p.kamayev"
Date: Thu, 28 Apr 2016 10:45:38 +0300
Subject: [PATCH 3/4] small refactoring
---
rest_framework/compat.py | 42 ++++++++-----------
.../{test_compat_py35.py => compat_py35.py} | 0
tests/test_compat.py | 2 +-
3 files changed, 18 insertions(+), 26 deletions(-)
rename tests/compat/{test_compat_py35.py => compat_py35.py} (100%)
diff --git a/rest_framework/compat.py b/rest_framework/compat.py
index 759cd1fbc..2a6af4c5c 100644
--- a/rest_framework/compat.py
+++ b/rest_framework/compat.py
@@ -7,6 +7,7 @@ versions of Django/Python, and compatibility wrappers around optional packages.
from __future__ import unicode_literals
import inspect
+import sys
import django
from django.conf import settings
@@ -139,37 +140,28 @@ if six.PY3:
LONG_SEPARATORS = (', ', ': ')
INDENT_SEPARATORS = (',', ': ')
- def is_simple_callable(obj):
- function = inspect.isfunction(obj)
- method = inspect.ismethod(obj)
-
- if not (function or method):
- return False
- # when we drop support of python3.2, we should replace getfullargspec with singnature
- # signature = inspect.signature(obj)
- # defaults = [p for p in signature.parameters.values() if p.default is not inspect.Parameter.empty]
- # return len(signature.parameters) <= len(defaults)
- function = inspect.isfunction(obj)
- args, _, _, defaults, _, kwonly, kwdefaults = inspect.getfullargspec(obj)
- len_args = (len(args) if function else len(args) - 1) + len(kwonly or ()) + len(kwdefaults or ())
- len_defaults = (len(defaults) if defaults else 0) + len(kwdefaults or ())
- return len_args <= len_defaults
-
else:
SHORT_SEPARATORS = (b',', b':')
LONG_SEPARATORS = (b', ', b': ')
INDENT_SEPARATORS = (b',', b': ')
- def is_simple_callable(obj):
- """
- True if the object is a callable that takes no arguments.
- """
+
+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
+ if sys.version_info >= (3, 3):
+ signature = inspect.signature(obj)
+ defaults = [p for p in signature.parameters.values() if p.default is not inspect.Parameter.empty]
+ return len(signature.parameters) <= len(defaults)
+ else:
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
diff --git a/tests/compat/test_compat_py35.py b/tests/compat/compat_py35.py
similarity index 100%
rename from tests/compat/test_compat_py35.py
rename to tests/compat/compat_py35.py
diff --git a/tests/test_compat.py b/tests/test_compat.py
index b0c40ce34..a93ccd398 100644
--- a/tests/test_compat.py
+++ b/tests/test_compat.py
@@ -47,7 +47,7 @@ class TestFunctionSimplicityCheck:
if sys.version_info >= (3, 5):
- from tests.compat.test_compat_py35 import FunctionSimplicityCheckPy35Mixin
+ from tests.compat.compat_py35 import FunctionSimplicityCheckPy35Mixin
class TestFunctionSimplicityCheckPy35(FunctionSimplicityCheckPy35Mixin, TestFunctionSimplicityCheck):
pass
From 3cbb515787ff8d83996627b05d94be359707f7da Mon Sep 17 00:00:00 2001
From: "p.kamayev"
Date: Thu, 28 Apr 2016 11:20:34 +0300
Subject: [PATCH 4/4] linting
---
.isort.cfg | 2 +-
rest_framework/fields.py | 4 +++-
rest_framework/relations.py | 4 +---
tests/compat/compat_py35.py | 4 ++++
tests/test_compat.py | 1 -
5 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/.isort.cfg b/.isort.cfg
index 4d4a6a509..6a749cf25 100644
--- a/.isort.cfg
+++ b/.isort.cfg
@@ -1,5 +1,5 @@
[settings]
-skip=.tox
+skip=.tox,tests/compat/compat_py35.py
atomic=true
multi_line_output=5
known_standard_library=types
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 0e79cf918..e7535bfe3 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, is_simple_callable
+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
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
index 554a8c943..5d8a41ad8 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -15,9 +15,7 @@ from django.utils.six.moves.urllib import parse as urlparse
from django.utils.translation import ugettext_lazy as _
from rest_framework.compat import is_simple_callable
-from rest_framework.fields import (
- Field, empty, get_attribute, iter_options
-)
+from rest_framework.fields import Field, empty, get_attribute, iter_options
from rest_framework.reverse import reverse
from rest_framework.utils import html
diff --git a/tests/compat/compat_py35.py b/tests/compat/compat_py35.py
index 19b849b0c..8265f2658 100644
--- a/tests/compat/compat_py35.py
+++ b/tests/compat/compat_py35.py
@@ -1,3 +1,7 @@
+# for now, linting is done by python2.7, so for that file it should be disabled.
+# flake8: noqa
+
+
class FunctionSimplicityCheckPy35Mixin:
def get_good_cases(self):
def annotated_simple() -> int:
diff --git a/tests/test_compat.py b/tests/test_compat.py
index a93ccd398..974b9ba25 100644
--- a/tests/test_compat.py
+++ b/tests/test_compat.py
@@ -1,6 +1,5 @@
from __future__ import unicode_literals
-import pytest
import sys
from rest_framework.compat import is_simple_callable