diff --git a/docs/config.md b/docs/config.md index ab5d5e2..f0391e7 100644 --- a/docs/config.md +++ b/docs/config.md @@ -3,38 +3,77 @@ that the handler name should be `python_xref` instead of `python`. Because this handler extends the standard [mkdocstrings-python][] handler, the same options are available. -Additional options are added by this extension. Currently, there are two: +Additional options are added by this extension. Currently, there are three: -* **relative_crossrefs** - if set to true enables use of relative path syntax in +* **relative_crossrefs**: `bool` - if set to true enables use of relative path syntax in cross-references. -* **check_crossrefs** - enables early checking of all cross-references. Note that +* **check_crossrefs**: `bool` - enables early checking of all cross-references. Note that this option only takes affect if **relative_crossrefs** is also true. This option is true by default, so this option is used to disable checking. Checking can also be disabled on a per-case basis by prefixing the reference with '?', e.g. `[something][?dontcheckme]`. -!!! Example "mkdocs.yml plugins specification using this handler" - -```yaml -plugins: -- search -- mkdocstrings: - default_handler: python_xref - handlers: - python_xref: - import: - - https://docs.python.org/3/objects.inv - options: - docstring_style: google - docstring_options: - ignore_init_summary: yes - merge_init_into_class: yes - relative_crossrefs: yes - check_crossrefs: no - separate_signature: yes - show_source: no - show_root_full_path: no -``` +* **check_crossrefs_exclude**: `list[str]` - exclude cross-references matching any of these + regex patterns from crossref checking. This option can be used disabling checking on + libraries which are very expensive to import without having to disable checking for all + cross-references. + +!!! Example "mkdocs.yml plugins specifications using this handler" + + === "Always check" + + !!! warning + + Crossrefs to libraries which are expensive to import (e.g., machine learning + frameworks) can cause very slow build times when checked! + + ```yaml + plugins: + - mkdocstrings: + default_handler: python_xref + handlers: + python_xref: + import: + - https://docs.python.org/3/objects.inv + - https://pytorch.org/docs/stable/objects.inv + options: + relative_crossrefs: yes + ``` + + === "Check all but listed exclusions" + + ```yaml + plugins: + - mkdocstrings: + default_handler: python_xref + handlers: + python_xref: + import: + - https://docs.python.org/3/objects.inv + - https://pytorch.org/docs/stable/objects.inv + options: + relative_crossrefs: yes + check_crossrefs_exclude: + - "^torch\\.(.*)" + ``` + + === "Never check" + + ```yaml + plugins: + - mkdocstrings: + default_handler: python_xref + handlers: + python_xref: + import: + - https://docs.python.org/3/objects.inv + - https://pytorch.org/docs/stable/objects.inv + options: + relative_crossrefs: yes + check_crossrefs: no + ``` + + [mkdocstrings-python]: https://mkdocstrings.github.io/python/ diff --git a/docs/index.md b/docs/index.md index 895569e..9b582d1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -105,7 +105,7 @@ but has not yet been accepted. If `relative_crossrefs` and `check_crossrefs` are both enabled (the latter is true by default), then all cross-reference expressions will be checked to ensure that they exist and failures -will be reported with the source location. Otherwise, missing cross-references will be reported +will be reported with the source location (with the exception of any crossrefs which match a pattern in `check_crossrefs_exclude`). Otherwise, missing cross-references will be reported by mkdocstrings without the source location, in which case it is often difficult to locate the source of the error. Note that the errors generated by this feature are in addition to the errors from mkdocstrings. @@ -121,5 +121,3 @@ This function returns a [Path][?pathlib.] instance. [mkdocstrings]: https://mkdocstrings.github.io/ [mkdocstrings_python]: https://mkdocstrings.github.io/python/ [relative-crossref-issue]: https://github.com/mkdocstrings/python/issues/27 - - diff --git a/src/mkdocstrings_handlers/python_xref/VERSION b/src/mkdocstrings_handlers/python_xref/VERSION index 4a02d2c..c807441 100644 --- a/src/mkdocstrings_handlers/python_xref/VERSION +++ b/src/mkdocstrings_handlers/python_xref/VERSION @@ -1 +1 @@ -1.16.2 +1.16.3 diff --git a/src/mkdocstrings_handlers/python_xref/handler.py b/src/mkdocstrings_handlers/python_xref/handler.py index 2efeed3..a1a61dc 100644 --- a/src/mkdocstrings_handlers/python_xref/handler.py +++ b/src/mkdocstrings_handlers/python_xref/handler.py @@ -17,8 +17,10 @@ from __future__ import annotations +import re import sys -from dataclasses import dataclass, fields +from dataclasses import dataclass, field, fields +from functools import partial from pathlib import Path from typing import Any, ClassVar, Mapping, MutableMapping, Optional from warnings import warn @@ -43,6 +45,7 @@ @dataclass(**_dataclass_options) class PythonRelXRefOptions(PythonOptions): check_crossrefs: bool = True + check_crossrefs_exclude: list[str | re.Pattern] = field(default_factory=list) class PythonRelXRefHandler(PythonHandler): """Extended version of mkdocstrings Python handler @@ -62,26 +65,30 @@ def __init__(self, config: PythonConfig, base_dir: Path, **kwargs: Any) -> None: base_dir: The base directory of the project. **kwargs: Arguments passed to the parent constructor. """ - check_crossrefs = config.options.pop('check_crossrefs', None) # Remove + self.check_crossrefs = config.options.pop('check_crossrefs', True) + exclude = config.options.pop('check_crossrefs_exclude', []) + self.check_crossrefs_exclude = [re.compile(p) for p in exclude] super().__init__(config, base_dir, **kwargs) - if check_crossrefs is not None: - self.global_options["check_crossrefs"] = check_crossrefs def get_options(self, local_options: Mapping[str, Any]) -> PythonRelXRefOptions: local_options = dict(local_options) - check_crossrefs = local_options.pop('check_crossrefs', None) + check_crossrefs = local_options.pop( + 'check_crossrefs', self.check_crossrefs) + check_crossrefs_exclude = local_options.pop( + 'check_crossrefs_exclude', self.check_crossrefs_exclude) _opts = super().get_options(local_options) opts = PythonRelXRefOptions( + check_crossrefs=check_crossrefs, + check_crossrefs_exclude=check_crossrefs_exclude, **{field.name: getattr(_opts, field.name) for field in fields(_opts)} ) - if check_crossrefs is not None: - opts.check_crossrefs = bool(check_crossrefs) return opts def render(self, data: CollectorItem, options: PythonOptions) -> str: if options.relative_crossrefs: - if isinstance(options, PythonRelXRefOptions): - checkref = self._check_ref if options.check_crossrefs else None + if isinstance(options, PythonRelXRefOptions) and options.check_crossrefs: + checkref = partial( + self._check_ref, exclude=options.check_crossrefs_exclude) else: checkref = None substitute_relative_crossrefs(data, checkref=checkref) @@ -98,8 +105,11 @@ def get_templates_dir(self, handler: Optional[str] = None) -> Path: handler = 'python' return super().get_templates_dir(handler) - def _check_ref(self, ref:str) -> bool: + def _check_ref(self, ref : str, exclude: list[str | re.Pattern] = []) -> bool: """Check for existence of reference""" + for ex in exclude: + if re.match(ex, ref): + return True try: self.collect(ref, PythonOptions()) return True