Skip to content

Commit

Permalink
gh-108901: Deprecate inspect.getfullargspec and slate it for remova…
Browse files Browse the repository at this point in the history
…l in 3.15
  • Loading branch information
sobolevn committed Nov 22, 2023
1 parent fef6fb8 commit 41c4e36
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 13 deletions.
15 changes: 15 additions & 0 deletions Doc/library/inspect.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1046,6 +1046,21 @@ Classes and functions
order of keyword-only parameters as of version 3.7, although in practice
this order had always been preserved in Python 3.

.. deprecated-removed:: 3.13, 3.15
Now all :func:`getfullargspec` differences from :class:`Signature`
can be solved by passing ``follow_wrapped=False, skip_bound_arg=False``
arguments::

from inspect import signature

signature(your_obj, follow_wrapped=False, skip_bound_arg=False)

For Python versions older than 3.13 you can use ``inspect313`` PyPI package::

from inspect313 import signature

signature(your_obj, follow_wrapped=False, skip_bound_arg=False)


.. function:: getargvalues(frame)

Expand Down
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,11 @@ Pending Removal in Python 3.15
All arguments will be removed from :func:`threading.RLock` in Python 3.15.
(Contributed by Nikita Sobolev in :gh:`102029`.)

* :func:`inspect.getfullargspec` is deprecated and slated for removal in 3.15,
use :func:`inspect.signature` instead or ``inspect313`` PyPI package
for older versions.
(Contributed by Nikita Sobolev in :gh:`108901`.)

Pending Removal in Python 3.16
------------------------------

Expand Down
20 changes: 17 additions & 3 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -1403,6 +1403,13 @@ def getfullargspec(func):
- the "self" parameter is always reported, even for bound methods
- wrapper chains defined by __wrapped__ *not* unwrapped automatically
"""
import warnings
warnings._deprecated(
"getfullargspec",
'{name!r} is deprecated since 3.13 and slated for removal in Python {remove}, '
'use `inspect.Signature` or `inspect313` for older Python versions instead',
remove=(3, 15),
)
try:
# Re: `skip_bound_arg=False`
#
Expand Down Expand Up @@ -1579,7 +1586,10 @@ def getcallargs(func, /, *positional, **named):
A dict is returned, with keys the function argument names (including the
names of the * and ** arguments, if any), and values the respective bound
values from 'positional' and 'named'."""
spec = getfullargspec(func)
import warnings
with warnings.catch_warnings():
warnings.simplefilter('ignore', category=DeprecationWarning)
spec = getfullargspec(func)
args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = spec
f_name = func.__name__
arg2value = {}
Expand Down Expand Up @@ -3103,10 +3113,12 @@ def __init__(self, parameters=None, *, return_annotation=_empty,

@classmethod
def from_callable(cls, obj, *,
follow_wrapped=True, globals=None, locals=None, eval_str=False):
follow_wrapped=True, skip_bound_arg=True,
globals=None, locals=None, eval_str=False):
"""Constructs Signature for the given callable object."""
return _signature_from_callable(obj, sigcls=cls,
follow_wrapper_chains=follow_wrapped,
skip_bound_arg=skip_bound_arg,
globals=globals, locals=locals, eval_str=eval_str)

@property
Expand Down Expand Up @@ -3361,9 +3373,11 @@ def __str__(self):
return rendered


def signature(obj, *, follow_wrapped=True, globals=None, locals=None, eval_str=False):
def signature(obj, *, follow_wrapped=True, skip_bound_arg=True,
globals=None, locals=None, eval_str=False):
"""Get a signature object for the passed callable."""
return Signature.from_callable(obj, follow_wrapped=follow_wrapped,
skip_bound_arg=skip_bound_arg,
globals=globals, locals=locals, eval_str=eval_str)


Expand Down
82 changes: 72 additions & 10 deletions Lib/test/test_inspect/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -1078,6 +1078,16 @@ def attrs_wo_objs(cls):


class TestClassesAndFunctions(unittest.TestCase):
def assertDeprecated(self):
import re
return self.assertWarnsRegex(
DeprecationWarning,
re.escape(
"'getfullargspec' is deprecated since 3.13 "
"and slated for removal in Python 3.15"
),
)

def test_newstyle_mro(self):
# The same w/ new-class MRO.
class A(object): pass
Expand All @@ -1094,8 +1104,9 @@ def assertFullArgSpecEquals(self, routine, args_e, varargs_e=None,
posonlyargs_e=[], kwonlyargs_e=[],
kwonlydefaults_e=None,
ann_e={}):
args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = \
inspect.getfullargspec(routine)
with self.assertDeprecated():
args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = \
inspect.getfullargspec(routine)
self.assertEqual(args, args_e)
self.assertEqual(varargs, varargs_e)
self.assertEqual(varkw, varkw_e)
Expand Down Expand Up @@ -1148,11 +1159,13 @@ def test():

def test_getfullargspec_signature_annos(self):
def test(a:'spam') -> 'ham': pass
spec = inspect.getfullargspec(test)
with self.assertDeprecated():
spec = inspect.getfullargspec(test)
self.assertEqual(test.__annotations__, spec.annotations)

def test(): pass
spec = inspect.getfullargspec(test)
with self.assertDeprecated():
spec = inspect.getfullargspec(test)
self.assertEqual(test.__annotations__, spec.annotations)

@unittest.skipIf(MISSING_C_DOCSTRINGS,
Expand All @@ -1174,7 +1187,8 @@ def test_getfullargspec_builtin_methods(self):
def test_getfullargspec_builtin_func(self):
import _testcapi
builtin = _testcapi.docstring_with_signature_with_defaults
spec = inspect.getfullargspec(builtin)
with self.assertDeprecated():
spec = inspect.getfullargspec(builtin)
self.assertEqual(spec.defaults[0], 'avocado')

@cpython_only
Expand All @@ -1183,7 +1197,7 @@ def test_getfullargspec_builtin_func(self):
def test_getfullargspec_builtin_func_no_signature(self):
import _testcapi
builtin = _testcapi.docstring_no_signature
with self.assertRaises(TypeError):
with self.assertRaises(TypeError), self.assertDeprecated():
inspect.getfullargspec(builtin)

cls = _testcapi.DocStringNoSignatureTest
Expand Down Expand Up @@ -1224,17 +1238,22 @@ def test_getfullargspec_builtin_func_no_signature(self):
tests.append((stat.S_IMODE, meth_o))
for builtin, template in tests:
with self.subTest(builtin):
self.assertEqual(inspect.getfullargspec(builtin),
inspect.getfullargspec(template))
with self.assertDeprecated():
builtin_args = inspect.getfullargspec(builtin)
with self.assertDeprecated():
template_args = inspect.getfullargspec(template)
self.assertEqual(builtin_args, template_args)

def test_getfullargspec_definition_order_preserved_on_kwonly(self):
for fn in signatures_with_lexicographic_keyword_only_parameters():
signature = inspect.getfullargspec(fn)
with self.assertDeprecated():
signature = inspect.getfullargspec(fn)
l = list(signature.kwonlyargs)
sorted_l = sorted(l)
self.assertTrue(l)
self.assertEqual(l, sorted_l)
signature = inspect.getfullargspec(unsorted_keyword_only_parameters_fn)
with self.assertDeprecated():
signature = inspect.getfullargspec(unsorted_keyword_only_parameters_fn)
l = list(signature.kwonlyargs)
self.assertEqual(l, unsorted_keyword_only_parameters)

Expand Down Expand Up @@ -4136,6 +4155,49 @@ class D2(D1):

self.assertEqual(inspect.signature(D2), inspect.signature(D1))

def test_signature_as_getfullargspec_replacement(self):
def decorator(func):
@functools.wraps(func) # set `__wrapper__` attribute
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper

@decorator
def func(a: int, /, b: str = '', *, c : bool = True) -> int: ...

sig = inspect.signature(func, follow_wrapped=False, skip_bound_arg=False)
self.assertEqual(str(sig), '(*args, **kwargs) -> int')
self.assertEqual(
str(sig),
str(inspect.Signature.from_callable(func,
follow_wrapped=False,
skip_bound_arg=False)),
)

class My:
def method(self, arg: int) -> None: ...
@classmethod
def cl(cls, arg2: str) -> None: ...

sigs = {
My.method: '(self, arg: int) -> None',
My().method: '(self, arg: int) -> None',
My.cl: '(cls, arg2: str) -> None',
My().cl: '(cls, arg2: str) -> None',
}
for f, text_sig in sigs.items():
with self.subTest(f=f):
sig = inspect.signature(f,
follow_wrapped=False,
skip_bound_arg=False)
self.assertEqual(str(sig), text_sig)
self.assertEqual(
str(sig),
str(inspect.Signature.from_callable(f,
follow_wrapped=False,
skip_bound_arg=False)),
)


class TestParameterObject(unittest.TestCase):
def test_signature_parameter_kinds(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Deprecate :func:`inspect.getfullargspec` and slate it for removal in 3.15.
Instead use :func:`inspect.signature` with ``follow_wrapped=False,
skip_bound_arg=False`` arguments or ``inspect313`` PyPI package for Python
versions older than 3.13

0 comments on commit 41c4e36

Please sign in to comment.