-
-
Notifications
You must be signed in to change notification settings - Fork 31.7k
gh-131116: Fix inspect._finddoc to work with cached_property objects. #131165
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
In Lib/test/test_inspect/inspect_fodder.py, the last class has a bunch of comments where the comment includes the specific line number, i.e. I'm having trouble getting the test suite working locally to get a sense of if that matters at all, but I'm guessing that some |
I believe I fully reverted that in my later commits. There should no longer be any changes to inspect_fodder.py. Which commit have you pulled? Those comments are there to test the comment extractor part of inspect.py. After a bit of testing I felt adding new stuff to the fodder file required changing more hard coded numbers than I thought was worthwhile, so I made a new fodder file. I'm also having trouble getting the test suite working after my last pull from main. Are you getting the same "cannot import name 'Union' from '_typing' (unknown location)" error? |
ope, my bad, was looking at a commit and spent so long trying to get the devcontainer to work you had made new updates since then that I didn't look at. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you please check the failing tests here please, Tests / Hypothesis tests on Ubuntu (pull_request) ? if they are relevant failures?
Syncing with main this afternoon seems to have fixed it. I'm not sure why it was failing in the first place, since it seems to test code that I didn't modify at all. The tests ran fine on my Ubuntu terminal before and after I submitted so that was a bit baffling to me. |
The test is flaky I think (we've seen similar failures elsewhere) |
@picnixz Do you have any thoughts on this pr? It's been a couple weeks and I'm hoping for core dev to look at it. |
I don't have time until the end of the week so I'm requesting myself a review |
Misc/NEWS.d/next/Library/2025-03-12-18-57-10.gh-issue-131116.uTpwXZ.rst
Outdated
Show resolved
Hide resolved
Co-authored-by: Bénédikt Tran <[email protected]>
Co-authored-by: Bénédikt Tran <[email protected]>
Co-authored-by: Bénédikt Tran <[email protected]>
Co-authored-by: Bénédikt Tran <[email protected]>
Co-authored-by: Bénédikt Tran <[email protected]>
def foo(self): | ||
pass | ||
|
||
# Redine foo as something other than cached_property |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# Redine foo as something other than cached_property | |
# redefine foo as something other than cached_property |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just some cosmetic nits.
@serhiy-storchaka is there something we need to be careful about here?
def foo(self): | ||
"""docstring for foo defined in parent""" | ||
|
||
class ChildInheritDoc(ParentInheritDoc): pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
class ChildInheritDoc(ParentInheritDoc): pass | |
class ChildInheritDoc(ParentInheritDoc): | |
pass |
def foo(self): | ||
pass | ||
|
||
class ChildNoDoc(ParentNoDoc): pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
class ChildNoDoc(ParentNoDoc): pass | |
class ChildNoDoc(ParentNoDoc): | |
pass |
self.assertEqual(inspect.getdoc(mod3.ChildNoDoc.foo), | ||
None) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self.assertEqual(inspect.getdoc(mod3.ChildNoDoc.foo), | |
None) | |
self.assertIsNone(inspect.getdoc(mod3.ChildNoDoc.foo)) |
self.assertEqual(inspect.getdoc(mod3.ChildInheritDoc.foo), | ||
'docstring for foo defined in parent') | ||
self.assertEqual(inspect.getdoc(mod3.ChildInheritDefineDoc.foo), | ||
'docstring for foo defined in parent') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self.assertEqual(inspect.getdoc(mod3.ChildInheritDoc.foo), | |
'docstring for foo defined in parent') | |
self.assertEqual(inspect.getdoc(mod3.ChildInheritDefineDoc.foo), | |
'docstring for foo defined in parent') | |
doc = inspect.getdoc(mod3.ParentInheritDoc.foo) | |
self.assertEqual(doc, 'docstring for foo defined in parent') | |
self.assertEqual(inspect.getdoc(mod3.ChildInheritDoc.foo), doc) | |
self.assertEqual(inspect.getdoc(mod3.ChildInheritDefineDoc.foo), doc) |
@property | ||
def foo(self): | ||
"""docstring for the property foo""" | ||
pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pass |
@@ -0,0 +1 @@ | |||
:func:`inspect.getdoc` now correctly returns an inherited docstring on :class:`~functools.cached_property` objects if none is given in a subclass. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:func:`inspect.getdoc` now correctly returns an inherited docstring on :class:`~functools.cached_property` objects if none is given in a subclass. | |
:func:`inspect.getdoc` now correctly returns an inherited docstring on | |
:class:`~functools.cached_property` objects if none is given in a subclass. |
I haven't looked at this PR yet, but note that there is a duplicate of this function in the pydoc module. |
Good to know then. @lincolnj1 can you check also the pydoc module please? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems to me that you went overboard with the tests (the simplest test would have been enough), but overall LGTM.
This is a trauma I had from maintaining Sphinx. There were so many edge cases because of inheritance. It also serves as a robust way to detect future possible regressions (but I agree that it's a bit of an overkill). |
Yes, it seems that one is also broken in the same way (see below). I'll take a look at fixing it. Should I open a new PR to fix that or keep working in this one? Edit: I used the private _getdoc method here when I should've used the public getdoc, but the behavior is consistent using either method. >>> import pydoc
>>> import inspect
>>> pydoc._getdoc(int)
"int([x]) -> integer\nint(x, base=10) -> integer\n\nConvert a number or string to an integer, or return 0 if no arguments\nare given. If x is a number, return x.__int__(). For floating-point\nnumbers, this truncates towards zero.\n\nIf x is not a number or if base is given, then x must be a string,\nbytes, or bytearray instance representing an integer literal in the\ngiven base. The literal can be preceded by '+' or '-' and be surrounded\nby whitespace. The base defaults to 10. Valid bases are 0 and 2-36.\nBase 0 means to interpret the base from the string as an integer literal.\n>>> int('0b100', base=0)\n4"
>>> inspect.getdoc(int)
"int([x]) -> integer\nint(x, base=10) -> integer\n\nConvert a number or string to an integer, or return 0 if no arguments\nare given. If x is a number, return x.__int__(). For floating-point\nnumbers, this truncates towards zero.\n\nIf x is not a number or if base is given, then x must be a string,\nbytes, or bytearray instance representing an integer literal in the\ngiven base. The literal can be preceded by '+' or '-' and be surrounded\nby whitespace. The base defaults to 10. Valid bases are 0 and 2-36.\nBase 0 means to interpret the base from the string as an integer literal.\n>>> int('0b100', base=0)\n4"
>>> inspect.getdoc(int) == pydoc._getdoc(int)
True
>>>
>>> from functools import cached_property
>>> class A:
... @cached_property
... def foo(self):
... "this is the docstring for foo"
... return 1
...
>>> class B(A):
... @cached_property
... def foo(self): ...
...
>>> pydoc._getdoc(B.foo)
>>> pydoc._getdoc(A.foo)
'this is the docstring for foo'
>>> pydoc._getdoc(B.foo) == pydoc._getdoc(A.foo)
False
>>>
>>>
>>>
>>> class MethodBase:
... def foo(self):
... "my method foo"
...
>>> class Child(MethodBase):
... def foo(self): ...
...
>>> pydoc._getdoc(MethodBase.foo) == pydoc._getdoc(Child.foo) #method docstrings are inherited properly!
True |
If there are plans to backport this change, you need to fix the pydoc version too (it is just a copy). Otherwise, you may wait until #132686 has been resolved. |
Calling inspect.getdoc on a cached_property that inherits a docstring and does not define one would return None rather than the inherited docstring. This is because inspect._finddoc would treat the cached_property obj as a methoddescriptor and encounter an AttributeError trying to access obj.__name__. This PR adds a special case for cached_properties in _finddoc so that it can find the inherited docstring.
inspect.getdoc()
failing to return docstring for cached_property for subclass that overrides code but not docstring #131116