mkdocstrings / python

A Python handler for mkdocstrings.
https://mkdocstrings.github.io/python
ISC License
191 stars 36 forks source link

feat: Option to generate headings (+ ToC entries) for items in summary tables (attributes, functions/methods, classes, modules) #133

Open EvieePy opened 9 months ago

EvieePy commented 9 months ago

Description of the bug

When using the Attributes section in a class docstring, E.g.

class Hello:
    """Hello World!

    Attributes
    ----------
    thing: int
        The thing for Hello.
    """

Also applies to Google style docstrings.

No ToC of those attributes are generated. Properties (@property) in the class are properly added to the ToC.

This is also an issue on the insiders build where the use of summaries doesn't include these attributes in it's summary. And without the show_docstring_attributes: false option, the Attributes section is actually generated twice; One with all the properties already included in the ToC and another with all the attributes in the docstring (but they can't be referenced).

Adding doctstring below the attribute works, but this is not ideal, especially when dealing with large documentation or classes with a large amount of attributes or when any ordering of these attributes or logic is needed to be done in __init__.

Full traceback

No traceback.

Expected behavior

The Attributes section from the class docstring displays and add attributes to the toc appropriately and doesn't generate a secondary summary on insiders builds.

Environment information

Boost priority

Fund with Polar

pawamoy commented 9 months ago

Hello, thanks for the report :slightly_smiling_face:

No ToC of those attributes are generated.

The logic here is that docstring sections never generate entries in the Table of Contents, except for the Parameters section (recent feature), because it is special and there is no other way to create permalinks to parameters.

Contrary to parameters, attributes can be documented individually thanks to a docstring below their assignment. This lets users write a long documentation string for the attributes, more easily than in a docstring section item. Attributes sections, just like Functions, Methods, Classes and Modules sections, then act as manual summary tables. These tables can be generated automatically thanks to the auto-summary feature.

The recommended way to document code with mkdocstrings-python is therefore to document objects individually rather than in docstring sections, since these sections can be automatically generated. Some might consider it best-practice because the docstring lives right next to the attribute itself, and makes it easier to update it / remove it when the attribute changes.

If we were to make Attributes, Functions, Methods, Classes and Modules sections generate headings, and therefore ToC entries, this would clash with their "summary" behavior. To prevent that, it could be made configurable. I'm not strongly opposed to do that, but it needs careful design to make it interoperable with all the other existing options and ways to document objects. And since we already have a lot of rendering options, I'm not enthusiastic at the idea of adding more, which can greatly increase the number of rendering combinations that must be made interoperable!

About the use-case your describe: specific ordering is an interesting point :thinking: Aren't the existing ordering option working for you (members and members order)? Users tend to prefer maintaining source order, so maybe it makes sense to order attribute assignments in __init__ methods too?

Note that you can document an attribute without assigning:

def __init__(self) -> None:
    """Initialize the object."""
    self.a: int
    """Docs for `a`."""

    self.a = 0

Happy to hear your thoughts @EvieePy :slightly_smiling_face:

EvieePy commented 9 months ago

Hi, thanks for the reply!

This is all valid, though a bit unexpected as having used sphinx for a very long time now I am used to the attributes sections being referenced and summarized. I guess a potential work around is to create a markdown link with a custom header point to the class itself, though this won't be ideal when dealing with properties.

Alternatively, we can use the docstrings under the attributes.

As for my point on ordering and logic in __init__, what I meant was, when ordering of attributes is somehow important E.g. (An attribute must be defined after something else or some other call, as an example), or simply when you are dealing with a large class, mixing these attributes might become quite hard to manage and read, especially when documenting attributes that explain something in depth.

Consider the following:

class Hello:
    """This is the Hello class!

    Attributes
    ----------
    world
        The world is a very big place to us, 
        but small in the eyes of the universe.

        When we say Hello to the world, 
        we are letting everyone know how 
        excited we are to be here!

        Sorry for the long boring text, 
        but this is a common use case for
        attributes. We might need more in depth
        explanations here to let the user know
        how the attribute affects the program in
        certain ways, or how it can be used and managed.

        We might even like to include additional information like version added, 
        changed, etc and it's default, etc etc etc...
    thing
        Some small documentation.
    """
    def __init__(self) -> None:
        self.thing: int = ...
        world = do_call()
        self.world: Earth = world
        self.etc = ...

class Bye:
    """This class shows the inverse scenario of Hello."""
    def __init__(self) -> None:
        self.thing: int = ...
        """Some small documentation."""
        world = do_call()
        self.world: Earth = world
        """
        The world is a very big place to us, 
        but small in the eyes of the universe.

        When we say Hello to the world, 
        we are letting everyone know how 
        excited we are to be here!

        Sorry for the long boring text, 
        but this is a common use case for
        attributes. We might need more in depth
        explanations here to let the user know
        how the attribute affects the program in
        certain ways, or how it can be used and managed.

        We might even like to include additional information like version added, 
        changed, etc and it's default, etc etc etc...
        """
        self.etc = ...

As you can see managing the logic within init now becomes harder because we have to read through documentation, which could be separated at the top on one known and predefined space.

Thanks again, and thanks for all the work put into this!

P.S I wrote this on my phone, sorry for any mistakes

pawamoy commented 9 months ago

Thanks for explaining your use-case.

I can see how this becomes a matter of preference: I prefer Bye rather than Hello. It reminds me of the PEP 727 discussion :sweat_smile:

I myself never extensively used Sphinx, so I wasn't aware it worked that way. Is there a Sphinx example deployed somewhere, so I can see its output?

Anyway. I've thought about it and here's what I conclude: mkdocstrings is not an opinionated tool. It gives different ways of doing things to users. So I'll add a "headings" option for all "summary" sections (attributes, function/methods, classes, modules). I'll still recommend documenting each object individually in the docs though :slightly_smiling_face:

pawamoy commented 9 months ago

I'll open a new issue for a feature/fix allowing to de-duplicate summary tables when the auto-summary option is activated.

pekkaklarck commented 1 month ago

FWIW, I'd prefer writing attribute documentation in the module/class docstring instead of using inline docstrings like

some_attr = 1
"""Some doc for `some_attr`."""
another_attr = 2
"""Another doc for `another_attr`."""

Inline docstrings (similarly as PEP 727) have a benefit that documentation is close to the thing it documents and it's less likely you forget to update it if the thing changes. The problem with inline docs is that there can be a lot of documentation with examples and everything, especially when writing user facing code, and in such cases concentrating on the code can get really hard. In other words, I highly prefer Hello over Bye in the earlier example.