diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0af078d9..26669bed 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,7 +19,7 @@ jobs: - 3.8 - 3.9 - "3.10" - #- 3.11-dev + - 3.11-dev - pypy-2.7 - pypy-3.6 - pypy-3.7 @@ -57,7 +57,7 @@ jobs: - 3.8 - 3.9 - "3.10" - #- 3.11-dev + - 3.11-dev - pypy-2.7 #- pypy-3.6 - pypy-3.7 @@ -117,7 +117,7 @@ jobs: - 3.8 - 3.9 - "3.10" - #- 3.11-dev + - 3.11-dev - pypy-2.7 - pypy-3.6 - pypy-3.7 diff --git a/setup.cfg b/setup.cfg index 40915a01..eded06fb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -93,6 +93,8 @@ python = deps = coverage pytest +install_command = + py311,py311-{without,install,disable}-extensions: python -m pip install --no-binary coverage {opts} {packages} commands = python -m coverage run --rcfile {toxinidir}/setup.cfg -m pytest -v {posargs} {toxinidir}/tests setenv = diff --git a/src/wrapt/decorators.py b/src/wrapt/decorators.py index 389a764b..a92e7e6f 100644 --- a/src/wrapt/decorators.py +++ b/src/wrapt/decorators.py @@ -31,10 +31,41 @@ def exec_(_code_, _globs_=None, _locs_=None): del builtins from functools import partial -from inspect import ismethod, isclass, formatargspec -from collections import namedtuple +from inspect import isclass from threading import Lock, RLock +try: + from inspect import Parameter, Signature +except ImportError: # < py3.5 + from inspect import formatargspec +else: + def formatargspec(args, varargs=None, varkw=None, defaults=None, + kwonlyargs=(), kwonlydefaults={}, annotations={}): + if kwonlydefaults is None: + kwonlydefaults = {} + ndefaults = len(defaults) if defaults else 0 + parameters = [ + Parameter( + arg, + Parameter.POSITIONAL_OR_KEYWORD, + default=defaults[i] if i >= 0 else Parameter.empty, + annotation=annotations.get(arg, Parameter.empty), + ) for i, arg in enumerate(args, ndefaults - len(args)) + ] + if varargs: + parameters.append(Parameter(varargs, Parameter.VAR_POSITIONAL)) + parameters.extend( + Parameter( + kwonlyarg, + Parameter.KEYWORD_ONLY, + default=kwonlydefaults.get(kwonlyarg, Parameter.empty), + annotation=annotations.get(kwonlyarg, Parameter.empty), + ) for kwonlyarg in kwonlyargs + ) + if varkw: + parameters.append(Parameter(varkw, Parameter.VAR_KEYWORD)) + return_annotation = annotations.get('return', Signature.empty) + return str(Signature(parameters, return_annotation=return_annotation)) try: from inspect import signature except ImportError: diff --git a/tests/compat.py b/tests/compat.py index 0eed716e..83b61f94 100644 --- a/tests/compat.py +++ b/tests/compat.py @@ -26,3 +26,8 @@ def exec_(_code_, _globs_=None, _locs_=None): exec_("""def reraise(tp, value, tb=None): raise tp, value, tb """) + +try: + from inspect import getfullargspec +except ImportError: + from inspect import getargspec as getfullargspec diff --git a/tests/test_adapter.py b/tests/test_adapter.py index 14ba6604..906210a8 100644 --- a/tests/test_adapter.py +++ b/tests/test_adapter.py @@ -6,7 +6,7 @@ import wrapt -from compat import PY2, PY3, exec_ +from compat import PY2, exec_, getfullargspec DECORATORS_CODE = """ import wrapt @@ -72,15 +72,15 @@ def test_argspec(self): def _adapter(arg1, arg2, arg3=None, *args, **kwargs): pass - function1a_argspec = inspect.getargspec(_adapter) - function1d_argspec = inspect.getargspec(function1d) + function1a_argspec = getfullargspec(_adapter) + function1d_argspec = getfullargspec(function1d) self.assertEqual(function1a_argspec, function1d_argspec) # Now bind the function to an instance. The argspec should # still match. bound_function1d = function1d.__get__(object(), object) - bound_function1d_argspec = inspect.getargspec(bound_function1d) + bound_function1d_argspec = getfullargspec(bound_function1d) self.assertEqual(function1a_argspec, bound_function1d_argspec) def test_signature(self): @@ -107,7 +107,7 @@ class TestDynamicAdapter(unittest.TestCase): def test_dynamic_adapter_function(self): def _adapter(arg1, arg2, arg3=None, *args, **kwargs): pass - argspec = inspect.getargspec(_adapter) + argspec = getfullargspec(_adapter) @wrapt.decorator(adapter=argspec) def _wrapper_1(wrapped, instance, args, kwargs): @@ -117,9 +117,9 @@ def _wrapper_1(wrapped, instance, args, kwargs): def _function_1(): pass - self.assertEqual(inspect.getargspec(_function_1), argspec) + self.assertEqual(getfullargspec(_function_1), argspec) - args = inspect.formatargspec(*argspec) + args = '(arg1, arg2, arg3=None, *args, **kwargs)' @wrapt.decorator(adapter=args) def _wrapper_2(wrapped, instance, args, kwargs): @@ -129,12 +129,12 @@ def _wrapper_2(wrapped, instance, args, kwargs): def _function_2(): pass - self.assertEqual(inspect.getargspec(_function_2), argspec) + self.assertEqual(getfullargspec(_function_2), argspec) def test_dynamic_adapter_instancemethod(self): def _adapter(self, arg1, arg2, arg3=None, *args, **kwargs): pass - argspec = inspect.getargspec(_adapter) + argspec = getfullargspec(_adapter) @wrapt.decorator(adapter=argspec) def _wrapper_1(wrapped, instance, args, kwargs): @@ -147,10 +147,10 @@ def function(self): instance1 = Class1() - self.assertEqual(inspect.getargspec(Class1.function), argspec) - self.assertEqual(inspect.getargspec(instance1.function), argspec) + self.assertEqual(getfullargspec(Class1.function), argspec) + self.assertEqual(getfullargspec(instance1.function), argspec) - args = inspect.formatargspec(*argspec) + args = '(self, arg1, arg2, arg3=None, *args, **kwargs)' @wrapt.decorator(adapter=args) def _wrapper_2(wrapped, instance, args, kwargs): @@ -163,13 +163,13 @@ def function(self): instance2 = Class2() - self.assertEqual(inspect.getargspec(Class2.function), argspec) - self.assertEqual(inspect.getargspec(instance2.function), argspec) + self.assertEqual(getfullargspec(Class2.function), argspec) + self.assertEqual(getfullargspec(instance2.function), argspec) def test_dynamic_adapter_classmethod(self): def _adapter(cls, arg1, arg2, arg3=None, *args, **kwargs): pass - argspec = inspect.getargspec(_adapter) + argspec = getfullargspec(_adapter) @wrapt.decorator(adapter=argspec) def _wrapper_1(wrapped, instance, args, kwargs): @@ -183,10 +183,10 @@ def function(cls): instance1 = Class1() - self.assertEqual(inspect.getargspec(Class1.function), argspec) - self.assertEqual(inspect.getargspec(instance1.function), argspec) + self.assertEqual(getfullargspec(Class1.function), argspec) + self.assertEqual(getfullargspec(instance1.function), argspec) - args = inspect.formatargspec(*argspec) + args = '(cls, arg1, arg2, arg3=None, *args, **kwargs)' @wrapt.decorator(adapter=args) def _wrapper_2(wrapped, instance, args, kwargs): @@ -200,12 +200,12 @@ def function(self): instance2 = Class2() - self.assertEqual(inspect.getargspec(Class2.function), argspec) - self.assertEqual(inspect.getargspec(instance2.function), argspec) + self.assertEqual(getfullargspec(Class2.function), argspec) + self.assertEqual(getfullargspec(instance2.function), argspec) def test_adapter_factory(self): def factory(wrapped): - argspec = inspect.getargspec(wrapped) + argspec = getfullargspec(wrapped) argspec.args.insert(0, 'arg0') return argspec @@ -217,7 +217,7 @@ def _wrapper_1(wrapped, instance, args, kwargs): def _function_1(arg1, arg2): pass - argspec = inspect.getargspec(_function_1) + argspec = getfullargspec(_function_1) self.assertEqual(argspec.args, ['arg0', 'arg1', 'arg2']) diff --git a/tests/test_adapter_py3.py b/tests/test_adapter_py3.py index 02c360f7..9291bd82 100644 --- a/tests/test_adapter_py3.py +++ b/tests/test_adapter_py3.py @@ -3,13 +3,12 @@ import inspect import unittest import imp -import collections from typing import Iterable import wrapt -from compat import PY2, PY3, exec_ +from compat import PY2, exec_ DECORATORS_CODE = """ import wrapt @@ -128,7 +127,7 @@ def _adapter2(arg1, arg2, arg3=None, *args, **kwargs) -> int: pass argspec2 = inspect.getfullargspec(_adapter2) - args = inspect.formatargspec(*argspec2) + args = '(arg1, arg2, arg3=None, *args, **kwargs) -> int' @wrapt.decorator(adapter=args) def _wrapper_2(wrapped, instance, args, kwargs): @@ -166,7 +165,7 @@ def _adapter2(self, arg1, arg2, arg3=None, *args, **kwargs) -> int: pass argspec2 = inspect.getfullargspec(_adapter2) - args = inspect.formatargspec(*argspec2) + args = '(self, arg1, arg2, arg3=None, *args, **kwargs) -> int' @wrapt.decorator(adapter=args) def _wrapper_2(wrapped, instance, args, kwargs): @@ -209,7 +208,7 @@ def _adapter2(cls, arg1, arg2, arg3=None, *args, **kwargs) -> int: pass argspec2 = inspect.getfullargspec(_adapter2) - args = inspect.formatargspec(*argspec2) + args = '(cls, arg1, arg2, arg3=None, *args, **kwargs) -> int' @wrapt.decorator(adapter=args) def _wrapper_2(wrapped, instance, args, kwargs): diff --git a/tests/test_formatargspec_py35.py b/tests/test_formatargspec_py35.py new file mode 100644 index 00000000..df607ba1 --- /dev/null +++ b/tests/test_formatargspec_py35.py @@ -0,0 +1,35 @@ +import unittest +import sys +from inspect import getfullargspec + +from wrapt.decorators import formatargspec + +class TestFormatargspec35(unittest.TestCase): + + def assertFormatEqual(self, func, ref): + formatted = formatargspec(*getfullargspec(func)) + self.assertEqual(formatted, ref) + + def test_formatargspec(self): + def foo1(): pass + self.assertFormatEqual(foo1, '()') + + def foo2(a, b='c'): pass + self.assertFormatEqual(foo2, ("(a, b='c')")) + + def foo3(a, b, *args, **kwargs): pass + self.assertFormatEqual(foo3, '(a, b, *args, **kwargs)') + + def foo4(a: int, b) -> list: pass + if sys.version_info[:2] < (3, 7): + formatted4 = '(a:int, b) -> list' + else: + formatted4 = '(a: int, b) -> list' + self.assertFormatEqual(foo4, formatted4) + + # examples from https://www.python.org/dev/peps/pep-3102/ + def sortwords(*wordlist, case_sensitive=False): pass + self.assertFormatEqual(sortwords, '(*wordlist, case_sensitive=False)') + + def compare(a, b, *, key=None): pass + self.assertFormatEqual(compare, '(a, b, *, key=None)') diff --git a/tests/test_formatargspec_py38.py b/tests/test_formatargspec_py38.py new file mode 100644 index 00000000..5a6a93e6 --- /dev/null +++ b/tests/test_formatargspec_py38.py @@ -0,0 +1,37 @@ +import unittest +import sys +from inspect import getfullargspec + +from wrapt.decorators import formatargspec + +class TestFormatargspec38(unittest.TestCase): + + def assertFormatEqual(self, func, ref): + formatted = formatargspec(*getfullargspec(func)) + self.assertEqual(formatted, ref) + + def test_formatargspec(self): + # exemples from https://www.python.org/dev/peps/pep-0570/ + def name1(p1, p2, /, p_or_kw, *, kw): pass + self.assertFormatEqual(name1, '(p1, p2, p_or_kw, *, kw)') + + def name2(p1, p2=None, /, p_or_kw=None, *, kw): pass + self.assertFormatEqual(name2, '(p1, p2=None, p_or_kw=None, *, kw)') + + def name3(p1, p2=None, /, *, kw): pass + self.assertFormatEqual(name3, '(p1, p2=None, *, kw)') + + def name4(p1, p2=None, /): pass + self.assertFormatEqual(name4, '(p1, p2=None)') + + def name5(p1, p2, /, p_or_kw): pass + self.assertFormatEqual(name5, '(p1, p2, p_or_kw)') + + def name6(p1, p2, /): pass + self.assertFormatEqual(name6, '(p1, p2)') + + def name7(p_or_kw, *, kw): pass + self.assertFormatEqual(name7, '(p_or_kw, *, kw)') + + def name8(*, kw): pass + self.assertFormatEqual(name8, '(*, kw)') diff --git a/tests/test_function.py b/tests/test_function.py index 64a71083..73a3b432 100644 --- a/tests/test_function.py +++ b/tests/test_function.py @@ -6,7 +6,7 @@ import wrapt -from compat import PY2, PY3, exec_ +from compat import exec_, getfullargspec DECORATORS_CODE = """ import wrapt @@ -57,8 +57,8 @@ def test_doc_string(self): def test_argspec(self): # Test preservation of function argument specification. - function1o_argspec = inspect.getargspec(function1o) - function1d_argspec = inspect.getargspec(function1d) + function1o_argspec = getfullargspec(function1o) + function1d_argspec = getfullargspec(function1d) self.assertEqual(function1o_argspec, function1d_argspec) def test_getmembers(self): diff --git a/tests/test_inner_classmethod.py b/tests/test_inner_classmethod.py index abf4ca16..d442d4f0 100644 --- a/tests/test_inner_classmethod.py +++ b/tests/test_inner_classmethod.py @@ -1,12 +1,11 @@ from __future__ import print_function import unittest -import inspect import imp import wrapt -from compat import PY2, PY3, exec_ +from compat import exec_, getfullargspec DECORATORS_CODE = """ import wrapt @@ -95,15 +94,15 @@ def test_instance_doc_string(self): def test_class_argspec(self): # Test preservation of instance method argument specification. - original_argspec = inspect.getargspec(Original.function) - function_argspec = inspect.getargspec(Class.function) + original_argspec = getfullargspec(Original.function) + function_argspec = getfullargspec(Class.function) self.assertEqual(original_argspec, function_argspec) def test_instance_argspec(self): # Test preservation of instance method argument specification. - original_argspec = inspect.getargspec(Original().function) - function_argspec = inspect.getargspec(Class().function) + original_argspec = getfullargspec(Original().function) + function_argspec = getfullargspec(Class().function) self.assertEqual(original_argspec, function_argspec) def test_class_isinstance(self): diff --git a/tests/test_inner_staticmethod.py b/tests/test_inner_staticmethod.py index 01a54bae..31948653 100644 --- a/tests/test_inner_staticmethod.py +++ b/tests/test_inner_staticmethod.py @@ -1,12 +1,11 @@ from __future__ import print_function import unittest -import inspect import imp import wrapt -from compat import PY2, PY3, exec_ +from compat import exec_, getfullargspec DECORATORS_CODE = """ import wrapt @@ -95,15 +94,15 @@ def test_instance_doc_string(self): def test_class_argspec(self): # Test preservation of instance method argument specification. - original_argspec = inspect.getargspec(Original.function) - function_argspec = inspect.getargspec(Class.function) + original_argspec = getfullargspec(Original.function) + function_argspec = getfullargspec(Class.function) self.assertEqual(original_argspec, function_argspec) def test_instance_argspec(self): # Test preservation of instance method argument specification. - original_argspec = inspect.getargspec(Original().function) - function_argspec = inspect.getargspec(Class().function) + original_argspec = getfullargspec(Original().function) + function_argspec = getfullargspec(Class().function) self.assertEqual(original_argspec, function_argspec) def test_class_isinstance(self): diff --git a/tests/test_instancemethod.py b/tests/test_instancemethod.py index b6356bd0..9ad6bb83 100644 --- a/tests/test_instancemethod.py +++ b/tests/test_instancemethod.py @@ -6,7 +6,7 @@ import wrapt -from compat import PY2, PY3, exec_ +from compat import exec_, getfullargspec DECORATORS_CODE = """ import wrapt @@ -95,15 +95,15 @@ def test_instance_doc_string(self): def test_class_argspec(self): # Test preservation of instance method argument specification. - original_argspec = inspect.getargspec(OldClass1o.function) - function_argspec = inspect.getargspec(OldClass1d.function) + original_argspec = getfullargspec(OldClass1o.function) + function_argspec = getfullargspec(OldClass1d.function) self.assertEqual(original_argspec, function_argspec) def test_instance_argspec(self): # Test preservation of instance method argument specification. - original_argspec = inspect.getargspec(OldClass1o().function) - function_argspec = inspect.getargspec(OldClass1d().function) + original_argspec = getfullargspec(OldClass1o().function) + function_argspec = getfullargspec(OldClass1d().function) self.assertEqual(original_argspec, function_argspec) def test_getmembers(self): @@ -198,15 +198,15 @@ def test_instance_doc_string(self): def test_class_argspec(self): # Test preservation of instance method argument specification. - original_argspec = inspect.getargspec(NewClass1o.function) - function_argspec = inspect.getargspec(NewClass1d.function) + original_argspec = getfullargspec(NewClass1o.function) + function_argspec = getfullargspec(NewClass1d.function) self.assertEqual(original_argspec, function_argspec) def test_instance_argspec(self): # Test preservation of instance method argument specification. - original_argspec = inspect.getargspec(NewClass1o().function) - function_argspec = inspect.getargspec(NewClass1d().function) + original_argspec = getfullargspec(NewClass1o().function) + function_argspec = getfullargspec(NewClass1d().function) self.assertEqual(original_argspec, function_argspec) def test_class_isinstance(self): diff --git a/tests/test_nested_function.py b/tests/test_nested_function.py index 4f55a36b..a6c3bcd8 100644 --- a/tests/test_nested_function.py +++ b/tests/test_nested_function.py @@ -1,12 +1,11 @@ from __future__ import print_function import unittest -import inspect import imp import wrapt -from compat import PY2, PY3, exec_ +from compat import exec_, getfullargspec DECORATORS_CODE = """ import wrapt @@ -66,8 +65,8 @@ def test_doc_string(self): def test_argspec(self): # Test preservation of function argument specification. - function1o_argspec = inspect.getargspec(function1o()) - function1d_argspec = inspect.getargspec(function1d()) + function1o_argspec = getfullargspec(function1o()) + function1d_argspec = getfullargspec(function1d()) self.assertEqual(function1o_argspec, function1d_argspec) def test_isinstance(self): diff --git a/tests/test_outer_classmethod.py b/tests/test_outer_classmethod.py index fbf37f7a..c4c2d345 100644 --- a/tests/test_outer_classmethod.py +++ b/tests/test_outer_classmethod.py @@ -1,13 +1,11 @@ from __future__ import print_function -import sys import unittest -import inspect import imp import wrapt -from compat import PY2, PY3, PYXY, exec_ +from compat import PYXY, exec_, getfullargspec DECORATORS_CODE = """ import wrapt @@ -96,15 +94,15 @@ def test_instance_doc_string(self): def test_class_argspec(self): # Test preservation of instance method argument specification. - original_argspec = inspect.getargspec(Original.function) - function_argspec = inspect.getargspec(Class.function) + original_argspec = getfullargspec(Original.function) + function_argspec = getfullargspec(Class.function) self.assertEqual(original_argspec, function_argspec) def test_instance_argspec(self): # Test preservation of instance method argument specification. - original_argspec = inspect.getargspec(Original().function) - function_argspec = inspect.getargspec(Class().function) + original_argspec = getfullargspec(Original().function) + function_argspec = getfullargspec(Class().function) self.assertEqual(original_argspec, function_argspec) def test_class_isinstance(self): diff --git a/tests/test_outer_staticmethod.py b/tests/test_outer_staticmethod.py index 0a0d0f77..2a8c1938 100644 --- a/tests/test_outer_staticmethod.py +++ b/tests/test_outer_staticmethod.py @@ -1,12 +1,11 @@ from __future__ import print_function import unittest -import inspect import imp import wrapt -from compat import PY2, PY3, exec_ +from compat import exec_, getfullargspec DECORATORS_CODE = """ import wrapt @@ -95,15 +94,15 @@ def test_instance_doc_string(self): def test_class_argspec(self): # Test preservation of instance method argument specification. - original_argspec = inspect.getargspec(Original.function) - function_argspec = inspect.getargspec(Class.function) + original_argspec = getfullargspec(Original.function) + function_argspec = getfullargspec(Class.function) self.assertEqual(original_argspec, function_argspec) def test_instance_argspec(self): # Test preservation of instance method argument specification. - original_argspec = inspect.getargspec(Original().function) - function_argspec = inspect.getargspec(Class().function) + original_argspec = getfullargspec(Original().function) + function_argspec = getfullargspec(Class().function) self.assertEqual(original_argspec, function_argspec) def test_class_isinstance(self):