diff --git a/.copier-answers.yml b/.copier-answers.yml
index f19d7882..3efb287c 100644
--- a/.copier-answers.yml
+++ b/.copier-answers.yml
@@ -1,5 +1,5 @@
# Changes here will be overwritten by Copier
-_commit: 0.10.8
+_commit: 0.10.10
_src_path: gh:pawamoy/copier-pdm.git
author_email: pawamoy@pm.me
author_fullname: Timothée Mazzucotelli
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4b086fed..ef882e3c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -32,7 +32,7 @@ jobs:
python-version: "3.8"
- name: Resolving dependencies
- run: pdm lock
+ run: pdm lock -v
- name: Install dependencies
run: pdm install -G duty -G docs -G quality -G typing -G security
@@ -75,6 +75,9 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
+ - name: Resolving dependencies
+ run: pdm lock -v
+
- name: Install dependencies
run: pdm install --no-editable -G duty -G tests -G docs
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 45eca34d..9086f223 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,20 @@ 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).
+## [0.20.0](https://github.com/mkdocstrings/mkdocstrings/releases/tag/0.20.0) - 2023-01-19
+
+[Compare with 0.19.1](https://github.com/mkdocstrings/mkdocstrings/compare/0.19.1...0.20.0)
+
+### Features
+- Add `enabled` configuration option ([8cf117d](https://github.com/mkdocstrings/mkdocstrings/commit/8cf117daeefb4fc522145cc567b40eb4256c0a94) by StefanBRas). [Issue #478](https://github.com/mkdocstrings/mkdocstrings/issues/478), [PR #504](https://github.com/mkdocstrings/mkdocstrings/pull/504)
+
+### Bug Fixes
+- Handle updating Jinja environment of multiple handlers ([a6ea80c](https://github.com/mkdocstrings/mkdocstrings/commit/a6ea80c992f2a200d8cee3c9ff3b651ddd049a3d) by David Patterson). [Related PR #201](https://github.com/mkdocstrings/mkdocstrings/pull/201), [Issue #502](https://github.com/mkdocstrings/mkdocstrings/issues/502), [PR #507](https://github.com/mkdocstrings/mkdocstrings/pull/507)
+
+### Code Refactoring
+- Make `_load_inventory` accept lists as arguments ([105ed82](https://github.com/mkdocstrings/mkdocstrings/commit/105ed8210d4665f6b52f2cc04d56df2d35cd3caf) by Sorin Sbarnea). [Needed by PR mkdocstrings/python#49](https://github.com/mkdocstrings/python/issues/49), [PR #511](https://github.com/mkdocstrings/mkdocstrings/pull/511)
+- Remove support for MkDocs < 1.2 (we already depended on MkDocs >= 1.2) ([ac963c8](https://github.com/mkdocstrings/mkdocstrings/commit/ac963c88c793e640d2a7a31392aff1fc2d15ba52) by Timothée Mazzucotelli).
+
## [0.19.1](https://github.com/mkdocstrings/mkdocstrings/releases/tag/0.19.1) - 2022-12-13
[Compare with 0.19.0](https://github.com/mkdocstrings/mkdocstrings/compare/0.19.0...0.19.1)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 64f33dc1..b796eb8c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -19,14 +19,14 @@ make setup
> you'll need to install
> [PDM](https://github.com/pdm-project/pdm)
> manually.
->
+>
> You can install it with:
->
+>
> ```bash
> python3 -m pip install --user pipx
> pipx install pdm
> ```
->
+>
> Now you can try running `make setup` again,
> or simply `pdm install`.
@@ -67,7 +67,7 @@ As usual:
If you are unsure about how to fix or ignore a warning,
just let the continuous integration fail,
-and we will help you during review.
+and we will help you during the review.
Don't bother updating the changelog, we will take care of this.
@@ -91,7 +91,7 @@ Scope and body are optional. Type can be:
- `feat`: New feature.
- `fix`: Bug fix.
- `perf`: About performance.
-- `refactor`: Changes which are not features nor bug fixes.
+- `refactor`: Changes that are not features or bug fixes.
- `style`: A change in code style/format.
- `tests`: About tests.
@@ -109,7 +109,7 @@ Fixes #15.
Link to any related issue in the Pull Request message.
-During review, we recommend using fixups:
+During the review, we recommend using fixups:
```bash
# SHA is the SHA of the commit you want to fix
diff --git a/config/flake8.ini b/config/flake8.ini
index e0a4cfbd..d477956a 100644
--- a/config/flake8.ini
+++ b/config/flake8.ini
@@ -87,6 +87,8 @@ ignore =
WPS230
# too complex function
WPS231
+ # too many objects imported from module
+ WPS235
# too many variables unpacked
WPS236
# too complex f-string
@@ -103,6 +105,8 @@ ignore =
WPS336
# line starts with dot (incompatible with Black)
WPS348
+ # yield from ()
+ WPS353
# blank line before bracket (incompatible with Black)
WPS355
# raw string
diff --git a/config/mypy.ini b/config/mypy.ini
index 814e2ac8..cb0dd886 100644
--- a/config/mypy.ini
+++ b/config/mypy.ini
@@ -3,3 +3,5 @@ ignore_missing_imports = true
exclude = tests/fixtures/
warn_unused_ignores = true
show_error_codes = true
+namespace_packages = true
+explicit_package_bases = true
diff --git a/docs/gen_ref_nav.py b/docs/gen_ref_nav.py
index a350a732..71c2dcba 100644
--- a/docs/gen_ref_nav.py
+++ b/docs/gen_ref_nav.py
@@ -28,5 +28,5 @@
mkdocs_gen_files.set_edit_path(full_doc_path, Path("../") / path)
-with mkdocs_gen_files.open("reference/SUMMARY.md", "w") as nav_file:
+with mkdocs_gen_files.open("reference/SUMMARY.txt", "w") as nav_file:
nav_file.writelines(nav.build_literate_nav())
diff --git a/docs/usage.md b/docs/usage.md
index ed08768d..3c7c5707 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -113,6 +113,9 @@ The above is equivalent to:
- `handlers`: the handlers global configuration.
- `enable_inventory`: whether to enable inventory file generation.
See [Cross-references to other projects / inventories](#cross-references-to-other-projects-inventories)
+- `enabled` **(New in version 0.20)**: Whether to enable the plugin. Defaults to `true`.
+ Can be used to reduce build times when doing local development.
+ Especially useful when used with environment variables (see example below).
- `watch` **(deprecated)**: a list of directories to watch while serving the documentation.
See [Watch directories](#watch-directories). **Deprecated in favor of the now built-in
[`watch` feature of MkDocs](https://www.mkdocs.org/user-guide/configuration/#watch).
@@ -121,6 +124,7 @@ The above is equivalent to:
```yaml title="mkdocs.yml"
plugins:
- mkdocstrings:
+ enabled: !ENV [ENABLE_MKDOCSTRINGS, true]
custom_templates: templates
default_handler: python
handlers:
@@ -357,3 +361,4 @@ For example, it will not tell the Python handler to look for packages in these p
(the paths are not added to the `PYTHONPATH` variable).
If you want to tell Python where to look for packages and modules,
see [Python Handler: Finding modules](https://mkdocstrings.github.io/python/usage/#finding-modules).
+
diff --git a/duties.py b/duties.py
index 58233386..e5103cbf 100644
--- a/duties.py
+++ b/duties.py
@@ -189,6 +189,7 @@ def check_types(ctx): # noqa: WPS231
Arguments:
ctx: The context instance (passed automatically).
"""
+ os.environ["MYPYPATH"] = "src"
ctx.run(f"mypy --config-file config/mypy.ini {PY_SRC}", title="Type-checking", pty=PTY)
diff --git a/mkdocs.yml b/mkdocs.yml
index 2c1ae40e..f8037a73 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -83,7 +83,7 @@ plugins:
- docs/gen_ref_nav.py
- docs/gen_redirects.py
- literate-nav:
- nav_file: SUMMARY.md
+ nav_file: SUMMARY.txt
- coverage
- section-index
- mkdocstrings:
diff --git a/src/mkdocstrings/extension.py b/src/mkdocstrings/extension.py
index 5b8848ab..8c21eaba 100644
--- a/src/mkdocstrings/extension.py
+++ b/src/mkdocstrings/extension.py
@@ -34,17 +34,12 @@
from markdown.blockprocessors import BlockProcessor
from markdown.extensions import Extension
from markdown.treeprocessors import Treeprocessor
+from mkdocs.exceptions import PluginError
from mkdocs_autorefs.plugin import AutorefsPlugin
from mkdocstrings.handlers.base import BaseHandler, CollectionError, CollectorItem, Handlers
from mkdocstrings.loggers import get_logger
-try:
- from mkdocs.exceptions import PluginError # New in MkDocs 1.2
-except ImportError:
- PluginError = SystemExit # noqa: WPS440
-
-
log = get_logger(__name__)
@@ -78,7 +73,7 @@ def __init__(
self._config = config
self._handlers = handlers
self._autorefs = autorefs
- self._updated_env = False
+ self._updated_envs: set = set()
def test(self, parent: Element, block: str) -> bool:
"""Match our autodoc instructions.
@@ -197,10 +192,10 @@ def _process_block(
log.error(f"Error reading page '{self._autorefs.current_page}':")
raise PluginError(f"Could not collect '{identifier}'") from exception
- if not self._updated_env:
+ if handler_name not in self._updated_envs: # We haven't seen this handler before on this document.
log.debug("Updating renderer's env")
handler._update_env(self.md, self._config) # noqa: WPS437 (protected member OK)
- self._updated_env = True
+ self._updated_envs.add(handler_name)
log.debug("Rendering templates")
try:
@@ -270,7 +265,7 @@ def extendMarkdown(self, md: Markdown) -> None: # noqa: N802 (casing: parent me
priority=75, # Right before markdown.blockprocessors.HashHeaderProcessor
)
md.treeprocessors.register(
- _PostProcessor(md.parser),
+ _PostProcessor(md),
"mkdocstrings_post",
priority=4, # Right after 'toc'.
)
diff --git a/src/mkdocstrings/handlers/base.py b/src/mkdocstrings/handlers/base.py
index a8de29ac..88e3de42 100644
--- a/src/mkdocstrings/handlers/base.py
+++ b/src/mkdocstrings/handlers/base.py
@@ -14,7 +14,7 @@
import warnings
from contextlib import suppress
from pathlib import Path
-from typing import Any, Dict, Iterable, List, Optional, Sequence
+from typing import Any, BinaryIO, Dict, Iterable, Iterator, List, Mapping, Optional, Sequence
from xml.etree.ElementTree import Element, tostring
from jinja2 import Environment, FileSystemLoader
@@ -42,7 +42,7 @@ class ThemeNotSupported(Exception):
"""An exception raised to tell a theme is not supported."""
-def do_any(seq: Sequence, attribute: str = None) -> bool:
+def do_any(seq: Sequence, attribute: str | None = None) -> bool:
"""Check if at least one of the item in the sequence evaluates to true.
The `any` builtin as a filter for Jinja templates.
@@ -119,16 +119,17 @@ def __init__(self, handler: str, theme: str, custom_templates: Optional[str] = N
self._headings: List[Element] = []
self._md: Markdown = None # type: ignore # To be populated in `update_env`.
- def render(self, data: CollectorItem, config: dict) -> str:
+ def render(self, data: CollectorItem, config: Mapping[str, Any]) -> str:
"""Render a template using provided data and configuration options.
Arguments:
data: The collected data to render.
- config: The rendering options.
+ config: The handler's configuraton options.
Returns:
The rendered template as HTML.
- """ # noqa: DAR202 (excess return section)
+ """ # noqa: DAR202,DAR401
+ raise NotImplementedError
def get_templates_dir(self, handler: str) -> Path:
"""Return the path to the handler's templates directory.
@@ -298,6 +299,7 @@ def update_env(self, md: Markdown, config: dict) -> None: # noqa: W0613 (unused
self.env.filters["heading"] = self.do_heading
def _update_env(self, md: Markdown, config: dict):
+ """Update our handler to point to our configured Markdown instance, grabbing some of the config from `md`."""
extensions = config["mdx"] + [MkdocstringsInnerExtension(self._headings)]
new_md = Markdown(extensions=extensions, extension_configs=config["mdx_configs"])
@@ -317,7 +319,7 @@ class BaseCollector:
You can also implement the `teardown` method.
"""
- def collect(self, identifier: str, config: dict) -> CollectorItem:
+ def collect(self, identifier: str, config: Mapping[str, Any]) -> CollectorItem:
"""Collect data given an identifier and selection configuration.
In the implementation, you typically call a subprocess that returns JSON, and load that JSON again into
@@ -327,12 +329,12 @@ def collect(self, identifier: str, config: dict) -> CollectorItem:
identifier: An identifier for which to collect data. For example, in Python,
it would be 'mkdocstrings.handlers' to collect documentation about the handlers module.
It can be anything that you can feed to the tool of your choice.
- config: Configuration options for the tool you use to collect data. Typically called "selection" because
- these options modify how the objects or documentation are "selected" in the source code.
+ config: The handler's configuraton options.
Returns:
Anything you want, as long as you can feed it to the renderer's `render` method.
- """ # noqa: DAR202 (excess return section)
+ """ # noqa: DAR202,DAR401
+ raise NotImplementedError
def teardown(self) -> None:
"""Teardown the collector.
@@ -474,6 +476,27 @@ def __init__(self, *args: str | BaseCollector | BaseRenderer, **kwargs: str | Ba
raise ValueError("'handler' and 'theme' cannot be None")
BaseRenderer.__init__(self, handler, theme, custom_templates) # noqa: WPS609
+ @classmethod
+ def load_inventory(
+ cls,
+ in_file: BinaryIO,
+ url: str,
+ base_url: Optional[str] = None,
+ **kwargs: Any,
+ ) -> Iterator[tuple[str, str]]:
+ """Yield items and their URLs from an inventory file streamed from `in_file`.
+
+ Arguments:
+ in_file: The binary file-like object to read the inventory from.
+ url: The URL that this file is being streamed from (used to guess `base_url`).
+ base_url: The URL that this inventory's sub-paths are relative to.
+ **kwargs: Ignore additional arguments passed from the config.
+
+ Yields:
+ Tuples of (item identifier, item URL).
+ """
+ yield from ()
+
class Handlers:
"""A collection of handlers.
diff --git a/src/mkdocstrings/loggers.py b/src/mkdocstrings/loggers.py
index f75d31d2..24004f8f 100644
--- a/src/mkdocstrings/loggers.py
+++ b/src/mkdocstrings/loggers.py
@@ -18,7 +18,7 @@
except ImportError:
TEMPLATES_DIRS: Sequence[Path] = ()
else:
- TEMPLATES_DIRS = tuple(mkdocstrings_handlers.__path__) # noqa: WPS609
+ TEMPLATES_DIRS = tuple(mkdocstrings_handlers.__path__) # type: ignore[arg-type] # noqa: WPS609
class LoggerAdapter(logging.LoggerAdapter):
diff --git a/src/mkdocstrings/plugin.py b/src/mkdocstrings/plugin.py
index 2c791774..bbb5ee8a 100644
--- a/src/mkdocstrings/plugin.py
+++ b/src/mkdocstrings/plugin.py
@@ -42,6 +42,18 @@
InventoryLoaderType = Callable[..., Iterable[Tuple[str, str]]]
+def list_to_tuple(function: Callable[..., Any]) -> Callable[..., Any]:
+ """Decorater to convert lists to tuples in the arguments."""
+
+ def wrapper(*args: Any, **kwargs: Any):
+ safe_args = [tuple(item) if isinstance(item, list) else item for item in args]
+ if kwargs:
+ kwargs = {key: tuple(value) if isinstance(value, list) else value for key, value in kwargs.items()}
+ return function(*safe_args, **kwargs)
+
+ return wrapper
+
+
class MkdocstringsPlugin(BasePlugin):
"""An `mkdocs` plugin.
@@ -62,6 +74,7 @@ class MkdocstringsPlugin(BasePlugin):
("default_handler", MkType(str, default="python")),
("custom_templates", MkType(str, default=None)),
("enable_inventory", MkType(bool, default=None)),
+ ("enabled", MkType(bool, default=True)),
)
"""
The configuration options of `mkdocstrings`, written in `mkdocs.yml`.
@@ -72,6 +85,7 @@ class MkdocstringsPlugin(BasePlugin):
Whenever a file changes in one of directories, the whole documentation is built again, and the browser refreshed.
Deprecated in favor of the now built-in `watch` feature of MkDocs.
- **`default_handler`**: The default handler to use. The value is the name of the handler module. Default is "python".
+ - **`enabled`**: Whether to enable the plugin. Default is true. If false, *mkdocstrings* will not collect or render anything.
- **`handlers`**: Global configuration of handlers. You can set global configuration per handler, applied everywhere,
but overridable in each "autodoc" instruction. Example:
@@ -128,6 +142,8 @@ def on_serve(
*args: Additional arguments passed by MkDocs.
**kwargs: Additional arguments passed by MkDocs.
"""
+ if not self.plugin_enabled:
+ return
if self.config["watch"]:
for element in self.config["watch"]:
log.debug(f"Adding directory '{element}' to watcher")
@@ -150,6 +166,9 @@ def on_config(self, config: Config, **kwargs: Any) -> Config: # noqa: W0613 (un
Returns:
The modified config.
"""
+ if not self.plugin_enabled:
+ log.debug("Plugin is not enabled. Skipping.")
+ return config
log.debug("Adding extension to the list")
theme_name = None
@@ -220,6 +239,15 @@ def inventory_enabled(self) -> bool:
inventory_enabled = any(handler.enable_inventory for handler in self.handlers.seen_handlers)
return inventory_enabled
+ @property
+ def plugin_enabled(self) -> bool:
+ """Tell if the plugin is enabled or not.
+
+ Returns:
+ Whether the plugin is enabled.
+ """
+ return self.config["enabled"]
+
def on_env(self, env, config: Config, *args, **kwargs) -> None:
"""Extra actions that need to happen after all Markdown rendering and before HTML rendering.
@@ -228,6 +256,8 @@ def on_env(self, env, config: Config, *args, **kwargs) -> None:
- Write mkdocstrings' extra files into the site dir.
- Gather results from background inventory download tasks.
"""
+ if not self.plugin_enabled:
+ return
if self._handlers:
css_content = "\n".join(handler.extra_css for handler in self.handlers.seen_handlers)
write_file(css_content.encode("utf-8"), os.path.join(config["site_dir"], self.css_filename))
@@ -260,6 +290,9 @@ def on_post_build(
config: The MkDocs config object.
**kwargs: Additional arguments passed by MkDocs.
"""
+ if not self.plugin_enabled:
+ return
+
for future in self._inv_futures:
future.cancel()
@@ -279,6 +312,8 @@ def get_handler(self, handler_name: str) -> BaseHandler:
return self.handlers.get_handler(handler_name)
@classmethod
+ # lru_cache does not allow mutable arguments such lists, but that is what we load from YAML config.
+ @list_to_tuple
@functools.lru_cache(maxsize=None)
def _load_inventory(cls, loader: InventoryLoaderType, url: str, **kwargs: Any) -> Mapping[str, str]:
"""Download and process inventory files using a handler.
diff --git a/tests/conftest.py b/tests/conftest.py
index 7025b8fd..c79b04d0 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -5,14 +5,7 @@
import pytest
from markdown.core import Markdown
from mkdocs import config
-
-try:
- from mkdocs.config.defaults import get_schema
-except ImportError:
-
- def get_schema(): # noqa: WPS440
- """Fallback for old versions of MkDocs."""
- return config.DEFAULT_SCHEMA
+from mkdocs.config.defaults import get_schema
@pytest.fixture(name="mkdocs_conf")
diff --git a/tests/fixtures/builtin.py b/tests/fixtures/builtin.py
deleted file mode 100644
index cab198e3..00000000
--- a/tests/fixtures/builtin.py
+++ /dev/null
@@ -1,2 +0,0 @@
-def func(foo=print):
- """test"""
diff --git a/tests/test_inventory.py b/tests/test_inventory.py
index 471ed941..afbaa9fe 100644
--- a/tests/test_inventory.py
+++ b/tests/test_inventory.py
@@ -38,7 +38,11 @@ def test_sphinx_load_inventory_file(our_inv):
def test_sphinx_load_mkdocstrings_inventory_file():
"""Perform the 'live' inventory load test on mkdocstrings own inventory."""
mkdocs_config = load_config()
- build(mkdocs_config)
+ mkdocs_config["plugins"].run_event("startup", command="build", dirty=False)
+ try:
+ build(mkdocs_config)
+ finally:
+ mkdocs_config["plugins"].run_event("shutdown")
own_inv = mkdocs_config["plugins"]["mkdocstrings"].handlers.inventory
with open("site/objects.inv", "rb") as fp:
diff --git a/tests/test_plugin.py b/tests/test_plugin.py
new file mode 100644
index 00000000..c8649dc8
--- /dev/null
+++ b/tests/test_plugin.py
@@ -0,0 +1,27 @@
+"""Tests for the mkdocstrings plugin."""
+
+
+from mkdocs.commands.build import build
+from mkdocs.config import load_config
+
+
+def test_disabling_plugin(tmp_path):
+ """Test disabling plugin."""
+ docs_dir = tmp_path / "docs"
+ site_dir = tmp_path / "site"
+ docs_dir.mkdir()
+ site_dir.mkdir()
+ docs_dir.joinpath("index.md").write_text("::: mkdocstrings")
+
+ mkdocs_config = load_config()
+ mkdocs_config["docs_dir"] = str(docs_dir)
+ mkdocs_config["site_dir"] = str(site_dir)
+ mkdocs_config["plugins"]["mkdocstrings"].config["enabled"] = False
+ mkdocs_config["plugins"].run_event("startup", command="build", dirty=False)
+ try:
+ build(mkdocs_config)
+ finally:
+ mkdocs_config["plugins"].run_event("shutdown")
+
+ # make sure the instruction was not processed
+ assert "::: mkdocstrings" in site_dir.joinpath("index.html").read_text()