-
-
Notifications
You must be signed in to change notification settings - Fork 42
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
Docstrings are not inherited #96
Comments
It seems like griffe's behavior matches python's behavior class A:
def foo(self):
"""This is a docstring"""
class B(A):
def foo(self):
# Inherited docstring defined in A.foo
...
# returns NoneType
type(B.foo.__doc__) |
That is correct, but griffe seems to be more like As I initially stated, the end goal is cc @pawamoy |
Hello @davfsa, sorry for the late reply and thanks for the report. Currently Griffe does not support any kind of inheritance. Since different users wish for different things, there is some work to do to specify exactly what we want to provide, and how it should be configurable. Just wanted to let you know that I don't forget about issues. My time is limited but I'll eventually get to it! |
Hi @pawamoy, I'm Gerard from Cloudblue. We are also interested on the inherited methods. How is it going? Is there any plan to do it? Thanks for all the work you are doing! |
Hi Gerard, I've started working on it, it shouldn't take long :) |
That's a really good news. Thank you again @pawamoy !! |
So, we now have inheritance support, so this issue can be tackled. I'm imagining a Griffe extension that would somehow post-process the whole tree, looking for class members (methods, attributes) that do not have a docstring, and checking in the parent classes (using MRO) for equally named members with docstrings. We would assign the first found docstring to the base members, without duplicating it (we need to maintain the docstring's parent, needed to parse annotations correctly, in the right scope). What do you think? |
Sorry for taking so long to get back to you! I would personally implement it in a way that when griffe gets the docstring of a specific object, if it were to be not set, it would traverse the inheritance members upwards in the search for that docstring, returning it if found. I feel like having an extension later re-parse the whole tree might be too expensive to do after the fact, instead of just in the first pass. |
Thanks for your feedback. It's not possible to do it in the first pass anyway since we need to be able to compute the inherited members, and for this the whole tree must have finished loading. Maybe you meant to do this dynamically, when trying to access the docstring? It would mean that we have to store the configuration option somewhere in the object hierarchy to know if we should try and get docstrings from parent classes. |
Yeah, that is what I meant 😅 Also, this may be slighly different to what you would like, but I found this comment while looking at the source code: griffe/src/griffe/agents/inspector.py Lines 149 to 151 in c1476d0
would using It is possible I am also entirely misunderstanding the reason for the comment |
That's a good remark. This comment is in the "inspector", which loads data from objects in memory (dynamic analysis). Using The main thing we have to consider when thinking about a solution for this, is how to integrate it well with To be honest I'm fine with fetching inherited docstrings dynamically. The one thing that is a bit annoying is that, as said above, we have to store the user choice somewhere in the object hierarchy to be able to do that: @property
def docstring(self) -> Docstring | None:
if USER_CONFIGURED_INHERIT_DOCSTRINGS: # where to store that?
self._docstring = self.try_and_get_docstring_from_parent_classes()
return self._docstring Or maybe we could add an |
We also have to think about making this configurable per-object in mkdocstrings 🤔 And if that's even something users want (like, "inherit docstrings but not for this object"). |
I would personally tackle this by only going through the inheritance and looking for the parent docstring once and then later storing it. Something along the lines of: # UNKNOWN is used here as a singleton. This could easily be NotImplemented or ...
@property
def is_inherited_docstring(self) -> bool:
if self._docstring is UNKNOWN:
self._search_for_docstring()
return self._is_inherited_docstring
@property
def docstring(self) -> bool:
if self._docstring is UNKNOWN:
self._search_for_docstring()
return self._docstring
def _search_for_docstring(self):
# This function will search for the docstring in the parents and
# set `_is_inherited_docstring` and `_docstring` accordingly
... this way, if the docstring is discovered immediately and passed, we can correctly set the attributes and never even search for it in the inheritance tree. Otherwise, we can do the search once, and have all the correct data.
I havent had a look at how this is implemented in mkdocstrings, this should be solved by the codeblock I gave before, as you will be able to properly decide whether the docstring is inherited or not.
Althought maybe not performant, the way to do this per object would be to check if the docstring exists, and if it does, check My use of properties here is to make it reusable and have different independent parts of the code "calling"
|
I wonder if this is getting more into downstream tool behavior (like mkdocstrings-python, quartodoc) than griffe? It seems okay to have this behavior in griffe, but also like the downstream tools might have everything they need on the object (class bases) to find an inherited docstring. In any event, it seems useful for people to have the option when documenting methods! edit: otherwise, it seems nice to keep |
I tried implementing it downstream indeed, in mkdocstrings-python. It worked, but wasn't elegant or efficient, because to allow a local It also complicates the renderer (Python handler)'s code, which is not great. We already have a lot of options, and therefore many more combination of options that all have to work together. For these reasons, I'm once again more inclined to provide this feature as a Griffe extension, which means the following limitation will apply: it can only be enabled/disabled once per package, not per object. I'm kinda expecting users to go full-in (always inherit docstrings) or not at all. I myself don't see a use-case where I'd want one method to inherit a docstring and not the others (or the opposite). Implementing it as an extension also lets us avoid additional complexity in Griffe itself. By complexity I mean storing downstream tools' desired state in Griffe data itself. Griffe should remain standalone. It does already store state though, for docstring parsers and options, and it's not super elegant (passing state along the call chain, modifying it in mkdocstrings-python), so I'd like to avoid making this worse. I'm not 100% satisfied with the situation but I do think it's the less worse solution 😅 Other than that, yes, we can also add an |
That's fair! I can see the value in telling people that the inheriting docstrings changes the behavior everywhere, so they don't have to worry about interactions (e.g. when switched on, all docstrings get inherited, so the |
I published a Griffe extension that allows docstrings inheritance: https://mkdocstrings.github.io/griffe-inherited-docstrings/. It's in the $500/month funding goal. I'll update the home page to show how to use it. Basically, install it and list it in the plugins:
- mkdocstrings:
handlers:
python:
options:
extensions:
- griffe_inherited_docstrings |
Describe the bug
Docstrings are not inherited. Considering this is not any special case and it defined by the Python standard, I thought it would be appropriate to open the issue here, but the end goal is for mkdocstrings.
To Reproduce
Steps to reproduce the behavior:
Expected behavior
Docstrings are inherited.
System:
griffe
version: 0.22.0Additional context
N/A
The text was updated successfully, but these errors were encountered: