Skip to content

Commit

Permalink
refactor: Update code base for mkdocstrings 0.28
Browse files Browse the repository at this point in the history
  • Loading branch information
pawamoy committed Jan 29, 2025
1 parent ad6f4ef commit 4d16efa
Show file tree
Hide file tree
Showing 8 changed files with 358 additions and 90 deletions.
179 changes: 179 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
# Configuration

This page lists the available configuration options and what they achieve.

[](){#option-extra}
## `extra`

- **:octicons-package-24: Type [`dict`][] :material-equal: `{}`{ title="default value" }**

The `extra` option lets you inject additional variables into the Jinja context used when rendering templates. You can then use this extra context in your [overridden templates](https://mkdocstrings.github.io/usage/theming/#templates).

Local `extra` options will be merged into the global `extra` option:

```yaml title="in mkdocs.yml (global configuration)"
plugins:
- mkdocstrings:
handlers:
shell:
options:
extra:
hello: world
```
```md title="in docs/some_page.md (local configuration)"
::: your_package.your_module.your_func
handler: shell
options:
extra:
foo: bar
```
...will inject both `hello` and `foo` into the Jinja context when rendering `your_package.your_module.your_func`.

[](){#option-heading_level}
## `heading_level`

- **:octicons-package-24: Type [`int`][] :material-equal: `2`{ title="default value" }**

The initial heading level to use.

When injecting documentation for an object,
the object itself and its members are rendered.
For each layer of objects, we increase the heading level by 1.

The initial heading level will be used for the first layer.
If you set it to 3, then headings will start with `<h3>`.

If the [heading for the root object][show_root_heading] is not shown,
then the initial heading level is used for its members.

```yaml title="in mkdocs.yml (global configuration)"
plugins:
- mkdocstrings:
handlers:
shell:
options:
heading_level: 2
```

```md title="or in docs/some_page.md (local configuration)"
::: path.to.module
handler: shell
options:
heading_level: 3
```

/// admonition | Preview
type: preview

//// tab | With level 3 and root heading
<h3><code>module</code> (3)</h3>
<p>Docstring of the module.</p>
<h4><code>ClassA</code> (4)</h4>
<p>Docstring of class A.</p>
<h4><code>ClassB</code> (4)</h4>
<p>Docstring of class B.</p>
<h5><code>method_1</code> (5)</h5>
<p>Docstring of the method.</p>
////

//// tab | With level 3, without root heading
<p>Docstring of the module.</p>
<h3><code>ClassA</code> (3)</h3>
<p>Docstring of class A.</p>
<h3><code>ClassB</code> (3)</h3>
<p>Docstring of class B.</p>
<h4><code>method_1</code> (4)</h4>
<p>Docstring of the method.</p>
////
///

[](){#option-show_root_heading}
## `show_root_heading`

- **:octicons-package-24: Type [`bool`][] :material-equal: `False`{ title="default value" }**

Show the heading of the object at the root of the documentation tree
(i.e. the object referenced by the identifier after `:::`).

While this option defaults to false for backwards compatibility, we recommend setting it to true. Note that the heading of the root object can be a level 1 heading (the first on the page):

```md
# ::: path.to.object
```

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

```md title="or in docs/some_page.md (local configuration)"
::: path.to.Class
handler: shell
options:
show_root_heading: true
```

[](){#option-show_root_toc_entry}
## `show_root_toc_entry`

- **:octicons-package-24: Type [`bool`][] :material-equal: `True`{ title="default value" }**
<!-- - **:octicons-project-template-24: Template :material-null:** (N/A) -->

If the root heading is not shown, at least add a ToC entry for it.

If you inject documentation for an object in the middle of a page,
after long paragraphs, and without showing the [root heading][show_root_heading],
then you will not be able to link to this particular object
as it won't have a permalink and will be "lost" in the middle of text.
In that case, it is useful to add a hidden anchor to the document,
which will also appear in the table of contents.

In other cases, you might want to disable the entry to avoid polluting the ToC.
It is not possible to show the root heading *and* hide the ToC entry.

```yaml title="in mkdocs.yml (global configuration)"
plugins:
- mkdocstrings:
handlers:
shell:
options:
show_root_toc_entry: true
```

```md title="or in docs/some_page.md (local configuration)"
## Some heading
Lots of text.
::: path.to.object
handler: shell
options:
show_root_toc_entry: false
## Other heading.
More text.
```

/// admonition | Preview
type: preview

//// tab | With ToC entry
**Table of contents**
[Some heading](#permalink-to-some-heading){ title="#permalink-to-some-heading" }
[`object`](#permalink-to-object){ title="#permalink-to-object" }
[Other heading](#permalink-to-other-heading){ title="#permalink-to-other-heading" }
////

//// tab | Without ToC entry
**Table of contents**
[Some heading](#permalink-to-some-heading){ title="#permalink-to-some-heading" }
[Other heading](#permalink-to-other-heading){ title="#permalink-to-other-heading" }
////
///
2 changes: 2 additions & 0 deletions duties.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ def check_types(ctx: Context) -> None:
ctx.run(
tools.mypy(*PY_SRC_LIST, config_file="config/mypy.ini"),
title=pyprefix("Type-checking"),
# TODO: Update when Pydantic supports 3.14.
nofail=sys.version_info >= (3, 14),
)


Expand Down
2 changes: 2 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ validation:
nav:
- Home:
- Overview: index.md
- Configuration: configuration.md
- Changelog: changelog.md
- Credits: credits.md
- License: license.md
Expand Down Expand Up @@ -107,6 +108,7 @@ markdown_extensions:

plugins:
- search
- autorefs
- markdown-exec
- gen-files:
scripts:
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ classifiers = [
"Typing :: Typed",
]
dependencies = [
"mkdocstrings>=0.18",
"mkdocstrings>=0.28",
"shellman>=1.0.0",
]

Expand Down Expand Up @@ -106,4 +106,5 @@ dev = [
"mkdocstrings[python]>=0.25",
# YORE: EOL 3.10: Remove line.
"tomli>=2.0; python_version < '3.11'",
"pydantic>=2.10",
]
133 changes: 133 additions & 0 deletions src/mkdocstrings_handlers/shell/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"""Configuration and options dataclasses."""

from __future__ import annotations

import sys
from dataclasses import field
from typing import TYPE_CHECKING, Annotated, Any

# YORE: EOL 3.10: Replace block with line 2.
if sys.version_info >= (3, 11):
from typing import Self
else:
from typing_extensions import Self

try:
# When Pydantic is available, use it to validate options (done automatically).
# Users can therefore opt into validation by installing Pydantic in development/CI.
# When building the docs to deploy them, Pydantic is not required anymore.

# When building our own docs, Pydantic is always installed (see `docs` group in `pyproject.toml`)
# to allow automatic generation of a JSON Schema. The JSON Schema is then referenced by mkdocstrings,
# which is itself referenced by mkdocs-material's schema system. For example in VSCode:
#
# "yaml.schemas": {
# "https://squidfunk.github.io/mkdocs-material/schema.json": "mkdocs.yml"
# }
from inspect import cleandoc

from pydantic import Field as BaseField
from pydantic.dataclasses import dataclass

_base_url = "https://mkdocstrings.github.io/shell/configuration"

def Field( # noqa: N802, D103
*args: Any,
description: str,
parent: str | None = None,
**kwargs: Any,
) -> None:
def _add_markdown_description(schema: dict[str, Any]) -> None:
url = f"{_base_url}/#{parent or schema['title']}"
schema["markdownDescription"] = f"[DOCUMENTATION]({url})\n\n{schema['description']}"

return BaseField(
*args,
description=cleandoc(description),
field_title_generator=lambda name, _: name,
json_schema_extra=_add_markdown_description,
**kwargs,
)
except ImportError:
from dataclasses import dataclass # type: ignore[no-redef]

def Field(*args: Any, **kwargs: Any) -> None: # type: ignore[misc] # noqa: D103, N802
pass


if TYPE_CHECKING:
from collections.abc import MutableMapping


# YORE: EOL 3.9: Remove block.
_dataclass_options = {"frozen": True}
if sys.version_info >= (3, 10):
_dataclass_options["kw_only"] = True


# YORE: EOL 3.9: Replace `**_dataclass_options` with `frozen=True, kw_only=True` within line.
@dataclass(**_dataclass_options) # type: ignore[call-overload]
class ShellInputOptions:
"""Accepted input options."""

extra: Annotated[
dict[str, Any],
Field(description="Extra options."),
] = field(default_factory=dict)

heading_level: Annotated[
int,
Field(description="The initial heading level to use."),
] = 2

show_root_heading: Annotated[
bool,
Field(
description="""Show the heading of the object at the root of the documentation tree.
The root object is the object referenced by the identifier after `:::`.
""",
),
] = False

show_root_toc_entry: Annotated[
bool,
Field(
description="If the root heading is not shown, at least add a ToC entry for it.",
),
] = True

@classmethod
def coerce(cls, **data: Any) -> MutableMapping[str, Any]:
"""Coerce data."""
return data

@classmethod
def from_data(cls, **data: Any) -> Self:
"""Create an instance from a dictionary."""
return cls(**cls.coerce(**data))


# YORE: EOL 3.9: Replace `**_dataclass_options` with `frozen=True, kw_only=True` within line.
@dataclass(**_dataclass_options) # type: ignore[call-overload]
class ShellOptions(ShellInputOptions): # type: ignore[override,unused-ignore]
"""Final options passed as template context."""


# YORE: EOL 3.9: Replace `**_dataclass_options` with `frozen=True, kw_only=True` within line.
@dataclass(**_dataclass_options) # type: ignore[call-overload]
class ShellInputConfig:
"""Python handler configuration."""

options: Annotated[
ShellInputOptions,
Field(description="Configuration options for collecting and rendering objects."),
] = field(default_factory=ShellInputOptions)


# YORE: EOL 3.9: Replace `**_dataclass_options` with `frozen=True, kw_only=True` within line.
@dataclass(**_dataclass_options) # type: ignore[call-overload]
class ShellConfig(ShellInputConfig): # type: ignore[override,unused-ignore]
"""Shell handler configuration."""

options: dict[str, Any] = field(default_factory=dict) # type: ignore[assignment]
Loading

0 comments on commit 4d16efa

Please # to comment.