]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-136914: Fix support of cached functions and properties in DocTest's lineno computa...
authorDenis Laxalde <denis@laxalde.org>
Fri, 25 Jul 2025 09:46:12 +0000 (11:46 +0200)
committerGitHub <noreply@github.com>
Fri, 25 Jul 2025 09:46:12 +0000 (12:46 +0300)
Previously, DocTest's lineno of functions and methods decorated with
functools.cache(), functools.lru_cache() and functools.cached_property()
was not properly returned (None was returned) because the
computation relied on inspect.isfunction() which does not consider the
decorated result as a function.

We now use the more generic inspect.isroutine(), as elsewhere
in doctest's logic.

Also, added a special case for functools.cached_property().

Lib/doctest.py
Lib/test/test_doctest/doctest_lineno.py
Lib/test/test_doctest/test_doctest.py
Misc/NEWS.d/next/Library/2025-07-21-15-40-00.gh-issue-136914.-GNG-d.rst [new file with mode: 0644]

index e77823f64b67e4df7beef290db04ae89e885bac3..92a2ab4f7e66f8b959bd137b75a988cd55465687 100644 (file)
@@ -94,6 +94,7 @@ __all__ = [
 
 import __future__
 import difflib
+import functools
 import inspect
 import linecache
 import os
@@ -1141,7 +1142,9 @@ class DocTestFinder:
         if inspect.ismethod(obj): obj = obj.__func__
         if isinstance(obj, property):
             obj = obj.fget
-        if inspect.isfunction(obj) and getattr(obj, '__doc__', None):
+        if isinstance(obj, functools.cached_property):
+            obj = obj.func
+        if inspect.isroutine(obj) and getattr(obj, '__doc__', None):
             # We don't use `docstring` var here, because `obj` can be changed.
             obj = inspect.unwrap(obj)
             try:
index 0dbcd9a11eaba2fa1d5306b06b297e56d79bacd8..0bd402e98288d01cd93a01c07d308a3c8580d0d7 100644 (file)
@@ -76,3 +76,32 @@ from test.test_doctest.decorator_mod import decorator
 @decorator
 def func_with_docstring_wrapped():
     """Some unrelated info."""
+
+
+# https://github.com/python/cpython/issues/136914
+import functools
+
+
+@functools.cache
+def cached_func_with_doctest(value):
+    """
+    >>> cached_func_with_doctest(1)
+    -1
+    """
+    return -value
+
+
+@functools.cache
+def cached_func_without_docstring(value):
+    return value + 1
+
+
+class ClassWithACachedProperty:
+
+    @functools.cached_property
+    def cached(self):
+        """
+        >>> X().cached
+        -1
+        """
+        return 0
index 72763d4a0132d0058480444299fef2730604a6fd..0fa74407e3c43651f8bfbcb4a5f9cd9d2e9fb351 100644 (file)
@@ -678,6 +678,8 @@ It used to be broken for quite some time until `bpo-28249`.
     >>> for t in tests:
     ...     print('%5s  %s' % (t.lineno, t.name))
      None  test.test_doctest.doctest_lineno
+     None  test.test_doctest.doctest_lineno.ClassWithACachedProperty
+      102  test.test_doctest.doctest_lineno.ClassWithACachedProperty.cached
        22  test.test_doctest.doctest_lineno.ClassWithDocstring
        30  test.test_doctest.doctest_lineno.ClassWithDoctest
      None  test.test_doctest.doctest_lineno.ClassWithoutDocstring
@@ -687,6 +689,8 @@ It used to be broken for quite some time until `bpo-28249`.
        45  test.test_doctest.doctest_lineno.MethodWrapper.method_with_doctest
      None  test.test_doctest.doctest_lineno.MethodWrapper.method_without_docstring
        61  test.test_doctest.doctest_lineno.MethodWrapper.property_with_doctest
+       86  test.test_doctest.doctest_lineno.cached_func_with_doctest
+     None  test.test_doctest.doctest_lineno.cached_func_without_docstring
         4  test.test_doctest.doctest_lineno.func_with_docstring
        77  test.test_doctest.doctest_lineno.func_with_docstring_wrapped
        12  test.test_doctest.doctest_lineno.func_with_doctest
diff --git a/Misc/NEWS.d/next/Library/2025-07-21-15-40-00.gh-issue-136914.-GNG-d.rst b/Misc/NEWS.d/next/Library/2025-07-21-15-40-00.gh-issue-136914.-GNG-d.rst
new file mode 100644 (file)
index 0000000..78ec802
--- /dev/null
@@ -0,0 +1,2 @@
+Fix retrieval of :attr:`doctest.DocTest.lineno` for objects decorated with
+:func:`functools.cache` or :class:`functools.cached_property`.