mirror of
https://github.com/mkdocs/mkdocs.git
synced 2026-03-27 09:58:31 +07:00
Let plugins choose the priority order of their events (#2973)
Add a decorator that sets a priority value for plugins' events. Events with higher priority are called first. Events without a chosen priority get a default of 0. Events that have the same priority are ordered as they appear in the config.
This commit is contained in:
@@ -60,6 +60,11 @@ body.homepage>div.container>div.row>div.col-md-9 {
|
||||
|
||||
/* mkdocstrings */
|
||||
|
||||
.doc-object {
|
||||
padding-left: 10px;
|
||||
border-left: 4px solid rgba(230, 230, 230);
|
||||
}
|
||||
|
||||
.doc-contents .field-body p:first-of-type {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
@@ -295,6 +295,16 @@ page events are called after the [post_template] event and before the
|
||||
show_root_heading: false
|
||||
show_root_toc_entry: false
|
||||
|
||||
### Event Priorities
|
||||
|
||||
For each event type, corresponding methods of plugins are called in the order that the plugins appear in the `plugins` [config][].
|
||||
|
||||
Since MkDocs 1.4, plugins can choose to set a priority value for their events. Events with higher priority are called first. Events without a chosen priority get a default of 0. Events that have the same priority are ordered as they appear in the config.
|
||||
|
||||
::: mkdocs.plugins.event_priority
|
||||
options:
|
||||
show_root_heading: true
|
||||
|
||||
### Handling Errors
|
||||
|
||||
MkDocs defines four error types:
|
||||
|
||||
@@ -16,6 +16,7 @@ else:
|
||||
|
||||
import jinja2.environment
|
||||
|
||||
from mkdocs import utils
|
||||
from mkdocs.config.base import Config, ConfigErrors, ConfigWarnings, PlainConfigSchema
|
||||
from mkdocs.livereload import LiveReloadServer
|
||||
from mkdocs.structure.files import Files
|
||||
@@ -346,6 +347,37 @@ for k in EVENTS:
|
||||
T = TypeVar('T')
|
||||
|
||||
|
||||
def event_priority(priority: float) -> Callable[[T], T]:
|
||||
"""A decorator to set an event priority for an event handler method.
|
||||
|
||||
Recommended priority values:
|
||||
`100` "first", `50` "early", `0` "default", `-50` "late", `-100` "last".
|
||||
As different plugins discover more precise relations to each other, the values should be further tweaked.
|
||||
|
||||
```python
|
||||
@plugins.event_priority(-100) # Wishing to run this after all other plugins' `on_files` events.
|
||||
def on_files(self, files, config, **kwargs):
|
||||
...
|
||||
```
|
||||
|
||||
New in MkDocs 1.4.
|
||||
Recommended shim for backwards compatibility:
|
||||
|
||||
```python
|
||||
try:
|
||||
from mkdocs.plugins import event_priority
|
||||
except ImportError:
|
||||
event_priority = lambda priority: lambda f: f # No-op fallback
|
||||
```
|
||||
"""
|
||||
|
||||
def decorator(event_method):
|
||||
event_method.mkdocs_priority = priority
|
||||
return event_method
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
class PluginCollection(OrderedDict):
|
||||
"""
|
||||
A collection of plugins.
|
||||
@@ -361,7 +393,9 @@ class PluginCollection(OrderedDict):
|
||||
|
||||
def _register_event(self, event_name: str, method: Callable) -> None:
|
||||
"""Register a method for an event."""
|
||||
self.events[event_name].append(method)
|
||||
utils.insort(
|
||||
self.events[event_name], method, key=lambda m: -getattr(m, 'mkdocs_priority', 0)
|
||||
)
|
||||
|
||||
def __setitem__(self, key: str, value: BasePlugin, **kwargs) -> None:
|
||||
if not isinstance(value, BasePlugin):
|
||||
|
||||
@@ -117,6 +117,45 @@ class TestPluginCollection(unittest.TestCase):
|
||||
},
|
||||
)
|
||||
|
||||
def test_event_priorities(self):
|
||||
class PrioPlugin(plugins.BasePlugin):
|
||||
config_scheme = base.get_schema(_DummyPluginConfig)
|
||||
|
||||
@plugins.event_priority(100)
|
||||
def on_pre_page(self, content, **kwargs):
|
||||
pass
|
||||
|
||||
@plugins.event_priority(-100)
|
||||
def on_nav(self, item, **kwargs):
|
||||
pass
|
||||
|
||||
def on_page_read_source(self, **kwargs):
|
||||
pass
|
||||
|
||||
@plugins.event_priority(-50)
|
||||
def on_post_build(self, **kwargs):
|
||||
pass
|
||||
|
||||
collection = plugins.PluginCollection()
|
||||
collection['dummy'] = dummy = DummyPlugin()
|
||||
collection['prio'] = prio = PrioPlugin()
|
||||
self.assertEqual(
|
||||
collection.events['pre_page'],
|
||||
[prio.on_pre_page, dummy.on_pre_page],
|
||||
)
|
||||
self.assertEqual(
|
||||
collection.events['nav'],
|
||||
[dummy.on_nav, prio.on_nav],
|
||||
)
|
||||
self.assertEqual(
|
||||
collection.events['page_read_source'],
|
||||
[dummy.on_page_read_source, prio.on_page_read_source],
|
||||
)
|
||||
self.assertEqual(
|
||||
collection.events['post_build'],
|
||||
[prio.on_post_build],
|
||||
)
|
||||
|
||||
def test_set_plugin_on_collection(self):
|
||||
collection = plugins.PluginCollection()
|
||||
plugin = DummyPlugin()
|
||||
|
||||
@@ -285,6 +285,22 @@ class UtilsTests(unittest.TestCase):
|
||||
[1, 2, 3, 4, 5, 6, 7, 8],
|
||||
)
|
||||
|
||||
def test_insort(self):
|
||||
a = [1, 2, 3]
|
||||
utils.insort(a, 5)
|
||||
self.assertEqual(a, [1, 2, 3, 5])
|
||||
utils.insort(a, -1)
|
||||
self.assertEqual(a, [-1, 1, 2, 3, 5])
|
||||
utils.insort(a, 2)
|
||||
self.assertEqual(a, [-1, 1, 2, 2, 3, 5])
|
||||
utils.insort(a, 4)
|
||||
self.assertEqual(a, [-1, 1, 2, 2, 3, 4, 5])
|
||||
|
||||
def test_insort_key(self):
|
||||
a = [(1, 'a'), (1, 'b'), (2, 'c')]
|
||||
utils.insort(a, (1, 'a'), key=lambda v: v[0])
|
||||
self.assertEqual(a, [(1, 'a'), (1, 'b'), (1, 'a'), (2, 'c')])
|
||||
|
||||
def test_get_themes(self):
|
||||
themes = utils.get_theme_names()
|
||||
self.assertIn('mkdocs', themes)
|
||||
|
||||
@@ -17,7 +17,18 @@ import warnings
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import PurePath
|
||||
from typing import IO, TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Tuple
|
||||
from typing import (
|
||||
IO,
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Dict,
|
||||
Iterable,
|
||||
List,
|
||||
MutableSequence,
|
||||
Optional,
|
||||
Tuple,
|
||||
TypeVar,
|
||||
)
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
@@ -34,6 +45,8 @@ from mkdocs import exceptions
|
||||
if TYPE_CHECKING:
|
||||
from mkdocs.structure.pages import Page
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
markdown_extensions = (
|
||||
@@ -132,6 +145,18 @@ def reduce_list(data_set: Iterable[str]) -> List[str]:
|
||||
return list(dict.fromkeys(data_set))
|
||||
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
from bisect import insort
|
||||
else:
|
||||
|
||||
def insort(a: MutableSequence[T], x: T, *, key=lambda v: v) -> None:
|
||||
kx = key(x)
|
||||
i = len(a)
|
||||
while i > 0 and kx < key(a[i - 1]):
|
||||
i -= 1
|
||||
a.insert(i, x)
|
||||
|
||||
|
||||
def copy_file(source_path: str, output_path: str) -> None:
|
||||
"""
|
||||
Copy source_path to output_path, making sure any parent directories exist.
|
||||
|
||||
Reference in New Issue
Block a user