diff --git a/.copier-answers.yml b/.copier-answers.yml
index 2076d959..f8c074b7 100644
--- a/.copier-answers.yml
+++ b/.copier-answers.yml
@@ -1,5 +1,5 @@
# Changes here will be overwritten by Copier
-_commit: 0.15.6
+_commit: 0.15.7
_src_path: gh:pawamoy/copier-pdm
author_email: pawamoy@pm.me
author_fullname: Timothée Mazzucotelli
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d4b4606c..abf5e825 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
+## [1.1.0](https://github.com/mkdocstrings/python/releases/tag/1.1.0) - 2023-05-25
+
+[Compare with 1.0.0](https://github.com/mkdocstrings/python/compare/1.0.0...1.1.0)
+
+### Features
+
+- Support custom templates through objects' extra data ([8ff2b06](https://github.com/mkdocstrings/python/commit/8ff2b06295e848b9c84867802eb845adf061dc10) by Timothée Mazzucotelli). [PR #70](https://github.com/mkdocstrings/python/pull/70)
+
## [1.0.0](https://github.com/mkdocstrings/python/releases/tag/1.0.0) - 2023-05-11
[Compare with 0.10.1](https://github.com/mkdocstrings/python/compare/0.10.1...1.0.0)
diff --git a/docs/usage/extensions.md b/docs/usage/extensions.md
new file mode 100644
index 00000000..4f6b96b3
--- /dev/null
+++ b/docs/usage/extensions.md
@@ -0,0 +1,17 @@
+# Extensions
+
+## :warning: Work in Progress!
+
+The Python handler supports extensions through
+[*mkdocstrings*' handler extensions](https://mkdocstrings.github.io/usage/handlers/#handler-extensions).
+
+Specifically, additional templates can be added to the handler,
+and Griffe extensions can instruct the handler to use a particular template
+for a particular object by setting a value in the Griffe object's `extra` dictionary:
+
+```python title="griffe_extension.py"
+obj = ... # get a reference to a Griffe object
+if "mkdocstrings" not in obj.extra:
+ obj.extra["mkdocstrings"] = {}
+obj.extra["mkdocstrings"]["template"] = "template_name.html"
+```
diff --git a/docs/usage/index.md b/docs/usage/index.md
index 041e8d72..6fb703e6 100644
--- a/docs/usage/index.md
+++ b/docs/usage/index.md
@@ -75,69 +75,92 @@ plugins:
Some options are **global only**, and go directly under the handler's name.
-- `import`: this option is used to import Sphinx-compatible objects inventories from other
- documentation sites. For example, you can import the standard library
- objects inventory like this:
+#### `import`
- ```yaml title="mkdocs.yml"
- plugins:
- - mkdocstrings:
- handlers:
- python:
- import:
- - https://docs.python-requests.org/en/master/objects.inv
- ```
+This option is used to import Sphinx-compatible objects inventories from other
+documentation sites. For example, you can import the standard library
+objects inventory like this:
- When importing an inventory, you enable automatic cross-references
- to other documentation sites like the standard library docs
- or any third-party package docs. Typically, you want to import
- the inventories of your project's dependencies, at least those
- that are used in the public API.
+```yaml title="mkdocs.yml"
+plugins:
+- mkdocstrings:
+ handlers:
+ python:
+ import:
+ - https://docs.python-requests.org/en/master/objects.inv
+```
- See [*mkdocstrings*' documentation on inventories][inventories]
- for more details.
+When importing an inventory, you enable automatic cross-references
+to other documentation sites like the standard library docs
+or any third-party package docs. Typically, you want to import
+the inventories of your project's dependencies, at least those
+that are used in the public API.
- [inventories]: https://mkdocstrings.github.io/usage/#cross-references-to-other-projects-inventories
+See [*mkdocstrings*' documentation on inventories][inventories]
+for more details.
- NOTE: This global option is common to *all* handlers, however
- they might implement it differently (or not even implement it).
+ [inventories]: https://mkdocstrings.github.io/usage/#cross-references-to-other-projects-inventories
-- `paths`: this option is used to provide filesystem paths in which to search for Python modules.
- Non-absolute paths are computed as relative to MkDocs configuration file. Example:
+Additionally, the Python handler accepts a `domains` option in the import items,
+which allows to select the inventory domains to select.
+By default the Python handler only selects the `py` domain (for Python objects).
+You might find useful to also enable the [`std` domain][std domain]:
- ```yaml title="mkdocs.yml"
- plugins:
- - mkdocstrings:
- handlers:
- python:
- paths: [src] # search packages in the src folder
- ```
+ [std domain]: https://www.sphinx-doc.org/en/master/usage/restructuredtext/domains.html#the-standard-domain
- More details at [Finding modules](#finding-modules).
-
-- `load_external_modules`: this option allows resolving aliases (imports) to any external module.
- Modules are considered external when they are not part
- of the package your are injecting documentation for.
- Enabling this option will tell the handler to resolve aliases recursively
- when they are made public through the [`__all__`][__all__] variable.
-
- WARNING: **Use with caution**
- This can load a *lot* of modules through [Griffe],
- slowing down your build or triggering errors that Griffe does not yet handle.
- **We recommend using the [`preload_modules`][] option instead**,
- which acts as an include-list rather than as include-all.
-
- Example:
+```yaml title="mkdocs.yml"
+plugins:
+- mkdocstrings:
+ handlers:
+ python:
+ import:
+ - url: https://docs.python-requests.org/en/master/objects.inv
+ domains: [std, py]
+```
- ```yaml title="mkdocs.yml"
- plugins:
- - mkdocstrings:
- handlers:
- python:
- load_external_modules: true
- ```
+NOTE: The `import` option is common to *all* handlers, however
+they might implement it differently, or not even implement it.
+
+#### `paths`
+
+This option is used to provide filesystem paths in which to search for Python modules.
+Non-absolute paths are computed as relative to MkDocs configuration file. Example:
+
+```yaml title="mkdocs.yml"
+plugins:
+- mkdocstrings:
+ handlers:
+ python:
+ paths: [src] # search packages in the src folder
+```
+
+More details at [Finding modules](#finding-modules).
+
+#### `load_external_modules`
+
+This option allows resolving aliases (imports) to any external module.
+Modules are considered external when they are not part
+of the package your are injecting documentation for.
+Enabling this option will tell the handler to resolve aliases recursively
+when they are made public through the [`__all__`][__all__] variable.
+
+WARNING: **Use with caution**
+This can load a *lot* of modules through [Griffe],
+slowing down your build or triggering errors that Griffe does not yet handle.
+**We recommend using the [`preload_modules`][] option instead**,
+which acts as an include-list rather than as include-all.
+
+Example:
+
+```yaml title="mkdocs.yml"
+plugins:
+- mkdocstrings:
+ handlers:
+ python:
+ load_external_modules: true
+```
- [__all__]: https://docs.python.org/3/tutorial/modules.html#importing-from-a-package
+ [__all__]: https://docs.python.org/3/tutorial/modules.html#importing-from-a-package
### Global/local options
diff --git a/mkdocs.yml b/mkdocs.yml
index 50ba6925..85fd7ef7 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -27,6 +27,7 @@ nav:
- Sphinx: usage/docstrings/sphinx.md
- Advanced:
- Customization: usage/customization.md
+ - Extensions: usage/extensions.md
# defer to gen-files + literate-nav
- Code Reference: reference/
- Development:
diff --git a/pyproject.toml b/pyproject.toml
index 84f0f2f1..511dbcd2 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -40,8 +40,8 @@ Changelog = "https://mkdocstrings.github.io/python/changelog"
Repository = "https://github.com/mkdocstrings/python"
Issues = "https://github.com/mkdocstrings/python/issues"
Discussions = "https://github.com/mkdocstrings/python/discussions"
-Gitter = "https://gitter.im/python/community"
-Funding = "https://github.com/sponsors/mkdocstrings"
+Gitter = "https://gitter.im/mkdocstrings/python"
+Funding = "https://github.com/sponsors/pawamoy"
[tool.pdm]
version = {source = "scm"}
diff --git a/scripts/insiders.py b/scripts/insiders.py
index add870cb..0d23a45a 100644
--- a/scripts/insiders.py
+++ b/scripts/insiders.py
@@ -112,7 +112,32 @@ def load_goals(data: str, funding: int = 0, project: Project | None = None) -> d
}
-def funding_goals(source: str | list[tuple[str, str, str]], funding: int = 0) -> dict:
+def _load_goals_from_disk(path: str, funding: int = 0) -> dict[int, Goal]:
+ try:
+ data = Path(path).read_text()
+ except OSError as error:
+ raise RuntimeError(f"Could not load data from disk: {path}") from error
+ return load_goals(data, funding)
+
+
+def _load_goals_from_url(source_data: tuple[str, str, str], funding: int = 0) -> dict[int, Goal]:
+ project_name, project_url, data_fragment = source_data
+ data_url = urljoin(project_url, data_fragment)
+ try:
+ with urlopen(data_url) as response: # noqa: S310
+ data = response.read()
+ except HTTPError as error:
+ raise RuntimeError(f"Could not load data from network: {data_url}") from error
+ return load_goals(data, funding, project=Project(name=project_name, url=project_url))
+
+
+def _load_goals(source: str | tuple[str, str, str], funding: int = 0) -> dict[int, Goal]:
+ if isinstance(source, str):
+ return _load_goals_from_disk(source, funding)
+ return _load_goals_from_url(source, funding)
+
+
+def funding_goals(source: str | list[str | tuple[str, str, str]], funding: int = 0) -> dict[int, Goal]:
"""Load funding goals from a given data source.
Parameters:
@@ -123,20 +148,10 @@ def funding_goals(source: str | list[tuple[str, str, str]], funding: int = 0) ->
A dictionaries of goals, keys being their target monthly amount.
"""
if isinstance(source, str):
- try:
- data = Path(source).read_text()
- except OSError as error:
- raise RuntimeError(f"Could not load data from disk: {source}") from error
- return load_goals(data, funding)
+ return _load_goals_from_disk(source, funding)
goals = {}
- for project_name, project_url, data_fragment in source:
- data_url = urljoin(project_url, data_fragment)
- try:
- with urlopen(data_url) as response: # noqa: S310
- data = response.read()
- except HTTPError as error:
- raise RuntimeError(f"Could not load data from network: {data_url}") from error
- source_goals = load_goals(data, funding, project=Project(name=project_name, url=project_url))
+ for src in source:
+ source_goals = _load_goals(src)
for amount, goal in source_goals.items():
if amount not in goals:
goals[amount] = goal
diff --git a/src/mkdocstrings_handlers/python/handler.py b/src/mkdocstrings_handlers/python/handler.py
index 9bfb02f4..c5a06d0c 100644
--- a/src/mkdocstrings_handlers/python/handler.py
+++ b/src/mkdocstrings_handlers/python/handler.py
@@ -297,7 +297,8 @@ def render(self, data: CollectorItem, config: Mapping[str, Any]) -> str: # noqa
mutabled_config = dict(copy.deepcopy(config))
final_config = ChainMap(mutabled_config, self.default_config)
- template = self.env.get_template(f"{data.kind.value}.html")
+ template_name = rendering.do_get_template(data)
+ template = self.env.get_template(template_name)
# Heading level is a "state" variable, that will change at each step
# of the rendering recursion. Therefore, it's easier to use it as a plain value
@@ -335,6 +336,7 @@ def update_env(self, md: Markdown, config: dict) -> None: # noqa: D102 (ignore
self.env.filters["format_signature"] = rendering.do_format_signature
self.env.filters["filter_objects"] = rendering.do_filter_objects
self.env.filters["stash_crossref"] = lambda ref, length: ref
+ self.env.filters["get_template"] = rendering.do_get_template
def get_anchors(self, data: CollectorItem) -> set[str]: # noqa: D102 (ignore missing docstring)
try:
diff --git a/src/mkdocstrings_handlers/python/rendering.py b/src/mkdocstrings_handlers/python/rendering.py
index d1f0eb75..5c8b0f19 100644
--- a/src/mkdocstrings_handlers/python/rendering.py
+++ b/src/mkdocstrings_handlers/python/rendering.py
@@ -248,3 +248,16 @@ def formatter(code: str, line_length: int) -> str:
return format_str(code, mode=mode)
return formatter
+
+
+def do_get_template(obj: Object) -> str:
+ """Get the template name used to render an object.
+
+ Parameters:
+ obj: A Griffe object.
+
+ Returns:
+ A template name.
+ """
+ extra_data = getattr(obj, "extra", {}).get("mkdocstrings", {})
+ return extra_data.get("template", "") or f"{obj.kind.value}.html"
diff --git a/src/mkdocstrings_handlers/python/templates/material/_base/children.html b/src/mkdocstrings_handlers/python/templates/material/_base/children.html
index 9e27ed0f..dda1c5ff 100644
--- a/src/mkdocstrings_handlers/python/templates/material/_base/children.html
+++ b/src/mkdocstrings_handlers/python/templates/material/_base/children.html
@@ -27,7 +27,7 @@
{% with heading_level = heading_level + extra_level %}
{% for attribute in attributes|order_members(config.members_order, members_list) %}
{% if not attribute.is_alias or attribute.is_explicitely_exported %}
- {% include "attribute.html" with context %}
+ {% include attribute|get_template with context %}
{% endif %}
{% endfor %}
{% endwith %}
@@ -42,7 +42,7 @@
{% with heading_level = heading_level + extra_level %}
{% for class in classes|order_members(config.members_order, members_list) %}
{% if not class.is_alias or class.is_explicitely_exported %}
- {% include "class.html" with context %}
+ {% include class|get_template with context %}
{% endif %}
{% endfor %}
{% endwith %}
@@ -58,7 +58,7 @@
{% for function in functions|order_members(config.members_order, members_list) %}
{% if not (obj.kind.value == "class" and function.name == "__init__" and config.merge_init_into_class) %}
{% if not function.is_alias or function.is_explicitely_exported %}
- {% include "function.html" with context %}
+ {% include function|get_template with context %}
{% endif %}
{% endif %}
{% endfor %}
@@ -75,7 +75,7 @@
{% with heading_level = heading_level + extra_level %}
{% for module in modules|order_members(config.members_order, members_list) %}
{% if not module.is_alias or module.is_explicitely_exported %}
- {% include "module.html" with context %}
+ {% include module|get_template with context %}
{% endif %}
{% endfor %}
{% endwith %}
@@ -91,26 +91,26 @@
filter_objects(filters=config.filters, members_list=members_list, keep_no_docstrings=config.show_if_no_docstring)|
order_members(config.members_order, members_list) %}
- {% if not (obj.kind.value == "class" and child.name == "__init__" and config.merge_init_into_class) %}
+ {% if not (obj.is_class and child.name == "__init__" and config.merge_init_into_class) %}
- {% if child.kind.value == "attribute" %}
+ {% if child.is_attribute %}
{% with attribute = child %}
- {% include "attribute.html" with context %}
+ {% include attribute|get_template with context %}
{% endwith %}
- {% elif child.kind.value == "class" %}
+ {% elif child.is_class %}
{% with class = child %}
- {% include "class.html" with context %}
+ {% include class|get_template with context %}
{% endwith %}
- {% elif child.kind.value == "function" %}
+ {% elif child.is_function %}
{% with function = child %}
- {% include "function.html" with context %}
+ {% include function|get_template with context %}
{% endwith %}
- {% elif child.kind.value == "module" and config.show_submodules %}
+ {% elif child.is_module and config.show_submodules %}
{% with module = child %}
- {% include "module.html" with context %}
+ {% include module|get_template with context %}
{% endwith %}
{% endif %}