Skip to content

Commit

Permalink
feat: Support inheritance
Browse files Browse the repository at this point in the history
  • Loading branch information
pawamoy committed Jul 13, 2023
1 parent 8eb459f commit ae42356
Show file tree
Hide file tree
Showing 8 changed files with 325 additions and 41 deletions.
163 changes: 163 additions & 0 deletions docs/usage/configuration/members.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,169 @@ this_attribute = 0

INFO: **The default behavior (with unspecified `members` or `members: null`) is to use [`filters`][].**

## `inherited_members`

- **:octicons-package-24: Type <code><span data-autorefs-optional="list">list</span>[<span data-autorefs-optional="str">str</span>] |
<span data-autorefs-optional="bool">bool</span></code> :material-equal: `False`{ title="default value" }**
<!-- - **:octicons-project-template-24: Template :material-null:** (N/A) -->

An explicit list of inherited members (for classes) to render.

Inherited members are always fetched from classes that are in the same package
as the currently rendered class. To fetch members inherited from base classes,
themselves coming from external packages, use the [`preload_modules`][preload_modules] option.
For example, if your class inherits from Pydantic's `BaseModel`, and you want to render
`BaseModel`'s methods in your class, use `preload_modules: [pydantic]`.
The `pydantic` package must be available in the current environment.

Passing a falsy value (`no`, `false` in YAML) or an empty list (`[]`)
will tell the Python handler not to render any inherited member.
Passing a truthy value (`yes`, `true` in YAML)
will tell the Python handler to render every inherited member.

When all inherited members are selected with `inherited_members: true`,
it is possible to specify both members and inherited members in the `members` list:

```yaml
inherited_members: true
members:
- inherited_member_a
- inherited_member_b
- member_x
- member_y
```
The alternative is not supported:
```yaml
inherited_members:
- inherited_member_a
- inherited_member_b
members:
- member_x
- member_y
```
...because it would make members ordering ambiguous/unspecified.
You can render inherited members *only* by setting `inherited_members: true`
(or a list of inherited members) and setting `members: false`:

```yaml
inherited_members: true
members: false
```

```yaml
inherited_members:
- inherited_member_a
- inherited_member_b
members: false
```

You can render *all declared members* and all or specific inherited members
by leaving `members` as null (default):

```yaml
inherited_members:
- inherited_member_a
- inherited_member_b
# members: null # (1)
```

1. In this case, only declared members will be subject
to further filtering with [`filters`][filters] and [`docstrings`][show_if_no_docstring].

```yaml
inherited_members: true # (1)
# members: null
```

1. In this case, both declared and inherited members will be subject
to further filtering with [`filters`][filters] and [`docstrings`][show_if_no_docstring].

You can render *all declared members* and all or specific inherited members,
avoiding further filtering with [`filters`][filters] and [`docstrings`][show_if_no_docstring]
by setting `members: true`:

```yaml
inherited_members: true
members: true
```

```yaml
inherited_members:
- inherited_member_a
- inherited_member_b
members: true
```

The general rule is that declared or inherited members specified in lists
are never filtered out.

```yaml title="in mkdocs.yml (global configuration)"
plugins:
- mkdocstrings:
handlers:
python:
options:
inherited_members: false
```

```md title="or in docs/some_page.md (local configuration)"
::: package.module
options:
inherited_members: true
```

```python title="package/module.py"
"""Module docstring."""
class Base:
"""Base class."""
def base(self):
"""Base method."""
class Main(Base):
"""Main class."""
def main(self):
"""Main method."""
```

/// admonition | Preview
type: preview

//// tab | With inherited members
<p>Module docstring.</p>
<h2><code>Base</code></h2>
<p>Base class.</p>
<h3><code>base</code></h3>
<p>Base method.</p>
<h2><code>Main</code></h2>
<p>Main class.</p>
<h3><code>base</code></h3>
<p>Base method.</p>
<h3><code>main</code></h3>
<p>Main method.</p>
////

//// tab | Without inherited members
<p>Module docstring.</p>
<h2><code>Base</code></h2>
<p>Base class.</p>
<h3><code>base</code></h3>
<p>Base method.</p>
<h2><code>Main</code></h2>
<p>Main class.</p>
<h3><code>main</code></h3>
<p>Main method.</p>
////

///

## `members_order`

- **:octicons-package-24: Type [`str`][] :material-equal: `"alphabetical"`{ title="default value" }**
Expand Down
2 changes: 2 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,9 @@ plugins:
ignore_init_summary: true
docstring_section_style: list
heading_level: 1
inherited_members: true
merge_init_into_class: true
preload_modules: [mkdocstrings]
separate_signature: true
show_root_heading: true
show_root_full_path: false
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ classifiers = [
]
dependencies = [
"mkdocstrings>=0.20",
"griffe>=0.24",
"griffe>=0.30",
]

[project.urls]
Expand Down
9 changes: 8 additions & 1 deletion src/mkdocstrings_handlers/python/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ class PythonHandler(BaseHandler):
"members_order": rendering.Order.alphabetical.value,
"docstring_section_style": "table",
"members": None,
"inherited_members": False,
"filters": ["!^_[^_]"],
"annotations_path": "brief",
"preload_modules": None,
Expand Down Expand Up @@ -138,7 +139,13 @@ class PythonHandler(BaseHandler):
show_category_heading (bool): When grouped by categories, show a heading for each category. Default: `False`.
Attributes: Members options:
members (list[str] | False | None): An explicit list of members to render. Default: `None`.
inherited_members (list[str] | bool | None): A boolean, or an explicit list of inherited members to render.
If true, select all inherited members, which can then be filtered with `members`.
If false or empty list, do not select any inherited member. Default: `False`.
members (list[str] | bool | None): A boolean, or an explicit list of members to render.
If true, select all members without further filtering.
If false or empty list, do not render members.
If none, select all members and apply further filtering with filters and docstrings. Default: `None`.
members_order (str): The members ordering to use. Options: `alphabetical` - order by the members names,
`source` - order members as they appear in the source file. Default: `"alphabetical"`.
filters (list[str] | None): A list of filters applied to filter objects based on their name.
Expand Down
43 changes: 31 additions & 12 deletions src/mkdocstrings_handlers/python/rendering.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,8 @@ def do_filter_objects(
objects_dictionary: dict[str, Object | Alias],
*,
filters: Sequence[tuple[Pattern, bool]] | None = None,
members_list: list[str] | None = None,
members_list: bool | list[str] | None = None,
inherited_members: bool | list[str] = False,
keep_no_docstrings: bool = True,
) -> list[Object | Alias]:
"""Filter a dictionary of objects based on their docstrings.
Expand All @@ -207,31 +208,49 @@ def do_filter_objects(
members_list: An optional, explicit list of members to keep.
When given and empty, return an empty list.
When given and not empty, ignore filters and docstrings presence/absence.
inherited_members: Whether to keep inherited members or exclude them.
keep_no_docstrings: Whether to keep objects with no/empty docstrings (recursive check).
Returns:
A list of objects.
"""
# no members
if members_list is False or members_list == []:
return []

objects = list(objects_dictionary.values())
inherited_members_specified = False
if inherited_members is True:
# Include all inherited members.
objects = list(objects_dictionary.values())
elif inherited_members is False:
# Include no inherited members.
objects = [obj for obj in objects_dictionary.values() if not obj.inherited]
else:
# Include specific inherited members.
inherited_members_specified = True
objects = [
obj for obj in objects_dictionary.values() if not obj.inherited or obj.name in set(inherited_members)
]

# all members
if members_list is True:
# Return all pre-selected members.
return objects

# list of members
if members_list is False or members_list == []:
# Return selected inherited members, if any.
return [obj for obj in objects if obj.inherited]

if members_list is not None:
return [obj for obj in objects if obj.name in set(members_list)]
# Return selected members (keeping any pre-selected inherited members).
return [
obj for obj in objects if obj.name in set(members_list) or (inherited_members_specified and obj.inherited)
]

# none, use filters and docstrings
# Use filters and docstrings.
if filters:
objects = [obj for obj in objects if _keep_object(obj.name, filters)]
objects = [
obj for obj in objects if _keep_object(obj.name, filters) or (inherited_members_specified and obj.inherited)
]
if keep_no_docstrings:
return objects
return [obj for obj in objects if obj.has_docstrings]

return [obj for obj in objects if obj.has_docstrings or (inherited_members_specified and obj.inherited)]


@lru_cache(maxsize=1)
Expand Down
Loading

0 comments on commit ae42356

Please sign in to comment.