From 15a85183880ebebe1b4a1e16e9b2202897d27d8e Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Fri, 19 Mar 2021 11:23:28 +0000 Subject: [PATCH] [3.2.x] Refs #31732 -- Fixed django.utils.inspect caching for bound methods. Thanks Alexandr Artemyev for the report, and Simon Charette for the original patch. Backport of 562898034f65e17bcdd2d951ac5236a1ec8ea690 from main --- django/utils/inspect.py | 33 ++++++++++++++++++++----------- tests/utils_tests/test_inspect.py | 10 ++++++++++ 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/django/utils/inspect.py b/django/utils/inspect.py index 567efcbc17..7e062244e5 100644 --- a/django/utils/inspect.py +++ b/django/utils/inspect.py @@ -3,14 +3,23 @@ import inspect @functools.lru_cache(maxsize=512) -def _get_signature(func): - return inspect.signature(func) +def _get_func_parameters(func, remove_first): + parameters = tuple(inspect.signature(func).parameters.values()) + if remove_first: + parameters = parameters[1:] + return parameters + + +def _get_callable_parameters(meth_or_func): + is_method = inspect.ismethod(meth_or_func) + func = meth_or_func.__func__ if is_method else meth_or_func + return _get_func_parameters(func, remove_first=is_method) def get_func_args(func): - sig = _get_signature(func) + params = _get_callable_parameters(func) return [ - arg_name for arg_name, param in sig.parameters.items() + param.name for param in params if param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD ] @@ -21,10 +30,10 @@ def get_func_full_args(func): does not have a default value, omit it in the tuple. Arguments such as *args and **kwargs are also included. """ - sig = _get_signature(func) + params = _get_callable_parameters(func) args = [] - for arg_name, param in sig.parameters.items(): - name = arg_name + for param in params: + name = param.name # Ignore 'self' if name == 'self': continue @@ -42,7 +51,7 @@ def get_func_full_args(func): def func_accepts_kwargs(func): """Return True if function 'func' accepts keyword arguments **kwargs.""" return any( - p for p in _get_signature(func).parameters.values() + p for p in _get_callable_parameters(func) if p.kind == p.VAR_KEYWORD ) @@ -52,7 +61,7 @@ def func_accepts_var_args(func): Return True if function 'func' accepts positional arguments *args. """ return any( - p for p in _get_signature(func).parameters.values() + p for p in _get_callable_parameters(func) if p.kind == p.VAR_POSITIONAL ) @@ -60,11 +69,11 @@ def func_accepts_var_args(func): def method_has_no_args(meth): """Return True if a method only accepts 'self'.""" count = len([ - p for p in _get_signature(meth).parameters.values() + p for p in _get_callable_parameters(meth) if p.kind == p.POSITIONAL_OR_KEYWORD ]) return count == 0 if inspect.ismethod(meth) else count == 1 -def func_supports_parameter(func, parameter): - return parameter in _get_signature(func).parameters +def func_supports_parameter(func, name): + return any(param.name == name for param in _get_callable_parameters(func)) diff --git a/tests/utils_tests/test_inspect.py b/tests/utils_tests/test_inspect.py index 9ea8ae84a4..ae83d675fe 100644 --- a/tests/utils_tests/test_inspect.py +++ b/tests/utils_tests/test_inspect.py @@ -22,6 +22,16 @@ class Person: class TestInspectMethods(unittest.TestCase): + def test_get_callable_parameters(self): + self.assertIs( + inspect._get_callable_parameters(Person.no_arguments), + inspect._get_callable_parameters(Person.no_arguments), + ) + self.assertIs( + inspect._get_callable_parameters(Person().no_arguments), + inspect._get_callable_parameters(Person().no_arguments), + ) + def test_get_func_full_args_no_arguments(self): self.assertEqual(inspect.get_func_full_args(Person.no_arguments), []) self.assertEqual(inspect.get_func_full_args(Person().no_arguments), [])