Skip to content
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

Hide class docstring #500

Open
mrsonne opened this issue Dec 22, 2022 · 3 comments
Open

Hide class docstring #500

mrsonne opened this issue Dec 22, 2022 · 3 comments
Labels
feature New feature or request

Comments

@mrsonne
Copy link

mrsonne commented Dec 22, 2022

Hi

Thanks for a great package!

I am trying to create sections in the docs for a class LCA . In the example below I first document the class and it's attributes from the class docstr. Subsequently methods starting with "set", "plot" and finally the remaining public methods are documented.

With my current approach the class docstr on LCA gets repeated in each section. Is there a way to avoid the class docstr being included e.g. options.self: no. Alternatively, is there a different pattern I could use - I would like to avoid adding and sorting all methods manually.

Happy holidays to you all.
Cheers, Jacob

::: src.analyses.LCA
    options: 
        members: no

## `set` methods

Some general description of set methods

::: src.analyses.LCA
    options:
      show_root_heading: false
    selection: 
        filters: 
            - "^set"

## `plot` methods

Some general description of plot methods

::: src.analyses.LCA
    options:
      show_root_heading: false
    selection: 
        filters: 
            - "^plot"

## Other methods

::: src.analyses.LCA
    options:
      show_root_heading: false
    selection: 
        filters:
            - "!^_"
            - "!^set"
            - "!^plot"

@mrsonne mrsonne added the feature New feature or request label Dec 22, 2022
@pawamoy
Copy link
Member

pawamoy commented Dec 22, 2022

Hi, thanks for the kind words 🙂
We could probably wrap the rendered docstring into a div with a specific class so that users can hide it through CSS.
An alternative is to auto-generate the Markdown page with mkdocs-gen-files, to add ::: src.analyses.LCA.method_name for each desired method.

By the way, you should use the paths option (see https://mkdocstrings.github.io/python/usage/#global-only-options) instead of prefixing your paths with src.

@mrsonne
Copy link
Author

mrsonne commented Dec 23, 2022

Thanks. The div solution would be nice if it can be controlled locally i.e. per '::: identifier'.

Meanwhile I'll try the gen-files solution and post my findings here.

And thanks for making me aware of the path global option.

@mrsonne
Copy link
Author

mrsonne commented Dec 28, 2022

If anyone comes across this post here's my working solution using gen-files. Following the gen-files docs I added a gen_pages.py (see below) which in this case generates foo.md which I reference from the nav: section in mkdocs.yml.

import inspect
import textwrap
from typing import Any, Optional, Sequence, TextIO
import mkdocs_gen_files


def gen_sectioned_docs(
    cls: type,
    filename: str,
    section_names: Sequence[str],
    section_descriptions: Optional[dict[str, str]] = None,
):
    """Generate sectioned documentation for the class in the specified `filename`.

    Args:
        cls: The target class.
        filename: Target filename of the generated docs.
        section_names: Sections names used to segrgate the member functions.
        section_descriptions: Description for each section. The keys
            are the section names and the values are the corresponding
            section descriptions. Missing and extraneous section keys
            are ignored.
    """

    # Get class name and dotted path
    cls_name = cls.__name__
    dotted_path = cls.__module__

    # Get list of (name, value) for cls
    methods = inspect.getmembers(cls, predicate=inspect.isfunction)
    methods += inspect.getmembers(cls, predicate=inspect.ismethod)

    # For bookeeping
    seen_methods = set()

    # Handle section_descriptions not specified
    if section_descriptions is None:
        _section_descriptions = {}
    else:
        _section_descriptions = section_descriptions

    def filter_methods(
        methods: Sequence[tuple[str, Any]], section_name=None
    ) -> list[tuple[str, Any]]:
        """Returns a filtered version of the specified methods list"""
        methods_in_section = []
        for name, fun in methods:

            # Skip methods that don't match the section name
            if (section_name is not None) and (not name.startswith(section_name)):
                continue

            # Skip private methods
            if name.startswith("_"):
                continue

            # Skip methods with no docstr
            if inspect.getdoc(fun) is None:
                continue

            methods_in_section.append((name, fun))

        return methods_in_section

    def add_methods_to_docs(methods: Sequence[tuple[str, Any]], file: TextIO):
        """Add lines to documentation file"""
        for method_name, _ in methods:
            file.write(f"::: {dotted_path}.{cls_name}.{method_name}\n")

    with mkdocs_gen_files.open(filename, "w") as f:

        # Add class docs
        f.write(
            textwrap.dedent(
                f"""
                ::: {dotted_path}.{cls_name}
                    options:
                        members: no
                """
            )
        )

        for section_name in section_names:

            # Add header
            f.write(f"## `{section_name}` methods\n")

            # Add section introduction
            if section_name in _section_descriptions:
                f.write(f"{_section_descriptions[section_name]}\n")

            # Filter methods to match section
            methods_in_section = filter_methods(methods, section_name)

            # Add filtered method
            add_methods_to_docs(methods_in_section, f)

            # Save methods that were added
            seen_methods.update(methods_in_section)

        # Make section for remaining methods
        f.write(f"## Other methods\n")
        unseen_methods = list(set(methods) - seen_methods)
        methods_in_section = filter_methods(unseen_methods)
        add_methods_to_docs(methods_in_section, f)


from src.analyses import LCA

SECTION_NAMES = "from", "set", "plot", "add", "save"

SECTION_DESCRIPTIONS = {
    "from": "`from_` methods can create an `LCA` object from a data source.",
    "set": "`set_` methods can _description goes here_.",
    "plot": "`plot_` methods can _description goes here_.",
    "add": "`add_` methods can _description goes here_.",
    "save": "`save_` methods can save the object in various representations.",
}

gen_sectioned_docs(LCA, "foo.md", SECTION_NAMES, SECTION_DESCRIPTIONS)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants